chore(14.3-01): add the phase 14.3 generator gate
- scripts/check-phase14.3.sh --generator runs summer new next to a symlinked checkout with no git identity, checks the layout and first commit, then docker compose up, summer build, migrate and serve, and requires the starter route to answer Claude-Session: https://claude.ai/code/session_01M8Qu9LgCKYy7iYEho52tW8
This commit is contained in:
149
scripts/check-phase14.3.sh
Executable file
149
scripts/check-phase14.3.sh
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 14.3 gate (one-command app scaffolding).
|
||||
#
|
||||
# --generator proves D-12 end to end: `summer new demo` run next to a
|
||||
# symlinked framework checkout, with no --framework flag and no git
|
||||
# identity, writes an app that builds, migrates against PostgreSQL 16 from
|
||||
# its own compose.yaml and serves the starter route with no edits. It ends
|
||||
# with "Phase 14.3 generator check passed".
|
||||
#
|
||||
# Needs Docker, the postgres:16 image (pulled when missing) and free host
|
||||
# ports 54320 and 8080. Every failure exits non-zero.
|
||||
#
|
||||
# Overrides: PHASE143_ROOT (framework checkout), PHASE143_KEEP=1 (keep the
|
||||
# temporary directory), PHASE143_TIMEOUT (seconds to wait for serve, 60).
|
||||
set -euo pipefail
|
||||
|
||||
unset FORCE_COLOR
|
||||
|
||||
ROOT="${PHASE143_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||
SERVE_TIMEOUT="${PHASE143_TIMEOUT:-60}"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
port_free() {
|
||||
# bash /dev/tcp connects when something listens; free means it fails.
|
||||
if (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
stage_generator() {
|
||||
command -v docker >/dev/null || fail "docker is not installed"
|
||||
docker info >/dev/null 2>&1 || fail "the Docker daemon is not reachable (docker info failed)"
|
||||
docker compose version >/dev/null 2>&1 || fail "docker compose is not available"
|
||||
command -v git >/dev/null || fail "git is not installed"
|
||||
command -v curl >/dev/null || fail "curl is not installed"
|
||||
port_free 54320 || fail "127.0.0.1:54320 is in use; the generated compose.yaml publishes PostgreSQL there"
|
||||
port_free 8080 || fail "127.0.0.1:8080 is in use; summer serve listens there"
|
||||
|
||||
# Globals, not locals: the EXIT trap runs after this function returns.
|
||||
tmp="$(mktemp -d)"
|
||||
app="$tmp/parent/sm-demo-app"
|
||||
project="phase143-$$-$(date +%s)"
|
||||
server_pid=""
|
||||
|
||||
cleanup() {
|
||||
local rc=$?
|
||||
if [ -n "$server_pid" ]; then
|
||||
kill "$server_pid" 2>/dev/null || true
|
||||
wait "$server_pid" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$app/compose.yaml" ]; then
|
||||
(cd "$app" && COMPOSE_PROJECT_NAME="$project" docker compose down -v --remove-orphans >/dev/null 2>&1) || true
|
||||
fi
|
||||
if [ "${PHASE143_KEEP:-0}" = "1" ]; then
|
||||
echo "kept $tmp" >&2
|
||||
else
|
||||
chmod -R u+w "$tmp" 2>/dev/null || true
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
exit "$rc"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "== build the summer CLI"
|
||||
mkdir -p "$tmp/bin" "$tmp/parent" "$tmp/home"
|
||||
go -C "$ROOT" build -o "$tmp/bin/summer" ./cmd/summer
|
||||
export PATH="$tmp/bin:$PATH"
|
||||
|
||||
# Reuse the module and build caches under the empty HOME below.
|
||||
GOMODCACHE="$(go env GOMODCACHE)"
|
||||
GOCACHE="$(go env GOCACHE)"
|
||||
GOPATH="$(go env GOPATH)"
|
||||
export GOMODCACHE GOCACHE GOPATH
|
||||
|
||||
echo "== summer new demo (sibling detection, no git identity)"
|
||||
ln -s "$ROOT" "$tmp/parent/summercms"
|
||||
(
|
||||
cd "$tmp/parent"
|
||||
env -u GIT_AUTHOR_NAME -u GIT_AUTHOR_EMAIL -u GIT_COMMITTER_NAME -u GIT_COMMITTER_EMAIL \
|
||||
HOME="$tmp/home" XDG_CONFIG_HOME="$tmp/home/.config" GIT_CONFIG_NOSYSTEM=1 \
|
||||
summer new demo
|
||||
) | tee "$tmp/new.out"
|
||||
grep -q "SummerCMS <summer@localhost>" "$tmp/new.out" || fail "summer new did not announce the fallback git identity"
|
||||
grep -q "docker compose up -d --wait" "$tmp/new.out" || fail "next steps lack docker compose up -d --wait"
|
||||
for step in "summer build" "summer migrate" "summer serve"; do
|
||||
grep -q "$step" "$tmp/new.out" || fail "next steps lack $step"
|
||||
done
|
||||
|
||||
echo "== layout checks"
|
||||
[ -d "$app" ] || fail "sm-demo-app was not created"
|
||||
grep -q "replace git.golem15.com/golem15/summercms => ../summercms" "$app/go.mod" ||
|
||||
fail "go.mod lacks the relative sibling replace"
|
||||
[ "$(stat -c %a "$app/.env")" = "600" ] || fail ".env mode is $(stat -c %a "$app/.env"), want 600"
|
||||
grep -q '^SUMMER_APP__KEY=.\{44\}$' "$app/.env" || fail ".env lacks a 44-character SUMMER_APP__KEY"
|
||||
[ "$(git -C "$app" rev-list --count HEAD)" = "1" ] || fail "want exactly one commit"
|
||||
[ "$(git -C "$app" rev-parse --abbrev-ref HEAD)" = "main" ] || fail "want branch main"
|
||||
[ "$(git -C "$app" log -1 --format='%an <%ae>')" = "SummerCMS <summer@localhost>" ] ||
|
||||
fail "first commit author is not the fallback identity"
|
||||
[ -z "$(git -C "$app" ls-files .env)" ] || fail ".env is committed"
|
||||
for f in .env.example go.work main.go plugins.gen.go .gitignore compose.yaml CLAUDE.md AGENTS.md summer.yaml go.mod; do
|
||||
[ -n "$(git -C "$app" ls-files "$f")" ] || fail "$f is not committed"
|
||||
done
|
||||
[ -L "$app/AGENTS.md" ] && [ "$(readlink "$app/AGENTS.md")" = "CLAUDE.md" ] || fail "AGENTS.md is not a symlink to CLAUDE.md"
|
||||
[ ! -e "$app/go.sum" ] || fail "the app root has a go.sum"
|
||||
if grep -Eq '^[[:space:]]*/' "$app/go.work"; then
|
||||
fail "go.work has an absolute use path"
|
||||
fi
|
||||
git -C "$app" log -1 --format=%B | grep -qi "co-authored-by" && fail "first commit carries a trailer"
|
||||
|
||||
echo "== docker compose up -d --wait, summer build, summer migrate"
|
||||
cd "$app"
|
||||
export COMPOSE_PROJECT_NAME="$project"
|
||||
docker compose up -d --wait
|
||||
summer build
|
||||
summer migrate
|
||||
|
||||
echo "== summer serve"
|
||||
summer serve >"$tmp/serve.log" 2>&1 &
|
||||
server_pid=$!
|
||||
local body="" i
|
||||
for ((i = 0; i < SERVE_TIMEOUT; i++)); do
|
||||
if body="$(curl -fsS http://127.0.0.1:8080/ 2>/dev/null)"; then
|
||||
break
|
||||
fi
|
||||
kill -0 "$server_pid" 2>/dev/null || { cat "$tmp/serve.log" >&2; fail "summer serve exited"; }
|
||||
sleep 1
|
||||
done
|
||||
[ -n "$body" ] || { cat "$tmp/serve.log" >&2; fail "GET / did not answer within ${SERVE_TIMEOUT}s"; }
|
||||
echo "GET / -> $body"
|
||||
case "$body" in
|
||||
*'"plugin":"golem15.demo"'*) ;;
|
||||
*) fail "GET / body lacks \"plugin\":\"golem15.demo\"" ;;
|
||||
esac
|
||||
|
||||
echo "Phase 14.3 generator check passed"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--generator) stage_generator ;;
|
||||
*)
|
||||
echo "usage: $0 --generator" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user