CI #268
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: CI | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| branches: [main, master] | |
| schedule: | |
| # Run daily at 1 AM UTC | |
| - cron: '0 1 * * *' | |
| permissions: | |
| contents: write # Required for gh-pages deployment | |
| issues: write | |
| pull-requests: write | |
| pages: write | |
| id-token: write | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| jobs: | |
| test: | |
| name: Test Suite | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-22.04, ubuntu-latest, macos-latest, windows-latest] | |
| rust: [stable, beta] | |
| include: | |
| # Test on nightly but allow failures | |
| - os: ubuntu-latest | |
| rust: nightly | |
| allow-failure: true | |
| # Use ubuntu-latest for Linux stable builds | |
| - os: ubuntu-latest | |
| rust: stable | |
| allow-failure: false | |
| runs-on: ${{ matrix.os }} | |
| continue-on-error: ${{ matrix.allow-failure || false }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.rust }} | |
| components: rustfmt, clippy | |
| - name: Cache Cargo dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| key: ${{ runner.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.rust }}-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb', '**/package.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install Bun for frontend tests | |
| uses: oven-sh/setup-bun@v1 | |
| with: | |
| bun-version: latest | |
| - name: Install frontend dependencies | |
| run: bun install | |
| - name: Install Pagefind CLI | |
| run: npm install -g pagefind | |
| - name: Check formatting | |
| if: matrix.rust == 'stable' | |
| run: cargo fmt --all -- --check | |
| - name: Run Clippy | |
| if: matrix.rust == 'stable' | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| - name: Run unit tests | |
| run: cargo test --lib --bins | |
| - name: Run integration tests | |
| run: cargo test --test integration --features "tokio,search,syntax-highlighting" | |
| - name: Run end-to-end tests | |
| run: cargo test --test e2e | |
| env: | |
| RUST_LOG: debug | |
| - name: Run frontend tests | |
| run: bun test | |
| - name: Install benchmark dependencies | |
| if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' | |
| run: sudo apt-get update && sudo apt-get install -y bc jq | |
| - name: Run performance benchmarks | |
| if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' | |
| run: | | |
| chmod +x ./scripts/bench.sh | |
| ./scripts/bench.sh --quick | |
| timeout-minutes: 10 | |
| continue-on-error: false # Fail CI if significant regressions detected | |
| - name: Test documentation build | |
| if: matrix.rust == 'stable' | |
| run: cargo doc --no-deps --document-private-items | |
| - name: Check example builds | |
| run: | | |
| mkdir -p example_input | |
| echo "# Example\nThis is an example page." > example_input/index.md | |
| cargo run -- -i example_input -o example_output | |
| wasm: | |
| name: WebAssembly Tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: wasm32-unknown-unknown | |
| - name: Cache Cargo dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-wasm-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-wasm-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Build for WebAssembly | |
| run: cargo build --target wasm32-unknown-unknown --lib --features wasm-core --no-default-features | |
| - name: Run WASM tests | |
| run: | | |
| # WASM compilation already verified by build step above | |
| # Skip running tests since criterion dev dependency doesn't work on WASM | |
| echo "✅ WASM compilation verified by build step" | |
| continue-on-error: true # WASM tests might not work in CI environment | |
| feature-tests: | |
| name: Feature Combination Tests | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| features: | |
| - "default" | |
| - "server,watcher,search,syntax-highlighting" | |
| - "wasm-core" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Set cache key | |
| id: cache-key | |
| run: | | |
| if [ "${{ matrix.features }}" = "" ]; then | |
| echo "key=${{ runner.os }}-feature-none-cargo-${{ hashFiles('**/Cargo.lock') }}" >> $GITHUB_OUTPUT | |
| else | |
| # Replace commas with dashes for cache key | |
| sanitized=$(echo "${{ matrix.features }}" | sed 's/,/-/g') | |
| echo "key=${{ runner.os }}-feature-$sanitized-cargo-${{ hashFiles('**/Cargo.lock') }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Cache Cargo dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ steps.cache-key.outputs.key }} | |
| restore-keys: | | |
| ${{ runner.os }}-feature-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| - name: Run tests with features | |
| run: | | |
| if [ "${{ matrix.features }}" = "default" ]; then | |
| cargo test | |
| else | |
| cargo test --no-default-features --features "${{ matrix.features }}" | |
| fi | |
| msrv: | |
| name: Minimum Supported Rust Version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install MSRV Rust toolchain | |
| uses: dtolnay/rust-toolchain@1.70.0 # Adjust based on actual MSRV | |
| - name: Cache Cargo dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-msrv-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-msrv-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Check MSRV compilation | |
| run: cargo check --lib | |
| - name: Run MSRV tests | |
| run: cargo test --lib | |
| security: | |
| name: Security Audit | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache Cargo dependencies and tools | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-audit-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-audit-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Install cargo-audit | |
| run: | | |
| # Check if cargo-audit is already installed | |
| if ! command -v cargo-audit &> /dev/null; then | |
| cargo install --locked cargo-audit | |
| else | |
| echo "cargo-audit is already installed: $(cargo-audit --version)" | |
| fi | |
| - name: Run security audit | |
| run: cargo audit | |
| coverage: | |
| name: Code Coverage | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: llvm-tools-preview | |
| - name: Cache Cargo dependencies and tools | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-coverage-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-coverage-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Install cargo-tarpaulin | |
| run: | | |
| # Check if cargo-tarpaulin is already installed | |
| if ! command -v cargo-tarpaulin &> /dev/null; then | |
| cargo install --locked cargo-tarpaulin | |
| else | |
| echo "cargo-tarpaulin is already installed: $(cargo-tarpaulin --version)" | |
| fi | |
| - name: Generate coverage report | |
| run: | | |
| cargo tarpaulin --out Xml --skip-clean | |
| continue-on-error: true | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| file: cobertura.xml | |
| fail_ci_if_error: false | |
| performance: | |
| name: Performance Regression | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| 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/ | |
| target/ | |
| key: ${{ runner.os }}-bench-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bench-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Install benchmark dependencies | |
| run: sudo apt-get update && sudo apt-get install -y bc jq | |
| - name: Cache benchmark results | |
| uses: actions/cache@v4 | |
| with: | |
| path: benchmark-results/ | |
| key: ${{ runner.os }}-benchmarks-${{ github.base_ref || github.ref_name }} | |
| restore-keys: | | |
| ${{ runner.os }}-benchmarks-main | |
| ${{ runner.os }}-benchmarks- | |
| - name: Run performance benchmarks | |
| run: | | |
| chmod +x ./scripts/bench.sh | |
| ./scripts/bench.sh --quick | |
| timeout-minutes: 10 | |
| - name: Upload benchmark results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: benchmark-results-${{ github.sha }} | |
| path: benchmark-results/ | |
| retention-days: 30 | |
| - name: Comment benchmark results on PR | |
| if: github.event_name == 'pull_request' && always() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = 'benchmark-results/current.json'; | |
| try { | |
| let body; | |
| if (fs.existsSync(path)) { | |
| const results = fs.readFileSync(path, 'utf8'); | |
| const truncatedResults = results.slice(0, 2000); | |
| const wasTruncated = results.length > 2000; | |
| // Parse JSON results and create a nice table | |
| let parsed; | |
| try { | |
| parsed = JSON.parse(results); | |
| } catch (e) { | |
| parsed = null; | |
| } | |
| if (parsed && parsed.benchmarks && parsed.benchmarks.length > 0) { | |
| let tableRows = ''; | |
| parsed.benchmarks.forEach(bench => { | |
| if (bench.benchmark_name && bench.mean && bench.mean.estimate) { | |
| const timeMs = (bench.mean.estimate / 1000000).toFixed(3); | |
| tableRows += `| ${bench.benchmark_name} | ${timeMs} ms |\n`; | |
| } | |
| }); | |
| if (tableRows) { | |
| body = `## 📊 Performance Benchmark Results\n\n| Benchmark | Time |\n|-----------|------|\n${tableRows}\n\n<details>\n<summary>Raw Results ${wasTruncated ? '(truncated)' : ''}</summary>\n\n\`\`\`json\n${truncatedResults}${wasTruncated ? '\n...\n[Results truncated - see artifacts for full output]' : ''}\n\`\`\`\n\n</details>`; | |
| } else { | |
| body = `## 📊 Performance Benchmark Results\n\nBenchmark completed successfully.\n\n<details>\n<summary>Raw Results ${wasTruncated ? '(truncated)' : ''}</summary>\n\n\`\`\`json\n${truncatedResults}${wasTruncated ? '\n...\n[Results truncated - see artifacts for full output]' : ''}\n\`\`\`\n\n</details>`; | |
| } | |
| } else { | |
| body = `## 📊 Performance Benchmark Results\n\nBenchmark completed successfully. Check the uploaded artifacts for detailed results.\n\n<details>\n<summary>Raw Results ${wasTruncated ? '(truncated)' : ''}</summary>\n\n\`\`\`json\n${truncatedResults}${wasTruncated ? '\n...\n[Results truncated - see artifacts for full output]' : ''}\n\`\`\`\n\n</details>`; | |
| } | |
| } else { | |
| body = '## ⚠️ Performance Benchmark Results\n\nBenchmark completed but no results file was generated. Please check the workflow logs and uploaded artifacts.'; | |
| } | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| } catch (error) { | |
| console.error('Failed to post benchmark comment:', error); | |
| // Try to post a simple error message | |
| try { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: '## ❌ Performance Benchmark Failed\n\nFailed to post benchmark results. Check workflow logs for details.' | |
| }); | |
| } catch (commentError) { | |
| console.error('Failed to post error comment:', commentError); | |
| // Don't fail the workflow if we can't post comments | |
| } | |
| } | |
| docs: | |
| name: Documentation | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| 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/ | |
| target/ | |
| key: ${{ runner.os }}-doc-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-doc-cargo- | |
| ${{ runner.os }}-stable-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Build documentation | |
| run: | | |
| cargo doc --no-deps --document-private-items --all-features | |
| echo '<meta http-equiv="refresh" content="0; url=md_book">' > target/doc/index.html | |
| - name: Deploy documentation | |
| uses: peaceiris/actions-gh-pages@v3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_dir: ./target/doc | |
| destination_dir: docs |