fix(deny): allow-wildcard-paths is a bool, set to true for workspace … #682
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] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Dry run' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| name: Test | |
| if: github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'chore(release):') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| components: clippy, rustfmt | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Format check | |
| run: cargo fmt --check | |
| - name: Clippy | |
| run: cargo clippy -- -D warnings | |
| env: | |
| FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }} | |
| - name: Tests | |
| run: cargo test | |
| env: | |
| FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }} | |
| build: | |
| name: Build Release Binary | |
| needs: test | |
| if: github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'chore(release):') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@nightly | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build ferrflow | |
| run: cargo build --release | |
| env: | |
| FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }} | |
| - name: Upload binary | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ferrflow-binary | |
| path: target/release/ferrflow | |
| retention-days: 1 | |
| coverage: | |
| name: Coverage | |
| if: github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'chore(release):') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@nightly | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install cargo-tarpaulin | |
| run: cargo install cargo-tarpaulin | |
| - name: Generate coverage | |
| run: cargo tarpaulin --out xml --skip-clean | |
| env: | |
| FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }} | |
| - uses: codecov/codecov-action@v6 | |
| with: | |
| files: cobertura.xml | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| fixture-generate: | |
| name: Generate Fixtures | |
| needs: test | |
| if: github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'chore(release):') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Cache generated fixtures | |
| id: fixture-cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: fixtures-generated | |
| key: fixtures-${{ hashFiles('tests/fixtures/definitions/**') }} | |
| - name: Generate fixtures | |
| if: steps.fixture-cache.outputs.cache-hit != 'true' | |
| uses: FerrLabs/Fixtures@v1 | |
| with: | |
| definitions: tests/fixtures/definitions | |
| - name: Resolve generated path | |
| id: path | |
| run: | | |
| if [ -d "fixtures-generated" ]; then | |
| echo "dir=fixtures-generated" >> "$GITHUB_OUTPUT" | |
| else | |
| for d in /tmp/tmp.*/generated; do | |
| if [ -d "$d" ]; then | |
| cp -r "$d" fixtures-generated | |
| echo "dir=fixtures-generated" >> "$GITHUB_OUTPUT" | |
| break | |
| fi | |
| done | |
| fi | |
| - name: Upload generated fixtures | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fixtures-generated | |
| path: fixtures-generated/ | |
| include-hidden-files: true | |
| retention-days: 1 | |
| fixture-monorepo: | |
| name: Fixtures — Monorepo | |
| needs: [build, fixture-generate] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: ferrflow-binary | |
| path: bin/ | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: fixtures-generated | |
| path: fixtures-generated/ | |
| - name: Run monorepo fixture tests | |
| env: | |
| FERRFLOW_BIN: ./bin/ferrflow | |
| DIFF_DIR: fixture-diffs | |
| run: | | |
| chmod +x ./bin/ferrflow | |
| mkdir -p filtered-fixtures | |
| for d in fixtures-generated/monorepo-*; do | |
| [ -d "$d" ] && cp -r "$d" filtered-fixtures/ | |
| done | |
| bash tests/fixtures/run-tests.sh filtered-fixtures | |
| - name: Upload snapshot diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fixture-diffs-monorepo | |
| path: fixture-diffs/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| fixture-single: | |
| name: Fixtures — Single Package | |
| needs: [build, fixture-generate] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: ferrflow-binary | |
| path: bin/ | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: fixtures-generated | |
| path: fixtures-generated/ | |
| - name: Run single-package fixture tests | |
| env: | |
| FERRFLOW_BIN: ./bin/ferrflow | |
| DIFF_DIR: fixture-diffs | |
| run: | | |
| chmod +x ./bin/ferrflow | |
| mkdir -p filtered-fixtures | |
| for d in fixtures-generated/single-* fixtures-generated/multiple-* fixtures-generated/no-tags-initial-* fixtures-generated/no-versioned-* fixtures-generated/version-*; do | |
| [ -d "$d" ] && cp -r "$d" filtered-fixtures/ | |
| done | |
| bash tests/fixtures/run-tests.sh filtered-fixtures | |
| - name: Upload snapshot diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fixture-diffs-single | |
| path: fixture-diffs/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| fixture-config: | |
| name: Fixtures — Config & Formats | |
| needs: [build, fixture-generate] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: ferrflow-binary | |
| path: bin/ | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: fixtures-generated | |
| path: fixtures-generated/ | |
| - name: Run config fixture tests | |
| env: | |
| FERRFLOW_BIN: ./bin/ferrflow | |
| DIFF_DIR: fixture-diffs | |
| run: | | |
| chmod +x ./bin/ferrflow | |
| mkdir -p filtered-fixtures | |
| for d in fixtures-generated/config-* fixtures-generated/format-* fixtures-generated/multi-versioned-*; do | |
| [ -d "$d" ] && cp -r "$d" filtered-fixtures/ | |
| done | |
| bash tests/fixtures/run-tests.sh filtered-fixtures | |
| - name: Upload snapshot diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fixture-diffs-config | |
| path: fixture-diffs/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| fixture-advanced: | |
| name: Fixtures — Advanced Features | |
| needs: [build, fixture-generate] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: ferrflow-binary | |
| path: bin/ | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: fixtures-generated | |
| path: fixtures-generated/ | |
| - name: Run advanced fixture tests | |
| env: | |
| FERRFLOW_BIN: ./bin/ferrflow | |
| DIFF_DIR: fixture-diffs | |
| run: | | |
| chmod +x ./bin/ferrflow | |
| mkdir -p filtered-fixtures | |
| for d in fixtures-generated/prerelease-* fixtures-generated/hooks-* fixtures-generated/floating-* \ | |
| fixtures-generated/tag-* fixtures-generated/changelog-* fixtures-generated/versioning-* \ | |
| fixtures-generated/orphaned-* fixtures-generated/recover-* fixtures-generated/release-* \ | |
| fixtures-generated/head-* fixtures-generated/merge-* fixtures-generated/commit-* \ | |
| fixtures-generated/skip-* fixtures-generated/no-tags-monorepo-*; do | |
| [ -d "$d" ] && cp -r "$d" filtered-fixtures/ | |
| done | |
| bash tests/fixtures/run-tests.sh filtered-fixtures | |
| - name: Upload snapshot diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fixture-diffs-advanced | |
| path: fixture-diffs/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # Compile the bench binary ONCE per PR and hand it to the matrix shards | |
| # below. Before this split, every shard ran `cargo bench` with its own | |
| # cold rust-cache (all 11 shards start in parallel and share a key, so | |
| # nobody hits a sibling's cache), paying the ~5–7 min build cost 11 | |
| # times. With a pre-built binary the matrix only does the actual | |
| # measurement step. See FerrLabs/FerrFlow#397. | |
| micro-bench-build: | |
| name: Build micro bench binary | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@nightly | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Compile bench binary (no run) | |
| run: cargo bench --bench ferrflow_benchmarks --no-run | |
| - name: Locate compiled binary | |
| id: locate | |
| run: | | |
| set -euo pipefail | |
| # criterion + cargo emit a hashed binary name like | |
| # ferrflow_benchmarks-1234abcd. Pick the most recent matching | |
| # executable and skip dSYMs, .d files, and pdbs. | |
| bin=$(find target/release/deps -maxdepth 1 -type f \ | |
| -name 'ferrflow_benchmarks-*' \ | |
| ! -name '*.d' ! -name '*.pdb' ! -name '*.dSYM' \ | |
| -printf '%T@ %p\n' \ | |
| | sort -nr | head -n1 | cut -d' ' -f2-) | |
| if [ -z "$bin" ] || [ ! -x "$bin" ]; then | |
| echo "::error::could not find compiled bench binary under target/release/deps" | |
| ls -la target/release/deps/ferrflow_benchmarks-* 2>/dev/null || true | |
| exit 1 | |
| fi | |
| echo "bin=$bin" >> "$GITHUB_OUTPUT" | |
| # Strip the hash so the matrix doesn't have to re-derive it. | |
| cp "$bin" target/release/deps/ferrflow_benchmarks | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: micro-bench-binary | |
| path: target/release/deps/ferrflow_benchmarks | |
| retention-days: 1 | |
| if-no-files-found: error | |
| micro-bench: | |
| name: Micro Benchmark - ${{ matrix.group }} | |
| needs: micro-bench-build | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| group: | |
| - commit_parsing | |
| - changelog | |
| - version_files | |
| - config_loading | |
| - git_commits | |
| - git_find_tag | |
| - git_collect_tags | |
| - git_changed_files | |
| - git_changed_since_tag | |
| - validate | |
| - full_check_flow | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: micro-bench-binary | |
| path: bench-bin/ | |
| - name: Run group ${{ matrix.group }} | |
| # Mirrors what FerrLabs/Benchmarks@v3 used to do for the micro | |
| # path: invoke the bench binary directly with the criterion | |
| # positional filter, capture bencher-format stdout, and keep | |
| # only the well-formed `test … bench: …` rows so the aggregate | |
| # job's benchmark-action consumer doesn't choke on noise. | |
| # The bench binary's fixtures are all in-process (tempdir + | |
| # libgit2), so no checkout is needed in this job. | |
| run: | | |
| set +e | |
| chmod +x bench-bin/ferrflow_benchmarks | |
| ./bench-bin/ferrflow_benchmarks --bench --output-format bencher \ | |
| "${{ matrix.group }}" \ | |
| 2> bench-stderr.log | tee raw-output.txt | |
| bench_exit=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$bench_exit" != "0" ]; then | |
| echo "::warning::bench binary exited ${bench_exit} — tolerating as long as rows were captured. Stderr tail:" | |
| tail -40 bench-stderr.log || true | |
| fi | |
| grep -E '^test .+ \.\.\. bench:[[:space:]]+[0-9,]+ ns/iter' raw-output.txt > output.txt || true | |
| echo "---" | |
| echo "Filtered bencher output ($(wc -l < output.txt) lines kept):" | |
| cat output.txt | |
| if [ ! -s output.txt ]; then | |
| echo "::error::no well-formed benchmark rows captured; bench exit was ${bench_exit}" | |
| exit 1 | |
| fi | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: micro-partial-${{ matrix.group }} | |
| path: output.txt | |
| retention-days: 1 | |
| micro-bench-aggregate: | |
| name: Micro Benchmark (aggregate) | |
| needs: micro-bench | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: micro-partial-* | |
| merge-multiple: true | |
| path: shards/ | |
| - name: Merge partials | |
| run: | | |
| cat shards/* > output.txt | |
| echo "Merged bench rows: $(wc -l < output.txt)" | |
| - name: Find baseline artifact from main | |
| id: find-baseline | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| RUN_ID=$(gh api "repos/${{ github.repository }}/actions/artifacts?name=criterion-baseline&per_page=1" \ | |
| --jq '.artifacts[0].workflow_run.id // empty') | |
| echo "run_id=${RUN_ID}" >> "$GITHUB_OUTPUT" | |
| continue-on-error: true | |
| - name: Download baseline | |
| if: steps.find-baseline.outputs.run_id | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: criterion-baseline | |
| path: baseline/ | |
| run-id: ${{ steps.find-baseline.outputs.run_id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| continue-on-error: true | |
| - name: Ensure baseline file | |
| run: | | |
| if [[ ! -f baseline/benchmark-data.json ]]; then | |
| mkdir -p baseline | |
| echo '[]' > baseline/benchmark-data.json | |
| fi | |
| - uses: benchmark-action/github-action-benchmark@v1 | |
| with: | |
| tool: cargo | |
| output-file-path: output.txt | |
| external-data-json-path: baseline/benchmark-data.json | |
| comment-on-alert: true | |
| alert-threshold: '120%' | |
| fail-on-alert: false | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| comment-always: true | |
| benchmark: | |
| name: Benchmark | |
| needs: test | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@nightly | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24' | |
| - uses: FerrLabs/Benchmarks@v3 | |
| with: | |
| type: full | |
| definitions: benchmarks/fixtures/definitions | |
| ferrflow-token: ${{ secrets.GITHUB_TOKEN }} | |
| skip-competitors: true | |
| env: | |
| FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }} | |
| release: | |
| name: Release | |
| needs: [test, fixture-monorepo, fixture-single, fixture-config, fixture-advanced, benchmark] | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: release-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| if: | | |
| always() && | |
| needs.test.result == 'success' && | |
| needs.fixture-monorepo.result == 'success' && | |
| needs.fixture-single.result == 'success' && | |
| needs.fixture-config.result == 'success' && | |
| needs.fixture-advanced.result == 'success' && | |
| (needs.benchmark.result == 'success' || needs.benchmark.result == 'skipped') && | |
| ( | |
| (github.event_name == 'push' && github.ref == 'refs/heads/main') || | |
| github.event_name == 'workflow_dispatch' | |
| ) | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| # Don't bake GITHUB_TOKEN into the git remote — ferrflow's OIDC | |
| # exchange yields an App installation token that authenticates as | |
| # ferrflow[bot]. Without this, every git push silently reverts | |
| # to GITHUB_TOKEN (i.e. github-actions[bot]) which isn't in the | |
| # branch-rule bypass list and can't push to main. | |
| persist-credentials: false | |
| - uses: dtolnay/rust-toolchain@nightly | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build ferrflow | |
| run: cargo build --release | |
| env: | |
| FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }} | |
| # Git identity is configured by the binary itself (see | |
| # ensure_bot_token in src/bot_token.rs) when FERRFLOW_BOT=true. | |
| - name: Run ferrflow release | |
| run: ./target/release/ferrflow ${{ inputs.dry_run == 'true' && '--dry-run' || '' }} release --draft | |
| env: | |
| FERRFLOW_BOT: "true" | |
| - name: Download benchmark summary | |
| if: needs.benchmark.result == 'success' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: benchmark-release-summary | |
| path: benchmark-summary/ | |
| continue-on-error: true | |
| - name: Append benchmark results to draft release | |
| if: needs.benchmark.result == 'success' | |
| run: | | |
| BENCH_FILE="benchmark-summary/release-summary.md" | |
| if [[ ! -f "$BENCH_FILE" ]]; then | |
| echo "No benchmark summary found, skipping" | |
| exit 0 | |
| fi | |
| TAG=$(./target/release/ferrflow tag --json 2>/dev/null | jq -r '.tag // empty') | |
| if [[ -z "$TAG" ]]; then | |
| echo "No tag found, skipping" | |
| exit 0 | |
| fi | |
| CURRENT_BODY=$(gh release view "$TAG" --json body --jq '.body' 2>/dev/null || echo "") | |
| BENCH=$(cat "$BENCH_FILE") | |
| # Remove existing Performance section to avoid duplicates | |
| CLEAN_BODY=$(echo "$CURRENT_BODY" | sed '/^## Performance$/,$d') | |
| printf -v NEW_BODY '%s\n\n%s' "$CLEAN_BODY" "$BENCH" | |
| gh release edit "$TAG" --notes "$NEW_BODY" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |