Skip to content

feat: type optimizations - semantic newtypes and boxed namespace metadata #168

feat: type optimizations - semantic newtypes and boxed namespace metadata

feat: type optimizations - semantic newtypes and boxed namespace metadata #168

Workflow file for this run

name: CI
permissions:
contents: read
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [main]
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUST_BACKTRACE: short
RUSTFLAGS: "-D warnings"
RUSTUP_MAX_RETRIES: 10
# Cancel previous runs on new push
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Detect which files changed to optimize CI
changes:
name: Detect Changes
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
rust-core: ${{ steps.filter.outputs.rust-core }}
python: ${{ steps.filter.outputs.python }}
node: ${{ steps.filter.outputs.node }}
ci: ${{ steps.filter.outputs.ci }}
docs: ${{ steps.filter.outputs.docs }}
full-ci: ${{ steps.check-full-ci.outputs.full-ci }}
steps:
- uses: actions/checkout@v6
- name: Check for full-ci label or main branch push
id: check-full-ci
run: |
FULL_CI="false"
# Always run full CI on main branch pushes
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
FULL_CI="true"
echo "Full CI enabled: main branch push"
fi
# Check for full-ci label on PRs
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'full-ci') }}" == "true" ]]; then
FULL_CI="true"
echo "Full CI enabled: full-ci label present"
fi
fi
echo "full-ci=$FULL_CI" >> $GITHUB_OUTPUT
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
rust-core:
- 'crates/feedparser-rs-core/**'
- 'Cargo.toml'
- 'Cargo.lock'
python:
- 'crates/feedparser-rs-py/**'
- 'crates/feedparser-rs-core/**'
- 'Cargo.toml'
- 'Cargo.lock'
node:
- 'crates/feedparser-rs-node/**'
- 'crates/feedparser-rs-core/**'
- 'Cargo.toml'
- 'Cargo.lock'
ci:
- '.github/workflows/**'
- 'deny.toml'
- 'rustfmt.toml'
- 'Makefile.toml'
docs:
- '**/*.md'
- 'docs/**'
# Fast checks - run in parallel, fail fast
lint-stable:
name: Lint (clippy + docs)
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "lint-stable"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install cargo-make
uses: taiki-e/install-action@cargo-make
- name: Run clippy and doc checks
run: cargo make ci-lint-stable
lint-nightly:
name: Lint (rustfmt)
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- name: Check formatting
run: cargo +nightly fmt --all -- --check
# Security audit
security:
name: Security Audit
needs: [changes]
runs-on: ubuntu-latest
timeout-minutes: 10
# Run on: Rust core changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.rust-core == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "security"
- name: Install cargo-deny
uses: taiki-e/install-action@cargo-deny
- name: Run cargo-deny
run: cargo deny check
# Cross-platform Rust tests
test-rust:
name: Test Rust (${{ matrix.os }})
needs: [changes, lint-stable, lint-nightly]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
# Run on: Rust core changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.rust-core == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Disable Windows Defender real-time monitoring
if: runner.os == 'Windows'
shell: powershell
run: Set-MpPreference -DisableRealtimeMonitoring $true
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "test-rust-${{ matrix.os }}"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install tools
uses: taiki-e/install-action@v2
with:
tool: cargo-make,cargo-nextest
- name: Run Rust tests
run: cargo make ci-test-rust
# Python bindings tests
test-python:
name: Test Python (${{ matrix.os }} - Py${{ matrix.python }})
needs: [changes, lint-stable, lint-nightly]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
# Run on: Python changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.python == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
exclude:
# Reduce matrix size - test all versions on Linux only
- os: macos-latest
python: '3.9'
- os: macos-latest
python: '3.10'
- os: macos-latest
python: '3.11'
- os: windows-latest
python: '3.9'
- os: windows-latest
python: '3.10'
- os: windows-latest
python: '3.11'
steps:
- uses: actions/checkout@v6
- name: Disable Windows Defender real-time monitoring
if: runner.os == 'Windows'
shell: powershell
run: Set-MpPreference -DisableRealtimeMonitoring $true
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "test-python-${{ matrix.os }}"
save-if: ${{ github.ref == 'refs/heads/main' }}
workspaces: crates/feedparser-rs-py
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install cargo-make
uses: taiki-e/install-action@cargo-make
- name: Run Python tests
run: cargo make ci-test-python
# Node.js bindings tests
test-node:
name: Test Node.js (${{ matrix.os }} - Node ${{ matrix.node }})
needs: [changes, lint-stable, lint-nightly]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
# Run on: Node changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.node == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ['20']
include:
# Test Node 22 only on Linux
- os: ubuntu-latest
node: '22'
steps:
- uses: actions/checkout@v6
- name: Disable Windows Defender real-time monitoring
if: runner.os == 'Windows'
shell: powershell
run: Set-MpPreference -DisableRealtimeMonitoring $true
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "test-node-${{ matrix.os }}"
save-if: ${{ github.ref == 'refs/heads/main' }}
workspaces: crates/feedparser-rs-node
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: 'pnpm'
cache-dependency-path: crates/feedparser-rs-node/pnpm-lock.yaml
- name: Install cargo-make
uses: taiki-e/install-action@cargo-make
- name: Run Node.js tests
run: cargo make ci-test-node
# Rust code coverage
coverage-rust:
name: Rust Code Coverage
needs: [changes, lint-stable, lint-nightly]
runs-on: ubuntu-latest
timeout-minutes: 20
# Run on: Rust core changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.rust-core == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "coverage-rust"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install tools
uses: taiki-e/install-action@v2
with:
tool: cargo-make,cargo-llvm-cov,cargo-nextest
- name: Generate Rust coverage
run: cargo make ci-coverage-rust
- name: Upload to codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov.info
fail_ci_if_error: true
flags: rust-core
verbose: true
# Python code coverage
coverage-python:
name: Python Code Coverage
needs: [changes, lint-stable, lint-nightly]
runs-on: ubuntu-latest
timeout-minutes: 15
# Run on: Python changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.python == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "coverage-python"
workspaces: crates/feedparser-rs-py
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install cargo-make
uses: taiki-e/install-action@cargo-make
- name: Generate Python coverage
run: cargo make ci-coverage-python
continue-on-error: true
- name: Upload Python coverage to codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: crates/feedparser-rs-py/coverage.xml
fail_ci_if_error: false
flags: python-bindings
if: hashFiles('crates/feedparser-rs-py/tests/*.py') != ''
# Node.js code coverage
coverage-node:
name: Node.js Code Coverage
needs: [changes, lint-stable, lint-nightly]
runs-on: ubuntu-latest
timeout-minutes: 15
# Run on: Node changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.node == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "coverage-node"
workspaces: crates/feedparser-rs-node
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: crates/feedparser-rs-node/pnpm-lock.yaml
- name: Install cargo-make
uses: taiki-e/install-action@cargo-make
- name: Generate Node.js coverage
run: cargo make ci-coverage-node
continue-on-error: true
- name: Upload Node.js coverage to codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: crates/feedparser-rs-node/coverage
fail_ci_if_error: false
flags: node-bindings
# MSRV check
msrv:
name: Check MSRV (1.88.0)
needs: [changes, lint-stable, lint-nightly]
runs-on: ubuntu-latest
timeout-minutes: 15
# Run on: Rust core changes, CI config changes, or full CI mode
if: |
needs.changes.outputs.rust-core == 'true' ||
needs.changes.outputs.ci == 'true' ||
needs.changes.outputs.full-ci == 'true'
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Install Rust 1.88.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.88.0"
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: "msrv"
- name: Install cargo-make
uses: taiki-e/install-action@cargo-make
- name: Check with MSRV
run: cargo make ci-msrv
# All checks passed gate
ci-success:
name: CI Success
needs: [changes, lint-stable, lint-nightly, security, test-rust, test-python, test-node, coverage-rust, coverage-python, coverage-node, msrv]
runs-on: ubuntu-latest
if: always()
permissions:
contents: read
steps:
- name: Check all jobs
run: |
# Helper function to check job result (success or skipped is OK)
check_job() {
local job_name=$1
local job_result=$2
if [[ "$job_result" == "success" ]] || [[ "$job_result" == "skipped" ]]; then
echo "$job_name: $job_result ✓"
return 0
else
echo "$job_name: $job_result ✗"
return 1
fi
}
# Track overall status
FAILED=0
# Lint jobs must always succeed (never skipped)
if [[ "${{ needs.lint-stable.result }}" != "success" ]]; then
echo "lint-stable: ${{ needs.lint-stable.result }} ✗ (must succeed)"
FAILED=1
else
echo "lint-stable: ${{ needs.lint-stable.result }} ✓"
fi
if [[ "${{ needs.lint-nightly.result }}" != "success" ]]; then
echo "lint-nightly: ${{ needs.lint-nightly.result }} ✗ (must succeed)"
FAILED=1
else
echo "lint-nightly: ${{ needs.lint-nightly.result }} ✓"
fi
# Check all other jobs (success or skipped is OK)
check_job "security" "${{ needs.security.result }}" || FAILED=1
check_job "test-rust" "${{ needs.test-rust.result }}" || FAILED=1
check_job "test-python" "${{ needs.test-python.result }}" || FAILED=1
check_job "test-node" "${{ needs.test-node.result }}" || FAILED=1
check_job "coverage-rust" "${{ needs.coverage-rust.result }}" || FAILED=1
check_job "coverage-python" "${{ needs.coverage-python.result }}" || FAILED=1
check_job "coverage-node" "${{ needs.coverage-node.result }}" || FAILED=1
check_job "msrv" "${{ needs.msrv.result }}" || FAILED=1
if [[ $FAILED -eq 1 ]]; then
echo ""
echo "❌ One or more jobs failed"
exit 1
fi
echo ""
echo "✅ All CI jobs passed successfully!"