Template
fix(ci): let the template test itself, and cover the entrypoint ./... cannot see
Closes #12. The guard that stops this template building itself also stopped it TESTING itself: check, build and deploy were all gated on is_template, which is always true here. So the repo that mints every other repo was the one repo with no working gate — and #8 sat in it for months with CI green throughout, found eventually by a child rather than by us. check is now ungated. build and deploy stay gated, because they genuinely cannot work here: placeholder module path, no image name, no namespace. Ungating alone would have been a false fix. `go build ./...`, `go vet ./...`, `go test ./...` and `golangci-lint run ./...` cannot see cmd/__PROJECT_NAME__ at all — Go skips directories whose names begin with an underscore when expanding package patterns: $ go list ./... __MODULE_PATH__/internal/web <- no cmd package So a newly-ungated check would still have skipped the package every generated repo is built around. scripts/tests/generated-child_test.sh substitutes the tracked tree into a throwaway child and gates the result: templ generate, go list, build, vet, golangci-lint. Ported from template-go-agent, plus a templ step, which also proves the pinned generator and pinned library agree. Proven to fail when it should. With a deliberate errcheck violation added to the entrypoint: generated-child harness -> FAIL cmd/childproj/main.go:12:26 errcheck golangci-lint run ./... -> "0 issues." The existing gate calls the same broken code clean. That contrast is the reason this harness exists rather than a wider lint invocation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,8 +34,16 @@ jobs:
|
|||||||
|
|
||||||
check:
|
check:
|
||||||
name: Lint / Test / Vet
|
name: Lint / Test / Vet
|
||||||
|
# Deliberately NOT gated on the guard (#12). build and deploy are, because
|
||||||
|
# they genuinely cannot work here -- placeholder module path, no image name,
|
||||||
|
# no namespace. But linting and testing the template is meaningful, and
|
||||||
|
# gating it meant this repo had no working gate at all: #8 sat here with CI
|
||||||
|
# green throughout, and was found by a child rather than by us.
|
||||||
|
#
|
||||||
|
# `task check` includes scripts/tests/generated-child_test.sh, which covers
|
||||||
|
# the entrypoint that ./... cannot see -- ungating without that would run a
|
||||||
|
# gate that still skips the package every child is built around.
|
||||||
needs: guard
|
needs: guard
|
||||||
if: needs.guard.outputs.is_template != 'true'
|
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ tasks:
|
|||||||
# The template guard decides whether CI runs at all in every child repo,
|
# The template guard decides whether CI runs at all in every child repo,
|
||||||
# so its regression test belongs in the same gate as the Go tests.
|
# so its regression test belongs in the same gate as the Go tests.
|
||||||
- bash scripts/tests/is-template_test.sh
|
- bash scripts/tests/is-template_test.sh
|
||||||
|
# The three Go commands above cannot see cmd/__PROJECT_NAME__ -- Go's
|
||||||
|
# ./... expansion skips directories whose names start with an underscore.
|
||||||
|
# This gates the substituted result, which is what a child actually runs.
|
||||||
|
- bash scripts/tests/generated-child_test.sh
|
||||||
|
|
||||||
context:sync:
|
context:sync:
|
||||||
desc: Regenerate all harness-specific context files
|
desc: Regenerate all harness-specific context files
|
||||||
|
|||||||
Executable
+102
@@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Does a repo generated from this template pass its own gate?
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS
|
||||||
|
#
|
||||||
|
# `golangci-lint run ./...` and friends cannot see this template's entrypoint.
|
||||||
|
# Go skips directories whose names begin with "_" or "." when expanding package
|
||||||
|
# patterns, and the entrypoint lives in cmd/__PROJECT_NAME__:
|
||||||
|
#
|
||||||
|
# $ go list ./...
|
||||||
|
# __MODULE_PATH__/internal/web <- no cmd package
|
||||||
|
#
|
||||||
|
# After substitution the directory has a real name, becomes visible, and any
|
||||||
|
# defect in it appears — in the child, not here. That is how template-go-agent
|
||||||
|
# shipped a broken entrypoint for three months while its own CI stayed green
|
||||||
|
# (template-go-agent#1, #2).
|
||||||
|
#
|
||||||
|
# Linting the directory by name is not a fix: `golangci-lint run
|
||||||
|
# ./cmd/__PROJECT_NAME__` works, but reports formatter findings that do not
|
||||||
|
# exist in a generated repo, because gofumpt's import grouping depends on the
|
||||||
|
# module path and this one is a placeholder.
|
||||||
|
#
|
||||||
|
# So this substitutes the tracked tree into a throwaway child and gates the
|
||||||
|
# RESULT, which is the only artifact anyone actually runs.
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/tests/generated-child_test.sh
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
pass=0; fail=0
|
||||||
|
ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); }
|
||||||
|
bad() { printf ' FAIL %s\n %s\n' "$1" "${2:-}"; fail=$((fail+1)); }
|
||||||
|
|
||||||
|
echo "generated-child tests"
|
||||||
|
|
||||||
|
for tool in golangci-lint templ; do
|
||||||
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||||
|
echo " SKIP $tool not installed — cannot gate the generated child" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
# Tracked files from the WORKING TREE, not `git archive HEAD`: this runs inside
|
||||||
|
# `task check`, so it must gate what is about to be committed rather than the
|
||||||
|
# previous commit. `git ls-files` keeps the file set honest while taking
|
||||||
|
# current content.
|
||||||
|
( cd "$ROOT" && git ls-files -z ) | while IFS= read -r -d '' f; do
|
||||||
|
mkdir -p "$tmp/$(dirname "$f")"
|
||||||
|
cp "$ROOT/$f" "$tmp/$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -d "$tmp/cmd/__PROJECT_NAME__" ]; then
|
||||||
|
bad "the template still has a placeholder entrypoint directory" \
|
||||||
|
"cmd/__PROJECT_NAME__ not found — has the layout changed?"
|
||||||
|
echo; echo "$pass passed, $fail failed"; exit 1
|
||||||
|
fi
|
||||||
|
mv "$tmp/cmd/__PROJECT_NAME__" "$tmp/cmd/childproj"
|
||||||
|
find "$tmp" -type f \( -name '*.go' -o -name '*.templ' -o -name '*.mod' -o -name '*.yml' -o -name '*.md' \) \
|
||||||
|
-exec sed -i \
|
||||||
|
-e 's|__MODULE_PATH__|git.d-ma.be/mathias/childproj|g' \
|
||||||
|
-e 's|__PROJECT_NAME__|childproj|g' {} +
|
||||||
|
|
||||||
|
# Generated files are gitignored, so the child arrives without them — exactly as
|
||||||
|
# a freshly scaffolded repo does. Generating here also proves the pinned
|
||||||
|
# generator and the pinned library agree, which is template-go-web#8.
|
||||||
|
out="$(cd "$tmp" && templ generate 2>&1)"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
ok "templ generate succeeds in the generated child"
|
||||||
|
else
|
||||||
|
bad "templ generate succeeds in the generated child" "$(printf '%s' "$out" | tail -3)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The whole point: the entrypoint must be visible to Go tooling in the child.
|
||||||
|
# If this regresses, everything below it passes for the wrong reason.
|
||||||
|
if ( cd "$tmp" && go list ./... 2>/dev/null ) | grep -q '/cmd/childproj$'; then
|
||||||
|
ok "the generated child's entrypoint is visible to go list"
|
||||||
|
else
|
||||||
|
bad "the generated child's entrypoint is visible to go list" \
|
||||||
|
"cmd package missing from ./... — the child would be unchecked too"
|
||||||
|
fi
|
||||||
|
|
||||||
|
out="$(cd "$tmp" && go build ./... 2>&1)"
|
||||||
|
[ -z "$out" ] && ok "the generated child builds" \
|
||||||
|
|| bad "the generated child builds" "$(printf '%s' "$out" | head -3)"
|
||||||
|
|
||||||
|
out="$(cd "$tmp" && go vet ./... 2>&1)"
|
||||||
|
[ -z "$out" ] && ok "the generated child passes go vet" \
|
||||||
|
|| bad "the generated child passes go vet" "$(printf '%s' "$out" | head -3)"
|
||||||
|
|
||||||
|
out="$(cd "$tmp" && golangci-lint run ./... 2>&1)"
|
||||||
|
if printf '%s' "$out" | grep -qE '^[0-9]+ issues:|issues:$'; then
|
||||||
|
bad "the generated child passes golangci-lint" "$(printf '%s' "$out" | grep -E '\.go:[0-9]+' | head -3)"
|
||||||
|
else
|
||||||
|
ok "the generated child passes golangci-lint"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "$pass passed, $fail failed"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
Reference in New Issue
Block a user