fix(cli): distinguish config-load errors and reject symlinked entries… #822
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: [master, main, develop] | |
| pull_request: | |
| branches: [master, main, develop] | |
| schedule: | |
| # Weekly security audit on Sundays at midnight | |
| - cron: "0 0 * * 0" | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_NET_RETRY: 10 | |
| RUST_BACKTRACE: short | |
| # Don't use RUSTFLAGS=-D warnings, use workspace lints configuration instead | |
| RUSTUP_MAX_RETRIES: 10 | |
| # Cancel previous runs on new push | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Detect whether the diff touches anything beyond documentation so the full | |
| # check chain can be skipped on docs-only changes. Workflow file changes | |
| # always force the full chain to run, since they could be the thing that's | |
| # broken. Scheduled runs (weekly security audit) always run everything too, | |
| # since there's no meaningful diff to compare against. | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| outputs: | |
| run-checks: ${{ github.event_name == 'schedule' || steps.filter.outputs.code == 'true' || steps.filter.outputs.workflows == 'true' }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Check changed paths | |
| if: github.event_name != 'schedule' | |
| uses: dorny/paths-filter@v4 | |
| id: filter | |
| with: | |
| filters: | | |
| code: | |
| - '**' | |
| - '!**/*.md' | |
| - '!specs/**' | |
| - '!docs/**' | |
| - '!README*' | |
| - '!CHANGELOG*' | |
| - '!CONTRIBUTING*' | |
| - '!LICENSE*' | |
| - '!SECURITY*' | |
| workflows: | |
| - '.github/workflows/**' | |
| # Quick checks first - fail fast strategy | |
| check: | |
| name: Check | |
| needs: [changes] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| if: always() && needs.changes.outputs.run-checks == 'true' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - name: Install Rust nightly (for rustfmt) | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| components: rustfmt | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "check" | |
| save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} | |
| - name: Check formatting (nightly) | |
| run: cargo +nightly fmt --all -- --check | |
| - name: Install cargo-sort | |
| uses: taiki-e/install-action@v2.85.10 | |
| with: | |
| tool: cargo-sort | |
| - name: Check Cargo.toml dependency order | |
| run: cargo sort --check --workspace --order package,lints,workspace,lib,bin,features,dependencies,build-dependencies,dev-dependencies | |
| - name: Clippy (all targets, all features) | |
| run: cargo +stable clippy --all-targets --all-features --workspace | |
| - name: Check documentation | |
| run: cargo +stable doc --no-deps --all-features --workspace | |
| env: | |
| RUSTDOCFLAGS: "-D warnings" | |
| # Security audit using cargo-deny | |
| security: | |
| name: Security Audit | |
| needs: [changes] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| if: always() && needs.changes.outputs.run-checks == 'true' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Run cargo-deny (advisories, licenses, bans, sources) | |
| uses: EmbarkStudios/cargo-deny-action@v2 | |
| with: | |
| log-level: warn | |
| command: check | |
| arguments: --all-features | |
| command-arguments: advisories licenses bans sources | |
| # Cross-platform build matrix, run in parallel with `test` (not sequentially | |
| # before it): nextest builds whatever it needs on its own, so this job only | |
| # verifies that all targets/features compile across the full OS/toolchain | |
| # matrix without blocking the test job. | |
| build: | |
| name: Build (${{ matrix.os }}, Rust ${{ matrix.rust }}) | |
| needs: [changes, check] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| if: needs.changes.outputs.run-checks == 'true' && needs.check.result == 'success' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| rust: [stable] | |
| include: | |
| # Test on beta channel on Linux only | |
| - os: ubuntu-latest | |
| rust: beta | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust ${{ matrix.rust }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.rust }} | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "build-${{ matrix.os }}-${{ matrix.rust }}" | |
| save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} | |
| - name: Build (all targets, all features) | |
| run: cargo build --all-targets --all-features --workspace | |
| # Cross-platform tests with matrix | |
| test: | |
| name: Test (${{ matrix.os }}, Rust ${{ matrix.rust }}) | |
| needs: [changes, check] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| if: needs.changes.outputs.run-checks == 'true' && needs.check.result == 'success' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| rust: [stable] | |
| include: | |
| # Test on beta channel on Linux only | |
| - os: ubuntu-latest | |
| rust: beta | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust ${{ matrix.rust }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.rust }} | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "test-${{ matrix.os }}-${{ matrix.rust }}" | |
| save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} | |
| - name: Install nextest | |
| uses: taiki-e/install-action@v2.85.10 | |
| with: | |
| tool: nextest | |
| # mcp-execution-codegen's test suite compiles and executes generated TypeScript under | |
| # `tsc`/`node` (e.g. the #201 runtime-bridge regression tests) to catch TypeScript-level | |
| # regressions that a Rust-only check can't see. Those tests hard-fail (rather than | |
| # silently skip) when the `CI` env var is set, so this toolchain must be present here. | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| - name: Install TypeScript | |
| run: npm install -g typescript | |
| - name: Run tests (nextest) | |
| run: cargo nextest run --all-features --workspace --no-fail-fast | |
| - name: Run doctests | |
| run: cargo test --doc --all-features --workspace | |
| # Code coverage (Linux only for speed) | |
| coverage: | |
| name: Code Coverage | |
| needs: [changes, check] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: write # For uploading artifacts | |
| if: needs.changes.outputs.run-checks == 'true' && needs.check.result == 'success' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install llvm-cov | |
| uses: taiki-e/install-action@v2.85.10 | |
| with: | |
| tool: cargo-llvm-cov | |
| - name: Install nextest | |
| uses: taiki-e/install-action@v2.85.10 | |
| with: | |
| tool: nextest | |
| # See the `test` job for why this toolchain is required: mcp-execution-codegen's | |
| # TypeScript-executing regression tests hard-fail under `CI` rather than skip. | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| - name: Install TypeScript | |
| run: npm install -g typescript | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "coverage" | |
| save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} | |
| - name: Generate coverage (nextest) | |
| run: | | |
| cargo llvm-cov --all-features --workspace --lcov \ | |
| --output-path lcov.info nextest | |
| - name: Upload to codecov | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: lcov.info | |
| fail_ci_if_error: false | |
| verbose: true | |
| flags: mcp-core,mcp-introspector,mcp-codegen,mcp-files,mcp-skill,mcp-server,mcp-cli | |
| name: codecov-umbrella | |
| - name: Archive coverage report | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage-report | |
| path: lcov.info | |
| retention-days: 30 | |
| # MSRV check (Minimum Supported Rust Version) | |
| msrv: | |
| name: Check MSRV (1.91) | |
| needs: [changes, check] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| if: needs.changes.outputs.run-checks == 'true' && needs.check.result == 'success' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust 1.91 | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: "1.91" | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "msrv" | |
| - name: Check with MSRV | |
| run: cargo check --all-features --workspace | |
| # Benchmarks (build only, don't run in CI) | |
| benchmark: | |
| name: Benchmark Build | |
| needs: [changes, check] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| permissions: | |
| contents: read | |
| if: needs.changes.outputs.run-checks == 'true' && needs.check.result == 'success' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "bench" | |
| - name: Build benchmarks (no run) | |
| run: cargo bench --no-run --profile bench-fast --workspace | |
| # Release build check (master only — skip on PRs and other branches) | |
| release: | |
| name: Release Build | |
| needs: [changes, test] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| permissions: | |
| contents: read | |
| if: github.ref == 'refs/heads/master' && needs.changes.outputs.run-checks == 'true' && needs.test.result == 'success' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "release" | |
| - name: Build release | |
| run: cargo build --release --all-features --workspace | |
| - name: Check binary sizes | |
| run: | | |
| echo "Binary sizes:" | |
| ls -lh target/release/ | grep -E "(mcp-execution-cli|mcp-examples)" || true | |
| echo "" | |
| echo "Target directory size:" | |
| du -sh target/release/ | |
| # All checks passed | |
| ci-success: | |
| name: CI Success | |
| needs: [changes, check, security, build, test, coverage, msrv, benchmark] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Check all jobs | |
| # `skipped` is an acceptable outcome here: on docs-only diffs, the | |
| # gated jobs (check/security/build/test/coverage/msrv/benchmark) skip | |
| # themselves via the `changes` job's `run-checks` output rather than | |
| # failing, and this status check must still report success/neutral | |
| # rather than staying red or permanently pending. | |
| run: | | |
| for result in \ | |
| "${{ needs.changes.result }}" \ | |
| "${{ needs.check.result }}" \ | |
| "${{ needs.security.result }}" \ | |
| "${{ needs.build.result }}" \ | |
| "${{ needs.test.result }}" \ | |
| "${{ needs.coverage.result }}" \ | |
| "${{ needs.msrv.result }}" \ | |
| "${{ needs.benchmark.result }}"; do | |
| if [[ "$result" != "success" && "$result" != "skipped" ]]; then | |
| echo "One or more required jobs failed or were cancelled" | |
| exit 1 | |
| fi | |
| done | |
| echo "All required jobs passed successfully (or were skipped as docs-only)!" |