4d - every gate leg names itself, its image and its seconds #106

Merged
lilleman merged 4 commits from gate-legs into main 2026-09-20 00:12:56 +02:00
4 changed files with 61 additions and 27 deletions
Showing only changes of commit 6ffafe7536 - Show all commits
+7
View File
@@ -238,6 +238,13 @@ The leg re-checks the conversions and nothing else — each fixture's emitted ma
document, its error code — leaving the corpus's pairing, uniqueness, source positions and document, its error code — leaving the corpus's pairing, uniqueness, source positions and
byte-level equality to the Node suite that owns them. byte-level equality to the Node suite that owns them.
Every leg announces its name and its image before it runs and its elapsed time after, `publish.sh`
alongside `ci.sh`, so a long run reads as progress rather than as a hang — which is what a silent
one cost the `0.1.0` release. A leg added later owes the same marker, and a leg that buffers its
output to read something out of it streams and keeps the copy in a file: `tee /dev/stderr` reopens
the stream, so under the `> log 2>&1` a reader runs locally the two offsets advance independently
and punch NUL holes through each other's lines (4d).
The floors live in the `test` script, so `npm test` and the gate are one path: 100% of lines and The floors live in the `test` script, so `npm test` and the gate are one path: 100% of lines and
functions, and a branch floor that only ever moves upward. It sits below 100 because the guards functions, and a branch floor that only ever moves upward. It sits below 100 because the guards
`noUncheckedIndexedAccess` and ADF's optional keys force — `?? []`, `?? {}`, `?.`, an index `noUncheckedIndexedAccess` and ADF's optional keys force — `?? []`, `?? {}`, `?.`, an index
+21 -17
View File
@@ -3,28 +3,32 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
source ./docker-runner.sh source ./docker-runner.sh
in_image "$node_image" npm ci test_log=$(mktemp)
in_image "$node_image" npm run typecheck
if ! test_output=$(in_image "$node_image" npm test 2>&1); then # Streamed, and copied so the zero-test guard reads the count without trading the output for it.
printf '%s\n' "$test_output" node_tests() {
exit 1 in_image "$node_image" npm test 2>&1 | tee "$test_log"
fi }
printf '%s\n' "$test_output"
if printf '%s' "$test_output" | grep -q 'ℹ tests 0'; then leg "install ($node_image)" in_image "$node_image" npm ci
leg "typecheck ($node_image)" in_image "$node_image" npm run typecheck
leg "tests ($node_image)" node_tests
if grep -q 'ℹ tests 0' "$test_log"; then
echo 'the gate ran zero tests — failing instead of a vacuous green' echo 'the gate ran zero tests — failing instead of a vacuous green'
exit 1 exit 1
fi fi
rm -f "$test_log"
in_image "$deno_image" deno test --allow-env=PROPERTY_RUNS --allow-read --no-check src/ leg "tests ($deno_image)" in_image "$deno_image" deno test --allow-env=PROPERTY_RUNS --allow-read --no-check src/
in_image "$bun_image" bun test src/ leg "tests ($bun_image)" in_image "$bun_image" bun test src/
in_image "$node_image" npm run build leg "build ($node_image)" in_image "$node_image" npm run build
in_image "$node_image" sh -c 'set -e leg "pack and install the tarball ($node_image)" in_image "$node_image" sh -c 'set -e
rm -rf package-tests/node_modules rm -rf package-tests/node_modules
npm pack --pack-destination /tmp >/dev/null npm pack --pack-destination /tmp
npm install --no-audit --no-fund --no-package-lock --no-save --offline --prefix package-tests /tmp/*.tgz >/dev/null' npm install --no-audit --no-fund --no-package-lock --no-save --offline --prefix package-tests /tmp/*.tgz'
in_image "$node_image" npx tsc -p package-tests leg "typecheck the consumer ($node_image)" in_image "$node_image" npx tsc -p package-tests
in_image "$floor_image" node package-tests/node-floor.js leg "round-trip on the engines floor ($floor_image)" in_image "$floor_image" node package-tests/node-floor.js
with_firefox in_image "$node_image" node browser-tests/run.js leg "browser ($firefox_image)" with_firefox in_image "$node_image" node browser-tests/run.js
+14
View File
@@ -10,6 +10,20 @@ in_image() {
docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp ${PROPERTY_RUNS+-e PROPERTY_RUNS} ${in_image_network:+--network "$in_image_network"} -v "$PWD:/app" -w /app --entrypoint "$entrypoint" "$image" "$@" docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp ${PROPERTY_RUNS+-e PROPERTY_RUNS} ${in_image_network:+--network "$in_image_network"} -v "$PWD:/app" -w /app --entrypoint "$entrypoint" "$image" "$@"
} }
# Markers go to stderr so a leg reporting a value stays capturable, and the locals carry the
# function's own name because bash scopes them dynamically into whatever the leg runs.
leg() {
local leg_name=$1 leg_elapsed leg_started leg_status=0
shift
printf '\n\033[1;34m==> %s\033[0m\n' "$leg_name" >&2
# EPOCHREALTIME carries the locale's radix character, so keep the digits and read microseconds.
leg_started=${EPOCHREALTIME//[^0-9]/}
"$@" || leg_status=$?
leg_elapsed=$((${EPOCHREALTIME//[^0-9]/} - leg_started))
printf '\033[1;34m<== %s: %d.%ds\033[0m\n' "$leg_name" "$((leg_elapsed / 1000000))" "$((leg_elapsed % 1000000 / 100000))" >&2
return $leg_status
}
with_firefox() { with_firefox() {
local container in_image_network status=0 local container in_image_network status=0
container=$(docker run -d --rm "$firefox_image") container=$(docker run -d --rm "$firefox_image")
+18 -9
View File
@@ -7,31 +7,40 @@ read_field() {
in_image "$node_image" npm pkg get "$1" | tr -d '"\r' in_image "$node_image" npm pkg get "$1" | tr -d '"\r'
} }
read_package_fields() {
private=$(read_field private)
name=$(read_field name)
version=$(read_field version)
}
published_version() { published_version() {
in_image "$node_image" npm view "$1@$2" version 2>/dev/null || true in_image "$node_image" npm view "$1@$2" version 2>/dev/null || true
} }
private=$(read_field private) push_tag() {
git tag "v$version"
git push origin "v$version"
}
leg "read package.json ($node_image)" read_package_fields
if [ "$private" = 'true' ]; then if [ "$private" = 'true' ]; then
echo 'package.json is private — the maintainer removes that in the bump that first publishes' echo 'package.json is private — the maintainer removes that in the bump that first publishes'
exit 0 exit 0
fi fi
name=$(read_field name) published=$(leg "ask npmjs for $name@$version" published_version "$name" "$version")
version=$(read_field version) tagged=$(leg "ask origin for v$version" git ls-remote --tags origin "v$version")
published=$(published_version "$name" "$version")
tagged=$(git ls-remote --tags origin "v$version")
# Both steps observe their own end state, so a partial run converges on the next push to main. # Both steps observe their own end state, so a partial run converges on the next push to main.
if [ -z "$published" ]; then if [ -z "$published" ]; then
: "${NPM_TOKEN:?the publish needs NPM_TOKEN}" : "${NPM_TOKEN:?the publish needs NPM_TOKEN}"
in_image "$node_image" npm ci leg "install ($node_image)" in_image "$node_image" npm ci
in_image "$node_image" npm run build leg "build ($node_image)" in_image "$node_image" npm run build
leg "publish $name@$version ($node_image)" \
docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -e NPM_TOKEN -v "$PWD:/app" -w /app --entrypoint sh "$node_image" -c \ docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -e NPM_TOKEN -v "$PWD:/app" -w /app --entrypoint sh "$node_image" -c \
'printf "//registry.npmjs.org/:_authToken=%s\n" "$NPM_TOKEN" > "$HOME/.npmrc" && npm publish --access public' 'printf "//registry.npmjs.org/:_authToken=%s\n" "$NPM_TOKEN" > "$HOME/.npmrc" && npm publish --access public'
fi fi
if [ -z "$tagged" ]; then if [ -z "$tagged" ]; then
git tag "v$version" leg "tag v$version" push_tag
git push origin "v$version"
fi fi