From c7e8b9b0112e9066780bae5d410761fe6e15fb43 Mon Sep 17 00:00:00 2001 From: Lucio Date: Sat, 25 Jul 2026 12:59:31 +0200 Subject: [PATCH 1/2] ci: run the shared gate on every PR via a lib_bespok3d submodule Mount lib_bespok3d as a submodule so scripts/check.sh finds the shared em-dash and workflow-pinning detectors on a CI runner with no sibling repos checked out, and add a pr-gate workflow that fetches the submodule with a read token and runs the check suite on each pull request. --- .github/workflows/pr-gate.yml | 41 +++++++++++++++++++++++++++++++++++ .gitmodules | 3 +++ lib_bespok3d | 1 + scripts/check.sh | 34 +++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 .github/workflows/pr-gate.yml create mode 100644 .gitmodules create mode 160000 lib_bespok3d create mode 100755 scripts/check.sh diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml new file mode 100644 index 0000000..221a20c --- /dev/null +++ b/.github/workflows/pr-gate.yml @@ -0,0 +1,41 @@ +name: pr-gate + +# Every pull request runs this repo's own check suite (scripts/check.sh) plus the shared em-dash and +# workflow-pinning guards. Those guards live in lib_bespok3d, mounted here as a git submodule, so the +# checkout must fetch it. +# +# The repo's own checkout uses the auto GITHUB_TOKEN, which is always valid for this repo. The submodule +# lives in a SEPARATE private repo that GITHUB_TOKEN cannot read, so it is fetched in its own step with a +# read token (MAIN_INDEX_TOKEN). The main checkout runs with persist-credentials off so it does not leave +# GITHUB_TOKEN in git config, where it would otherwise be reused for the submodule fetch and 403 on the +# private repo. The submodule URL in .gitmodules is HTTPS so the token applies. +# +# This is a check-only run: it never builds, packs, signs or publishes, so no signing key is in scope. +# The single secret it holds is the submodule read token. It is deliberately NOT named pr-build.yml: that +# name is reserved for a b3-builder preview build, which the pinning guard forbids from holding any secret +# at all. See the go-public finding: swap this token for a read-only one, or make lib_bespok3d public, +# before external forks open PRs (a fork PR receives no secret and its checkout of the private submodule +# would fail). +on: + pull_request: + +jobs: + gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: false + persist-credentials: false + + - name: Fetch the shared-tooling submodule + run: | + git config --global url."https://x-access-token:${{ secrets.MAIN_INDEX_TOKEN }}@github.com/".insteadOf "https://github.com/" + git submodule update --init --recursive + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Repo gate (shared detectors + check suite) + run: bash scripts/check.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dd7e77d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib_bespok3d"] + path = lib_bespok3d + url = https://github.com/Bespok3d/lib_bespok3d.git diff --git a/lib_bespok3d b/lib_bespok3d new file mode 160000 index 0000000..8ce6759 --- /dev/null +++ b/lib_bespok3d @@ -0,0 +1 @@ +Subproject commit 8ce67593b469619306db0c9f389ac21cedaf1a31 diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..7b72475 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# This repo's own gate: it must pass from this repo's root, with no sibling repo cloned except +# lib_bespok3d. Exits non-zero on any failure. +set -uo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# The shared gate helpers and the detectors that enforce a workspace-wide rule live in one place. +# See lib_bespok3d/tooling/README.md. This is the only line that knows where they are. +B3D_TOOLING="${B3D_TOOLING:-$REPO_ROOT/lib_bespok3d/tooling}" +# shellcheck source=/dev/null +. "$B3D_TOOLING/gate-lib.sh" + +cd "$REPO_ROOT" || exit 1 + +echo "" +echo "reference-python-plugins gate" + +b3d_python_tools + +# print-time-human vendors humanize under files/site-packages, so its own tree is the import path +# its tests and its type checks resolve against. +export PYTHONPATH="$REPO_ROOT/print-time-human/files/site-packages" + +run_check "pytest (status-feed)" pytest_in_dir "$REPO_ROOT/status-feed" tests +run_check "ruff (status-feed)" ruff_in_dir "$REPO_ROOT/status-feed" files tests +run_check "ruff (print-time-human)" ruff_in_dir "$REPO_ROOT/print-time-human" files/klipper + +unset PYTHONPATH + +workflow_pinning_check "$REPO_ROOT" +em_dash_check "$REPO_ROOT" +shellcheck_repo "$REPO_ROOT" + +gate_summary || exit 1 From c1d7f1aa8546fb59c59a70c4e5771385e49dbc10 Mon Sep 17 00:00:00 2001 From: Lucio Date: Sat, 25 Jul 2026 13:18:48 +0200 Subject: [PATCH 2/2] fix: provision the status-feed test dependency so its test runs in CI The vendored deps under files/site-packages are gitignored, so a clean CI checkout had no humanize and the status-feed test skipped, leaving the gate green on an untested plugin. Install the plugin's declared dependency into that tree before the test so it actually exercises the code. --- scripts/check.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/check.sh b/scripts/check.sh index 7b72475..564f8a9 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -21,6 +21,13 @@ b3d_python_tools # its tests and its type checks resolve against. export PYTHONPATH="$REPO_ROOT/print-time-human/files/site-packages" +# That vendored tree is gitignored (baked into the package at publish), so a clean checkout, which +# is what CI gets, has no humanize: the status-feed test would importorskip it and never run, +# leaving the gate green on an untested plugin. Provision the plugin's own declared dependency into +# the tree PYTHONPATH resolves against so the test actually exercises the code. B3D_PY is the gate's +# tool interpreter, provisioned by b3d_python_tools above. +"$B3D_PY" -m pip install --quiet --target "$REPO_ROOT/print-time-human/files/site-packages" -r "$REPO_ROOT/status-feed/requirements.txt" + run_check "pytest (status-feed)" pytest_in_dir "$REPO_ROOT/status-feed" tests run_check "ruff (status-feed)" ruff_in_dir "$REPO_ROOT/status-feed" files tests run_check "ruff (print-time-human)" ruff_in_dir "$REPO_ROOT/print-time-human" files/klipper