feat(cli): P2 — add tuitbot doctor command for self-diagnosis (#300) #107
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: Benchmarks | |
| # Run on every push to main only — benchmarks are expensive and comparing | |
| # against a moving baseline in PRs adds noise. Regressions surface on the | |
| # next main push and are reported as PR comments on the triggering merge. | |
| on: | |
| push: | |
| branches: [main] | |
| env: | |
| CARGO_TERM_COLOR: always | |
| TUITBOT_SKIP_DASHBOARD_BUILD: '1' | |
| # benchmark-action/github-action-benchmark still declares node20 upstream. | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────────── | |
| # Rust benchmarks — cargo criterion (scoring + content hot paths) | |
| # ───────────────────────────────────────────────────────────────────────── | |
| rust-benchmarks: | |
| name: Rust benchmarks (criterion) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # required by benchmark-action to push to gh-pages | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry + build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-bench-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-bench- | |
| - name: Run benchmarks | |
| run: | | |
| mkdir -p bench-output | |
| cargo bench -p tuitbot-core \ | |
| 2>&1 | tee bench-output/raw.txt | |
| - name: Convert criterion output to JSON | |
| # criterion 0.8 outputs: <name> time: [low median high] | |
| # Convert to customSmallerIsBetter JSON for benchmark-action. | |
| run: | | |
| grep 'time:' bench-output/raw.txt | awk '{ | |
| name = $1 | |
| # fields: $3=low $4=low_unit $5=median $6=med_unit $7=high $8=high_unit | |
| # strip trailing ] from high_unit | |
| gsub(/\]/, "", $8) | |
| med = $5; unit = $6 | |
| printf "{\"name\": \"%s\", \"unit\": \"%s\", \"value\": %s}\n", name, unit, med | |
| }' | jq -s '.' > bench-output/rust.json | |
| - name: Upload criterion HTML report (artifact) | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: criterion-report | |
| path: target/criterion/ | |
| retention-days: 30 | |
| - name: Clean working tree before gh-pages push | |
| run: git checkout -- . | |
| - name: Store benchmark results + alert on regression | |
| uses: benchmark-action/github-action-benchmark@v1 | |
| with: | |
| name: Rust Benchmarks | |
| tool: customSmallerIsBetter | |
| output-file-path: bench-output/rust.json | |
| # Alert when a benchmark regresses by more than 10% vs stored baseline. | |
| alert-threshold: '110%' | |
| comment-on-alert: true | |
| fail-on-alert: false # Warn, don't hard-fail (flaky hardware) | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| # Persist results to gh-pages branch under benchmarks/rust/ | |
| auto-push: true | |
| benchmark-data-dir-path: benchmarks/rust | |
| # ───────────────────────────────────────────────────────────────────────── | |
| # Frontend bundle size — track JS/CSS growth commit-to-commit | |
| # ───────────────────────────────────────────────────────────────────────── | |
| bundle-size: | |
| name: Frontend bundle size | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Install dependencies | |
| run: cd dashboard && npm ci | |
| - name: Build dashboard | |
| run: cd dashboard && npm run build | |
| - name: Measure bundle sizes | |
| id: sizes | |
| run: | | |
| set -euo pipefail | |
| js_bytes=$(find dashboard/build -name "*.js" -not -name "*.map" \ | |
| | xargs wc -c 2>/dev/null | awk 'END {print $1+0}') | |
| css_bytes=$(find dashboard/build -name "*.css" -not -name "*.map" \ | |
| | xargs wc -c 2>/dev/null | awk 'END {print $1+0}') | |
| total_bytes=$(du -sb dashboard/build | awk '{print $1}') | |
| echo "js_bytes=$js_bytes" | |
| echo "css_bytes=$css_bytes" | |
| echo "total_bytes=$total_bytes" | |
| # github-action-benchmark customSmallerIsBetter format | |
| cat > bundle-size.json <<EOF | |
| [ | |
| { "name": "JS (minified)", "unit": "bytes", "value": $js_bytes }, | |
| { "name": "CSS (minified)", "unit": "bytes", "value": $css_bytes }, | |
| { "name": "Total build", "unit": "bytes", "value": $total_bytes } | |
| ] | |
| EOF | |
| echo "=== Bundle sizes ===" | |
| echo " JS: $(numfmt --to=iec $js_bytes)" | |
| echo " CSS: $(numfmt --to=iec $css_bytes)" | |
| echo " Total: $(numfmt --to=iec $total_bytes)" | |
| - name: Upload bundle-size JSON (artifact) | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: bundle-size | |
| path: bundle-size.json | |
| retention-days: 90 | |
| - name: Store bundle size + alert on growth > 5% | |
| uses: benchmark-action/github-action-benchmark@v1 | |
| with: | |
| name: Frontend Bundle Size | |
| tool: customSmallerIsBetter | |
| output-file-path: bundle-size.json | |
| # Alert when any metric grows more than 5% vs stored baseline. | |
| alert-threshold: '105%' | |
| comment-on-alert: true | |
| fail-on-alert: false | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| auto-push: true | |
| benchmark-data-dir-path: benchmarks/bundle-size |