#!/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/spatial-agent-app: # # $ go list ./... # git.d-ma.be/mathias/spatial-agent-app/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/spatial-agent-app` 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" # A missing tool is a FAILURE, not a skip. An `exit 0` with zero assertions is # indistinguishable from a real pass to anything reading the exit code -- which # is the whole failure mode this harness exists to close. for tool in golangci-lint templ; do if ! command -v "$tool" >/dev/null 2>&1; then bad "$tool is available" "not on PATH — the generated child cannot be gated" echo; echo "$pass passed, $fail failed"; exit 1 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/spatial-agent-app" ]; then bad "the template still has a placeholder entrypoint directory" \ "cmd/spatial-agent-app not found — has the layout changed?" echo; echo "$pass passed, $fail failed"; exit 1 fi mv "$tmp/cmd/spatial-agent-app" "$tmp/cmd/childproj" find "$tmp" -type f \( -name '*.go' -o -name '*.templ' -o -name '*.mod' -o -name '*.yml' -o -name '*.md' -o -name 'Dockerfile' \) \ -exec sed -i \ -e 's|git.d-ma.be/mathias/spatial-agent-app|git.d-ma.be/mathias/childproj|g' \ -e 's|spatial-agent-app|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)"; rc=$? if [ "$rc" -eq 0 ]; then ok "templ generate succeeds in the generated child" else bad "templ generate succeeds in the generated child" "exit $rc: $(printf '%s' "$out" | tail -2)" 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)" # Graded on the EXIT CODE, never on grepping the output. golangci-lint exits # 0 clean / 1 issues found / >1 the linter itself failed. Matching its # human-readable summary instead scores a CRASHED linter as a pass -- bad # config, unwritable TMPDIR, OOM, or simply a future change to its output # format all produce non-matching text. Since this harness is the only gate # covering the entrypoint, that would leave cmd/spatial-agent-app unchecked with # CI green, which is precisely the defect class this file exists to end. out="$(cd "$tmp" && golangci-lint run ./... 2>&1)"; rc=$? case "$rc" in 0) ok "the generated child passes golangci-lint" ;; 1) bad "the generated child passes golangci-lint" \ "$(printf '%s' "$out" | grep -E '\.go:[0-9]+' | head -3)" ;; *) bad "golangci-lint ran successfully" \ "linter exited $rc — the child is UNCHECKED, not clean: $(printf '%s' "$out" | tail -2)" ;; esac echo echo "$pass passed, $fail failed" [ "$fail" -eq 0 ]