fix: lint the release path — globstar does not descend into dot-directories #118
3 changed files with 56 additions and 2 deletions
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
|
|
@ -14,10 +14,35 @@ jobs:
|
||||||
# globstar so a script in a new subdirectory is linted without anyone
|
# globstar so a script in a new subdirectory is linted without anyone
|
||||||
# remembering to edit this list; bin/* covers the extensionless entrypoint
|
# remembering to edit this list; bin/* covers the extensionless entrypoint
|
||||||
# (bin/box). The file list is printed so under-coverage shows up in the log.
|
# (bin/box). The file list is printed so under-coverage shows up in the log.
|
||||||
|
#
|
||||||
|
# dotglob is not decoration (#116): globstar makes `**` descend, but a
|
||||||
|
# glob still does not MATCH a dot-prefixed name, so `**/` never entered
|
||||||
|
# `.github/` — and the whole release path (changelog-armed.sh, which
|
||||||
|
# gates every PR, release-notes.sh, labels-reconcile.sh) went unlinted
|
||||||
|
# while the comment above told the next author it was covered.
|
||||||
|
# Measured on this tree: dotglob adds exactly those three and nothing
|
||||||
|
# else — a checkout's `.git` carries no `*.sh` (its hooks ship as
|
||||||
|
# `*.sample`), so `**/*.sh` does not wander into it.
|
||||||
|
#
|
||||||
|
# The sweep below is the CLASS check, same shape as the eof_guard_sweep
|
||||||
|
# in test/cli.sh (#112): the one-time fix is `dotglob`, but what keeps
|
||||||
|
# the gap from reopening is asserting that every TRACKED script is in
|
||||||
|
# the set actually handed to shellcheck. `git ls-files` is the authority
|
||||||
|
# on what the repo contains; if the glob ever drifts from it again —
|
||||||
|
# another dot-directory, another shopt subtlety — CI says which files
|
||||||
|
# escaped instead of quietly linting a subset and passing.
|
||||||
run: |
|
run: |
|
||||||
shopt -s globstar
|
shopt -s globstar dotglob
|
||||||
files=(bin/* **/*.sh)
|
files=(bin/* **/*.sh)
|
||||||
printf 'shellcheck: %s\n' "${files[@]}"
|
printf 'shellcheck: %s\n' "${files[@]}"
|
||||||
|
missing="$(comm -13 \
|
||||||
|
<(printf '%s\n' "${files[@]}" | sort -u) \
|
||||||
|
<(git ls-files '*.sh' | sort -u))"
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo "tracked scripts the shellcheck sweep does not cover (#116):"
|
||||||
|
printf '%s\n' "$missing" | sed 's/^/ /'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
shellcheck -x "${files[@]}"
|
shellcheck -x "${files[@]}"
|
||||||
- name: cli tests
|
- name: cli tests
|
||||||
run: bash test/cli.sh
|
run: bash test/cli.sh
|
||||||
|
|
|
||||||
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -59,6 +59,29 @@ which records not just what changed but what each drill run proved.
|
||||||
operator copy-pastes is as wrong as a role box executes, and it fails
|
operator copy-pastes is as wrong as a role box executes, and it fails
|
||||||
later and further from the cause.
|
later and further from the cause.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CI's shellcheck sweep never lints `.github/scripts/*.sh`** (#116) —
|
||||||
|
`globstar` makes `**` descend into subdirectories, but a glob still does
|
||||||
|
not *match* a dot-prefixed name, so `**/` never entered `.github/`. The
|
||||||
|
three scripts that escaped are the release path: `changelog-armed.sh` (the
|
||||||
|
#108/#110 guard that gates every PR, and had never been linted),
|
||||||
|
`release-notes.sh` (which produces the published release body), and
|
||||||
|
`labels-reconcile.sh` (the label state machine) — while the step's own
|
||||||
|
comment promised that "a script in a new subdirectory is linted without
|
||||||
|
anyone remembering to edit this list". Latent, not broken: all three pass
|
||||||
|
shellcheck as-is, so this lands as a no-op on current code and the fix is
|
||||||
|
that a regression in them would now be caught. `dotglob` alongside
|
||||||
|
`globstar` closes it, measured rather than assumed — it adds exactly those
|
||||||
|
three and nothing else, a checkout's `.git` carrying no `*.sh` (its hooks
|
||||||
|
ship as `*.sample`). Paired with a CLASS check in the same shape as the
|
||||||
|
`eof_guard_sweep` of #112: the sweep now compares the globbed set against
|
||||||
|
`git ls-files '*.sh'` and fails naming any tracked script it does not
|
||||||
|
cover, so the gap cannot reopen silently the next time a dot-directory or
|
||||||
|
a shopt subtlety hides one. `eof_guard_sweep` itself carried the identical
|
||||||
|
blind spot — it rebuilds the same glob — and is widened the same way.
|
||||||
|
|
||||||
|
|
||||||
## 0.8.0 — 2026-07-19
|
## 0.8.0 — 2026-07-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -967,7 +967,13 @@ fi
|
||||||
# guard is not.
|
# guard is not.
|
||||||
eof_guard_sweep() {
|
eof_guard_sweep() {
|
||||||
local f n line bad=0 files
|
local f n line bad=0 files
|
||||||
files="$(cd "$ROOT" && shopt -s globstar && printf '%s\n' bin/* ./**/*.sh | sed 's|^\./||' | sort -u)"
|
# dotglob alongside globstar for the same reason CI's shellcheck step carries
|
||||||
|
# it (#116): globstar descends, but a glob does not MATCH a dot-prefixed name,
|
||||||
|
# so this sweep skipped '.github/scripts/*.sh' — the release path — exactly as
|
||||||
|
# the linter did. Those three set errexit, so they are in scope for this class
|
||||||
|
# by construction; today none of them reads at all, which is why widening the
|
||||||
|
# set is a no-op on current code rather than a bug fix.
|
||||||
|
files="$(cd "$ROOT" && shopt -s globstar dotglob && printf '%s\n' bin/* ./**/*.sh | sed 's|^\./||' | sort -u)"
|
||||||
while IFS= read -r f; do
|
while IFS= read -r f; do
|
||||||
[ -f "$ROOT/$f" ] || continue
|
[ -f "$ROOT/$f" ] || continue
|
||||||
grep -qE '^[[:space:]]*set[[:space:]]+-[a-zA-Z]*e' "$ROOT/$f" || continue
|
grep -qE '^[[:space:]]*set[[:space:]]+-[a-zA-Z]*e' "$ROOT/$f" || continue
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue