name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
CARGO_NET_GIT_FETCH_WITH_CLI: false
permissions:
contents: read
checks: write
pull-requests: write
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
shared-key: lint
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-features --all-targets --workspace -- -D warnings
- name: Check docs
run: cargo doc --all-features --no-deps --workspace
openspec:
name: OpenSpec Validation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install openspec
run: |
if ! command -v openspec &> /dev/null; then
cargo install --git https://github.com/anomalyco/openspec --locked
fi
- name: Cache openspec
uses: actions/cache@v6
with:
path: ~/.cargo/bin/openspec
key: ${{ runner.os }}-openspec-${{ hashFiles('**/Cargo.lock') }}
- name: Run openspec validation
run: openspec validate --strict
- name: List active changes
run: openspec list
security:
name: Security Audit
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo-deny
uses: actions/cache@v6
with:
path: |
~/.cargo/bin/cargo-deny
~/.cargo/registry/index
key: ${{ runner.os }}-cargo-deny-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-deny-
- name: Install cargo-deny
run: |
if [ ! -f ~/.cargo/bin/cargo-deny ]; then
cargo install --locked cargo-deny
fi
- name: Run cargo-deny check
run: cargo deny check license advisories
- name: Check for vulnerable dependencies
run: cargo deny check bans --show-transitive
test:
name: Test (${{ matrix.db }})
needs: [lint, openspec, security]
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
db: [sqlite, postgres, mysql]
include:
- db: sqlite
db-image: sqlite
- db: postgres
db-image: postgres:15-alpine
- db: mysql
db-image: mysql:8.0
services:
postgres:
image: postgres:15-alpine
ports:
- 5432:5432
env:
POSTGRES_USER: dbnexus
POSTGRES_PASSWORD: dbnexus_password
POSTGRES_DB: dbnexus_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
mysql:
image: mysql:8.0
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: dbnexus_password
MYSQL_DATABASE: dbnexus_test
MYSQL_USER: dbnexus
MYSQL_PASSWORD: dbnexus_password
options: >-
--health-cmd "mysqladmin ping -h localhost"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
shared-key: ${{ matrix.db }}
${{ runner.os }}-
- name: Set up database environment
run: |
echo "DATABASE_URL=${{ matrix.db }}://dbnexus:dbnexus_password@localhost:5432/dbnexus_test" >> $GITHUB_ENV
if [ "${{ matrix.db }}" == "mysql" ]; then
echo "DATABASE_URL=mysql://dbnexus:dbnexus_password@localhost:3306/dbnexus_test" >> $GITHUB_ENV
fi
- name: Wait for database
run: |
if [ "${{ matrix.db }}" == "postgres" ]; then
for i in {1..30}; do
if pg_isready -h localhost -p 5432 -q; then
echo "PostgreSQL is ready"
break
fi
echo "Waiting for PostgreSQL..."
sleep 2
done
fi
if [ "${{ matrix.db }}" == "mysql" ]; then
for i in {1..30}; do
if mysqladmin ping -h localhost -P 3306 -q; then
echo "MySQL is ready"
break
fi
echo "Waiting for MySQL..."
sleep 2
done
fi
- name: Run tests
run: cargo test --features ${{ matrix.db }},all-optional --workspace
- name: Run doc tests
run: cargo test --features ${{ matrix.db }},all-optional --doc --workspace
build:
name: Build (${{ matrix.os }}-${{ matrix.db }})
needs: [lint, openspec, security]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
db: [sqlite]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
shared-key: ${{ matrix.db }}
${{ runner.os }}-
- name: Build
run: cargo build --features ${{ matrix.db }},all-optional --workspace --target ${{ matrix.target }}
- name: Build release
run: cargo build --features ${{ matrix.db }},all-optional --workspace --target ${{ matrix.target }} --release
coverage:
name: Code Coverage
needs: test
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache tarpaulin
uses: actions/cache@v6
with:
path: |
~/.cargo/bin/cargo-tarpaulin
target
key: ${{ runner.os }}-tarpaulin-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-tarpaulin-
- name: Install tarpaulin
run: |
if [ ! -f ~/.cargo/bin/cargo-tarpaulin ]; then
cargo install cargo-tarpaulin
fi
- name: Generate coverage
run: |
cargo tarpaulin --verbose --features sqlite,all-optional --workspace --exclude dbnexus-cli --exclude dbnexus-macros --timeout 300 --out xml --exclude-files "**/tests/**"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
files: ./cobertura.xml
directory: coverage-report
flags: unittests
name: codecov-umbrella
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@v2
with:
parallel: true
flag-name: Unit Tests
docs:
name: Documentation
needs: [lint, openspec]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
shared-key: docs
- name: Build documentation
run: cargo doc --no-deps --features sqlite,all-optional --workspace
- name: Check documentation links
run: |
# 验证文档构建成功
ls -la target/doc/dbnexus/
- name: Verify docs.rs configuration
run: |
# 检查 docs.rs 配置是否正确
cargo metadata --format-version 1 | jq -r '.[0].metadata["docs.rs"]'
dependabot:
name: Dependency Updates
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v7
- name: Check for outdated dependencies
run: |
cargo outdated --root-deps-only --exit-code 1 || echo "OUTDATED=true" >> $GITHUB_ENV
- name: Report outdated dependencies
if: env.OUTDATED == 'true'
run: |
echo "⚠️ Some dependencies are outdated"
cargo outdated --root-deps-only
status:
name: Status Check
needs: [test, build, coverage, docs]
runs-on: ubuntu-latest
timeout-minutes: 5
if: always()
steps:
- name: Check status
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 CI Status Summary"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "✅ Lint: ${{ needs.lint.result }}"
echo "✅ OpenSpec: ${{ needs.openspec.result }}"
echo "✅ Security: ${{ needs.security.result }}"
echo "✅ Test: ${{ needs.test.result }}"
echo "✅ Build: ${{ needs.build.result }}"
echo "✅ Coverage: ${{ needs.coverage.result }}"
echo "✅ Documentation: ${{ needs.docs.result }}"
echo ""
if [[ "${{ needs.test.result }}" == "success" ]] && \
[[ "${{ needs.build.result }}" == "success" ]]; then
echo "🎉 All critical checks passed!"
exit 0
else
echo "❌ Some checks failed"
exit 1
fi