Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/report/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ as SVG/HTML in an uploaded artifact.
- run: cargo test
env:
TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens
- uses: vyncint/termlens/.github/actions/report@v0.10.1
- uses: vyncint/termlens/.github/actions/report@v0.11.1
if: failure()
```

Expand Down
62 changes: 62 additions & 0 deletions .github/scripts/check-report-pin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# The `report` action is pinned by tag in three hand-maintained files, and
# the workspace version is a fourth copy of the same number. Three releases
# bumped two of the three and missed the action's own README (#386); this
# makes the pin checked rather than maintained, in the shape
# `check-candidate-statement.sh` already uses for the other promise made in
# more than one place.
#
# The three files are checked as a set rather than one by one: a listed file
# with no pin at all fails the same way a stale one does, so a copy that
# quietly disappeared is caught, and the file list is a single place to add
# the next one to.
#
# `REPORT_PIN_VERSION` overrides the workspace version, for the selftest
# only (`tools/report-pin-gate-selftest/`); CI never sets it.
#
# Usage: check-report-pin.sh [file ...]
# (default: the three files that carry the pin)
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"

version="${REPORT_PIN_VERSION:-$(sed -n 's/^version *= *"\(.*\)"$/\1/p' "$root/Cargo.toml" | head -1)}"
if [ -z "$version" ]; then
echo "::error::Cargo.toml has no workspace version"
exit 1
fi

files=("$@")
if [ "${#files[@]}" -eq 0 ]; then
files=(README.md .github/actions/report/README.md skills/termlens/SKILL.md)
fi

status=0
refs=0
for file in "${files[@]}"; do
if [ ! -f "$file" ]; then
echo "::error::\"$file\" is not a file"
status=1
continue
fi
found=0
while IFS= read -r ref; do
[ -z "$ref" ] && continue
found=$((found + 1))
refs=$((refs + 1))
if [ "$ref" != "report@v$version" ]; then
echo "::error::$file pins \`$ref\` but the workspace version is $version"
status=1
fi
done <<<"$(grep -oE 'actions/report@v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?' "$file" \
| sed 's|^actions/||' | sort -u || true)"
if [ "$found" -eq 0 ]; then
echo "::error::$file carries no \`actions/report@vX.Y.Z\` pin"
status=1
fi
done

if [ "$status" -eq 0 ]; then
echo "report action pin: $refs ref(s) across ${#files[@]} file(s), every one at report@v$version"
fi
exit "$status"
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ jobs:
# misspelled org repository and a deleted in-repo target are both
# asserted to go red, so today's green run means something.
- run: tools/link-gate-selftest/run.sh
# …and the same for the report-action pin gate (#386): a stale ref, a
# listed file that lost its ref, and one stale ref among two are all
# asserted to go red.
- run: tools/report-pin-gate-selftest/run.sh
# The emit fixture's documented steps against the ones it implements
# (#318). It lives in this job because it needs no toolchain — it is
# two greps and a `comm` — and the fixture is what almost every
Expand Down Expand Up @@ -294,6 +298,11 @@ jobs:
# header and docs/STABILITY.md, in the same words (#328, #334). Three
# copies of one promise drift; this is the check that stops it.
- run: .github/scripts/check-candidate-statement.sh
# The `report` action's tag is pinned in three hand-maintained files
# and the workspace version is a fourth copy of the same number; two
# releases bumped two of the three and missed the action's own README
# (#386). This is the check that keeps the four in step.
- run: .github/scripts/check-report-pin.sh

deny:
name: deny
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ cargo build -p termlens-cli # then the CLI's docume
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --all-features
.github/scripts/check-candidate-statement.sh # README, CHANGELOG and STABILITY state the candidate in the same words
.github/scripts/check-report-pin.sh # the three report@v refs agree with the workspace version
cargo deny check # cargo install cargo-deny
pipx run zizmor==1.29.0 --persona=pedantic .github/workflows/ # workflow audit; needs GH_TOKEN for the online checks
cargo +1.85 check --workspace --exclude ratatui-app --locked --all-targets # the MSRV: `rust-version` in Cargo.toml (the ratatui fixture needs 1.88)
Expand All @@ -61,6 +62,7 @@ cargo +1.85 check -p termlens --no-default-features --all-targets --locked
cargo clippy --workspace --all-targets --all-features --target x86_64-pc-windows-msvc -- -D warnings # the Windows build, from any host: `rustup target add x86_64-pc-windows-msvc` once
tools/semver-gate-selftest/run.sh # the semver gate can fail (cargo install cargo-semver-checks)…
tools/link-gate-selftest/run.sh # …and so can the link gate, with no extra tooling
tools/report-pin-gate-selftest/run.sh # …and the report-pin gate, likewise
.github/scripts/check-semver.sh 0.11.1 # …and the public API is compatible with the last published release
```

Expand Down
6 changes: 5 additions & 1 deletion docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ gh run watch # ubuntu, macos, windows × 5 shar

# 1. Bump the version (workspace.package.version in root Cargo.toml), and
# the same number in crates/termlens-cli/Cargo.toml's `termlens = { version
# = … }` — a path dependency publishes by its version.
# = … }` — a path dependency publishes by its version. The `report@vX.Y.Z`
# action pin lives in three hand-maintained files, and releases bumped two
# of the three twice before the gate that catches it existed (#386);
# `check-report-pin.sh` fails whenever one disagrees with this number.
$EDITOR Cargo.toml crates/termlens-cli/Cargo.toml
$EDITOR README.md .github/actions/report/README.md skills/termlens/SKILL.md
cargo check --workspace # refreshes Cargo.lock

# 2. Move the CHANGELOG section. A `- **Breaking:**` bullet moves with it,
Expand Down
6 changes: 6 additions & 0 deletions tools/report-pin-gate-selftest/absent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Fixture: a file that lost its pin

A listed file carrying no `actions/report@vX.Y.Z` ref is drift too; without
this the gate would pass a file whose pin someone deleted.

- uses: vyncint/termlens/.github/actions/report
6 changes: 6 additions & 0 deletions tools/report-pin-gate-selftest/matching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Fixture: a matching pin

The ref below must equal the workspace version — 9.9.9 as the selftest
injects it — exactly as the three real files' refs must.

- uses: vyncint/termlens/.github/actions/report@v9.9.9
54 changes: 54 additions & 0 deletions tools/report-pin-gate-selftest/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Proves the report-action pin gate can fail, before it is trusted to pass
# (#386).
#
# Four fixtures model the ways the pin drifts: a matching ref, a stale one,
# a listed file that lost its ref entirely, and a file with two refs where
# only one was bumped. They are written against an injected version so they
# do not go stale with the workspace every release; the last assertion drops
# the injection and runs the repository's own three files against the real
# workspace version, which is what a green CI run means.
#
# Usage: tools/report-pin-gate-selftest/run.sh
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")"
root="$(cd ../.. && pwd)"
gate="$root/.github/scripts/check-report-pin.sh"
status=0

# `<version|-> <expected: zero|nonzero> <label> <file ...>`
# `-` runs the gate without the injection, against the real workspace.
expect() {
version=$1
want=$2
label=$3
shift 3
got=0
if [ "$version" = "-" ]; then
"$gate" "$@" > out.log 2>&1 || got=$?
else
REPORT_PIN_VERSION="$version" "$gate" "$@" > out.log 2>&1 || got=$?
fi
case "$want:$got" in
zero:0 | nonzero:[1-9]*)
printf ' ok %-58s exit %s\n' "$label" "$got" ;;
*)
printf ' FAIL %-58s exit %s, expected %s\n' "$label" "$got" "$want" >&2
sed 's/^/ /' out.log >&2
status=1 ;;
esac
}

expect 9.9.9 zero "a matching pin passes" matching.md
expect 9.9.9 nonzero "a stale pin fails" stale.md
expect 9.9.9 nonzero "a listed file with no pin fails" absent.md
expect 9.9.9 nonzero "one stale ref among two fails" twice.md
expect - zero "this repository's own three files pass" "$root/README.md" \
"$root/.github/actions/report/README.md" "$root/skills/termlens/SKILL.md"

rm -f out.log
if [ "$status" -eq 0 ]; then
echo "report-pin gate selftest: 5 expectation(s), the gate fails when it should"
fi
exit "$status"
6 changes: 6 additions & 0 deletions tools/report-pin-gate-selftest/stale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Fixture: a stale pin

One release behind the workspace version, the shape 0.11.0 and 0.11.1 both
shipped in the action's own README (#386).

- uses: vyncint/termlens/.github/actions/report@v9.9.8
7 changes: 7 additions & 0 deletions tools/report-pin-gate-selftest/twice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Fixture: two refs, one bumped

The bumped half matches and the other does not; the gate must fail on the
stale one rather than the file passing because a ref was found at all.

- uses: vyncint/termlens/.github/actions/report@v9.9.9
- uses: vyncint/termlens/.github/actions/report@v9.9.8