F1 scoreboard: fuller driver names + fix calendar type/date overlap #238
Workflow file for this run
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: Plugin Safety | |
| # Renders every changed plugin across a representative spread of matrix sizes and | |
| # screens using the LEDMatrix core harness, and validates manifests. Gates PRs | |
| # that touch plugin code so a change can't silently break a size or screen. | |
| on: | |
| pull_request: | |
| paths: | |
| - 'plugins/**' | |
| workflow_dispatch: | |
| inputs: | |
| all: | |
| description: 'Check every plugin (not just changed ones)' | |
| type: boolean | |
| default: false | |
| env: | |
| # The core repo provides the harness (scripts/check_plugin.py). Point these at | |
| # the fork/branch where the harness lives until it is merged upstream. | |
| CORE_REPO: ChuckBuilds/LEDMatrix | |
| CORE_REF: main | |
| jobs: | |
| safety: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout plugins | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| path: plugins-repo | |
| - name: Checkout core (harness) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.CORE_REPO }} | |
| ref: ${{ env.CORE_REF }} | |
| path: core | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| cache: pip | |
| - name: Install core + harness deps | |
| working-directory: core | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt -r requirements-test.txt | |
| pip install RGBMatrixEmulator | |
| - name: Determine changed plugins (non-test code only) | |
| id: changed | |
| working-directory: plugins-repo | |
| env: | |
| ALL_INPUT: ${{ github.event.inputs.all }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| if [ "$ALL_INPUT" = "true" ]; then | |
| ids=$(ls plugins) | |
| else | |
| # Plugins with a changed file outside their test/ dir. | |
| ids=$(git diff --name-only "$BASE_SHA"...HEAD -- 'plugins/*' \ | |
| | grep -vE '^plugins/[^/]+/test/' \ | |
| | sed -E 's#^plugins/([^/]+)/.*#\1#' | sort -u) | |
| fi | |
| echo "ids<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$ids" >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| echo "Changed plugins:"; echo "$ids" | |
| - name: Enforce version bump on changed plugins | |
| if: steps.changed.outputs.ids != '' && github.event.inputs.all != 'true' | |
| working-directory: plugins-repo | |
| env: | |
| # Read IDs from env (never interpolate ${{ }} into the script body) and | |
| # validate each one — plugin ids flow into python -c strings below, so a | |
| # crafted directory name on a PR branch could otherwise inject code. | |
| IDS: ${{ steps.changed.outputs.ids }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| fail=0 | |
| for pid in $IDS; do | |
| case "$pid" in | |
| '' | *[!a-z0-9._-]*) echo "::error::invalid plugin id '$pid'"; fail=1; continue ;; | |
| esac | |
| cur=$(python -c "import json;print(json.load(open('plugins/$pid/manifest.json'))['version'])") | |
| old=$(git show "$BASE_SHA:plugins/$pid/manifest.json" 2>/dev/null \ | |
| | python -c "import json,sys;print(json.load(sys.stdin)['version'])" 2>/dev/null || echo "") | |
| if [ -n "$old" ] && [ "$cur" = "$old" ]; then | |
| echo "::error::$pid code changed but version not bumped (still $cur). Users won't get the update." | |
| fail=1 | |
| else | |
| echo "[ok] $pid version $old -> $cur" | |
| fi | |
| done | |
| exit $fail | |
| - name: Validate manifests against schema | |
| if: steps.changed.outputs.ids != '' | |
| env: | |
| IDS: ${{ steps.changed.outputs.ids }} | |
| run: | | |
| python - <<'PY' | |
| import json, os, re, sys | |
| from pathlib import Path | |
| import jsonschema | |
| schema = json.load(open("core/schema/manifest_schema.json")) | |
| valid = re.compile(r"^[a-z0-9][a-z0-9._-]*$") | |
| ids = os.environ.get("IDS", "").split() | |
| failed = False | |
| for pid in ids: | |
| if not valid.match(pid): | |
| print(f"[FAIL] invalid plugin id: {pid!r}"); failed = True; continue | |
| mpath = Path("plugins-repo/plugins") / pid / "manifest.json" | |
| if not mpath.exists(): | |
| print(f"[skip] {pid}: no manifest (deleted?)"); continue | |
| try: | |
| jsonschema.validate(json.load(open(mpath)), schema) | |
| print(f"[ok] {pid}: manifest valid") | |
| except jsonschema.ValidationError as e: | |
| print(f"[FAIL] {pid}: {e.message}"); failed = True | |
| sys.exit(1 if failed else 0) | |
| PY | |
| - name: Run safety harness on changed plugins | |
| if: steps.changed.outputs.ids != '' | |
| env: | |
| IDS: ${{ steps.changed.outputs.ids }} | |
| run: | | |
| set -e | |
| fail=0 | |
| for pid in $IDS; do | |
| case "$pid" in | |
| '' | *[!a-z0-9._-]*) echo "::error::invalid plugin id '$pid'"; fail=1; continue ;; | |
| esac | |
| pdir="plugins-repo/plugins/$pid" | |
| [ -d "$pdir" ] || { echo "::notice::$pid removed, skipping"; continue; } | |
| echo "::group::$pid" | |
| # Install the plugin's own runtime deps so it loads like a real install. | |
| # A failure here is a real problem (the plugin can't load), so let it fail. | |
| if [ -f "$pdir/requirements.txt" ]; then pip install -r "$pdir/requirements.txt"; fi | |
| python core/scripts/check_plugin.py --plugin "$pid" \ | |
| --plugin-dir "$PWD/plugins-repo/plugins" || fail=1 | |
| echo "::endgroup::" | |
| done | |
| exit $fail | |
| - name: Nothing to check | |
| if: steps.changed.outputs.ids == '' | |
| run: echo "No plugin code changed (test-only or docs change) — safety harness skipped." |