Skip to content

Merge pull request #24 from link-foundation/issue-19-d88ab983ff7d #21

Merge pull request #24 from link-foundation/issue-19-d88ab983ff7d

Merge pull request #24 from link-foundation/issue-19-d88ab983ff7d #21

Workflow file for this run

name: Rust CI/CD
on:
push:
branches:
- main
paths:
- 'rust/**'
- '.github/workflows/rust.yml'
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'rust/**'
- '.github/workflows/rust.yml'
workflow_dispatch:
inputs:
release_mode:
description: 'Manual release mode'
required: true
type: choice
default: 'instant'
options:
- instant
- changelog-pr
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
description:
description: 'Release description (optional)'
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
jobs:
# === DETECT CHANGES - determines which jobs should run ===
detect-changes:
name: Detect Changes
runs-on: ubuntu-latest
if: github.event_name != 'workflow_dispatch'
outputs:
rs-changed: ${{ steps.changes.outputs.rs-changed }}
toml-changed: ${{ steps.changes.outputs.toml-changed }}
mjs-changed: ${{ steps.changes.outputs.mjs-changed }}
docs-changed: ${{ steps.changes.outputs.docs-changed }}
workflow-changed: ${{ steps.changes.outputs.workflow-changed }}
any-code-changed: ${{ steps.changes.outputs.any-code-changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Detect changes
id: changes
working-directory: ./rust
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
# === CHANGELOG CHECK - only runs on PRs with code changes ===
# Docs-only PRs (./docs folder, markdown files) don't require changelog fragments
changelog:
name: Changelog Fragment Check
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Check for changelog fragments
working-directory: ./rust
env:
GITHUB_BASE_REF: ${{ github.base_ref }}
run: node scripts/check-changelog-fragment.mjs
# === VERSION CHECK - prevents manual version modification in PRs ===
# This ensures versions are only modified by the automated release pipeline
version-check:
name: Version Modification Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Check for manual version changes
working-directory: ./rust
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_BASE_REF: ${{ github.base_ref }}
run: node scripts/check-version-modification.mjs
# === LINT AND FORMAT CHECK ===
# Lint runs independently of changelog check - it's a fast check that should always run
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
needs: [detect-changes]
# Note: always() is required because detect-changes is skipped on workflow_dispatch
if: |
always() && !cancelled() && (
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.rs-changed == 'true' ||
needs.detect-changes.outputs.toml-changed == 'true' ||
needs.detect-changes.outputs.mjs-changed == 'true' ||
needs.detect-changes.outputs.docs-changed == 'true' ||
needs.detect-changes.outputs.workflow-changed == 'true'
)
steps:
- uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Cache cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
rust/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
- name: Check formatting
working-directory: ./rust
run: cargo fmt --check
- name: Run clippy
working-directory: ./rust
run: cargo clippy --all-targets --all-features
- name: Check file size limit
working-directory: ./rust
run: node scripts/check-file-size.mjs
# === TEST ===
# Test runs independently of changelog check
test:
name: Test (Rust on ${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [detect-changes, changelog]
# Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR)
if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
rust/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
- name: Run tests
working-directory: ./rust
run: cargo test --verbose
- name: Run doc tests
working-directory: ./rust
run: cargo test --doc --verbose
- name: Run example
working-directory: ./rust
run: cargo run --example basic_usage
# === BUILD ===
# Build package - only runs if lint and test pass
build:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, test]
if: always() && !cancelled() && needs.lint.result == 'success' && needs.test.result == 'success'
steps:
- uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
rust/target/
key: ${{ runner.os }}-cargo-build-${{ hashFiles('rust/Cargo.lock') }}
- name: Build release
working-directory: ./rust
run: cargo build --release
- name: Package crate
working-directory: ./rust
run: cargo package --list
# === AUTO RELEASE ===
# Automatic release on push to main using changelog fragments
auto-release:
name: Auto Release
needs: [lint, test, build]
if: |
always() && !cancelled() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
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: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Configure git
working-directory: ./rust
run: node scripts/git-config.mjs
- name: Determine bump type from changelog fragments
id: bump_type
working-directory: ./rust
run: node scripts/get-bump-type.mjs
- name: Check if version already released
id: version_check
working-directory: ./rust
run: |
# Get current version from Cargo.toml
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml)
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
# Check if tag exists
if git rev-parse "rust-v$CURRENT_VERSION" >/dev/null 2>&1; then
echo "Tag rust-v$CURRENT_VERSION already exists"
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "New version detected: $CURRENT_VERSION"
echo "should_release=true" >> $GITHUB_OUTPUT
fi
- name: Collect changelog and bump version
id: version
if: steps.version_check.outputs.should_release == 'true' && steps.bump_type.outputs.has_fragments == 'true'
working-directory: ./rust
run: |
node scripts/version-and-commit.mjs \
--bump-type "${{ steps.bump_type.outputs.bump_type }}"
- name: Get current version
id: current_version
if: steps.version_check.outputs.should_release == 'true'
working-directory: ./rust
run: |
VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build release
if: steps.version_check.outputs.should_release == 'true'
working-directory: ./rust
run: cargo build --release
- name: Publish to crates.io
if: steps.version_check.outputs.should_release == 'true'
working-directory: ./rust
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}
run: |
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
echo "::warning::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set, skipping publish to crates.io"
else
cargo publish
fi
- name: Create GitHub Release
if: steps.version_check.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: ./rust
run: |
node scripts/create-github-release.mjs \
--version "${{ steps.current_version.outputs.version }}" \
--repository "${{ github.repository }}" \
--tag-prefix "rust-v"
# === MANUAL INSTANT RELEASE ===
# Manual release via workflow_dispatch - only after CI passes
manual-release:
name: Instant Release
needs: [lint, test, build]
if: |
always() && !cancelled() &&
github.event_name == 'workflow_dispatch' &&
github.event.inputs.release_mode == 'instant' &&
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: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Configure git
working-directory: ./rust
run: node scripts/git-config.mjs
- name: Collect changelog fragments
working-directory: ./rust
run: node scripts/collect-changelog.mjs
- name: Version and commit
id: version
working-directory: ./rust
env:
BUMP_TYPE: ${{ github.event.inputs.bump_type }}
DESCRIPTION: ${{ github.event.inputs.description }}
run: node scripts/version-and-commit.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}"
- name: Build release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
working-directory: ./rust
run: cargo build --release
- name: Publish to crates.io
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
working-directory: ./rust
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}
run: |
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
echo "::warning::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set, skipping publish to crates.io"
else
cargo publish
fi
- name: Create GitHub Release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: ./rust
run: |
node scripts/create-github-release.mjs \
--version "${{ steps.version.outputs.new_version }}" \
--repository "${{ github.repository }}" \
--tag-prefix "rust-v"
# === MANUAL CHANGELOG PR ===
changelog-pr:
name: 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@v4
with:
node-version: '22'
- name: Create changelog fragment
working-directory: ./rust
run: |
# Create timestamp for unique filename
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
FILENAME="changelog.d/${TIMESTAMP}_manual_release.md"
# Create the changelog fragment
cat > "$FILENAME" << EOF
---
bump: ${{ github.event.inputs.bump_type }}
---
${{ github.event.inputs.description || 'Manual release' }}
EOF
echo "Created changelog fragment: $FILENAME"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: add changelog for manual ${{ github.event.inputs.bump_type }} release'
branch: changelog-manual-release-${{ github.run_id }}
delete-branch: true
title: 'chore: manual ${{ github.event.inputs.bump_type }} release (Rust)'
body: |
## Manual Release Request (Rust)
This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release for the Rust package.
### Release Details
- **Type:** ${{ github.event.inputs.bump_type }}
- **Description:** ${{ github.event.inputs.description || 'Manual release' }}
- **Triggered by:** @${{ github.actor }}
### Next Steps
1. Review the changelog fragment in this PR
2. Merge this PR to main
3. The automated release workflow will publish to crates.io and create a GitHub release