Der Umfang war schon richtig — genau eine Datei, kein Glob —, aber als bloße
Zusage zu schwach. `[x]` steht im Repo an Stellen, wo eine Beförderung falsch
bis unsinnig wäre, und der Schaden wäre still:
- Die Legende („Agenda") zeigt `[x] fertig` als ANSCHAUUNGSMATERIAL für die
Notation (index.html, chip('fertig','[x]') in app.js). Daraus würde
„[^] fertig" — Unsinn, den beim Durchsehen eines Diffs niemand bemerkt.
- Das mitgelieferte „Example"-Dokument (INITIAL in app.js) und die übrigen
docs/examples/*.werkbaum sind erfunden.
- SPEC §10 (kanonisches Beispiel, zugleich Test-Fixture) und die Checkboxen in
docs/TASKS.md.
Deshalb jetzt eine Laufzeitsicherung statt einer Absichtserklärung: Der Lauf
vergleicht `git status` vor und nach dem Schreiben und bricht ab, sobald mehr
als die Plandatei NEU geändert ist — Plandatei zurückgesetzt, nichts committet.
Der Vergleich ist gegen den Vorher-Zustand gebildet, damit anderweitig
schmutzige Dateien im Arbeitsbaum keinen Fehlalarm auslösen. Dazu ein
UMFANG-Absatz im Skriptkopf, der die drei Fallen benennt und ausdrücklich sagt:
nicht auf ein Muster erweitern.
Verifiziert im Wegwerf-Worktree: Normalfall befördert die zwei Knoten und
committet; ein absichtlich auf example-plan-0.werkbaum ausgeweiteter sed bricht
mit Exit 1 ab, listet beide betroffenen Dateien, setzt die Plandatei zurück und
committet nichts; danach läuft der Normalfall unverändert durch.
(Beim ersten Testlauf griff der Wächter scheinbar nicht — Ursache war der Test,
nicht das Skript: `git reset --hard` hatte die eingespielte Skriptfassung durch
die committete, noch ungesicherte ersetzt.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
134 lines
5.1 KiB
Bash
Executable File
134 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Werkbaum — „fertig" auf „in Produktion" befördern.
|
|
#
|
|
# Der mitgelieferte Plan docs/examples/example-werkbaum.werkbaum beschreibt
|
|
# Werkbaum selbst. SPEC §4 unterscheidet `[x]` fertig (abgeschlossen) von `[^]`
|
|
# in Produktion (deployed/live) — und die zweite Aussage kann erst der Deploy
|
|
# wahr machen. Konvention deshalb: beim Mergen `[x]`, unmittelbar VOR dem
|
|
# Deploy dieser Lauf, der daraus `[^]` macht und das als Commit festhält.
|
|
#
|
|
# Warum ein Commit und kein Rewrite beim Bauen: siehe docs/DECISIONS.md D30.
|
|
# Kurz — der Commit wird von BEIDEN Pipelines gesehen (GitHub Pages baut ihn
|
|
# beim Push, prod beim nächsten rsync), das Deployment-Artefakt bleibt
|
|
# inhaltsgleich mit dem Repo, und die „Was ist neu?"-Anzeige (D28) bleibt auf
|
|
# der Pages-Instanz erlebbar statt dort für immer stumm zu sein.
|
|
#
|
|
# Verwendung:
|
|
# scripts/promote-shipped.sh [-n] [-y]
|
|
#
|
|
# -n, --dry-run nur zeigen, was befördert würde
|
|
# -y, --yes ohne Rückfrage befördern und committen
|
|
#
|
|
# Ohne zu befördernde Knoten endet der Lauf mit 0 und ändert nichts.
|
|
# Gepusht wird NICHT — das bleibt eine bewusste Handlung.
|
|
#
|
|
# UMFANG — bewusst genau EINE Datei, niemals ein Glob:
|
|
# Nur der Werkbaum-eigene Plan sagt etwas über das Deployment aus. `[x]` steht
|
|
# im Repo an mehreren Stellen, wo eine Beförderung falsch bis unsinnig wäre:
|
|
# * die Legende („Agenda") zeigt `[x] fertig` als ANSCHAUUNGSMATERIAL für die
|
|
# Notation (frontend/index.html, `chip('fertig','[x]')` in app.js) — daraus
|
|
# würde `[^] fertig`, also Quatsch, den beim Diff niemand bemerkt;
|
|
# * das mitgelieferte „Example"-Dokument (INITIAL in app.js) und die übrigen
|
|
# docs/examples/*.werkbaum sind erfunden und beschreiben kein Deployment;
|
|
# * SPEC §10 (kanonisches Beispiel, zugleich Test-Fixture) und die Checkboxen
|
|
# in docs/TASKS.md.
|
|
# Wer diesen Lauf erweitern will, erweitert ihn deshalb NICHT auf ein Muster.
|
|
# Die Prüfung weiter unten bricht ab, sobald mehr als die Plandatei anders ist.
|
|
|
|
set -euo pipefail
|
|
|
|
DRY=0
|
|
YES=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
-n|--dry-run) DRY=1 ;;
|
|
-y|--yes) YES=1 ;;
|
|
-h|--help)
|
|
awk 'NR>2 { if ($0 ~ /^#/) { sub(/^# ?/, ""); print } else exit }' "$0"
|
|
exit 0 ;;
|
|
*) echo "Unbekannte Option: $arg" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
PLAN="docs/examples/example-werkbaum.werkbaum"
|
|
[ -f "$PLAN" ] || { echo "Plan nicht gefunden: $PLAN" >&2; exit 1; }
|
|
|
|
# Nur Statusboxen am Zeilenanfang (nach optionalem Zeichen -/+/|), damit ein
|
|
# „[x]" mitten im Label unangetastet bleibt. `x` auch als `X` (SPEC §4).
|
|
MATCH='^([[:space:]]*([-+|][[:space:]]*)?)\[[xX]\]'
|
|
|
|
mapfile -t HITS < <(grep -nE "$MATCH" "$PLAN" || true)
|
|
|
|
if [ "${#HITS[@]}" -eq 0 ]; then
|
|
echo "==> Nichts zu befördern — kein [x] im Plan."
|
|
exit 0
|
|
fi
|
|
|
|
echo "==> ${#HITS[@]} Knoten würden auf [^] befördert:"
|
|
printf ' %s\n' "${HITS[@]}"
|
|
|
|
[ "$DRY" -eq 1 ] && exit 0
|
|
|
|
# Der Commit fasst genau diese eine Datei an. Ist sie schon geändert, lässt sich
|
|
# die Beförderung nicht sauber von der Änderung trennen — dann lieber abbrechen.
|
|
if git rev-parse HEAD >/dev/null 2>&1 && [ -n "$(git status --porcelain -- "$PLAN")" ]; then
|
|
echo " ! $PLAN hat uncommittete Änderungen — erst committen, dann befördern." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$YES" -ne 1 ]; then
|
|
printf '==> Befördern und committen? [y/N] '
|
|
read -r ANS
|
|
case "$ANS" in y|Y|j|J) : ;; *) echo "Abgebrochen."; exit 1 ;; esac
|
|
fi
|
|
|
|
# Labels für den Commit-Text sammeln (ohne Zeichen, Statusbox und Größe).
|
|
mapfile -t LABELS < <(printf '%s\n' "${HITS[@]}" \
|
|
| sed -E 's/^[0-9]+:[[:space:]]*([-+|][[:space:]]*)?\[[xX]\][[:space:]]*//' \
|
|
| sed -E 's/[[:space:]]*\((XS|S|M|L|XL|XXL)\)[[:space:]]*$//')
|
|
|
|
IS_GIT=0
|
|
git rev-parse HEAD >/dev/null 2>&1 && IS_GIT=1
|
|
|
|
BEFORE="$(mktemp)"; AFTER="$(mktemp)"
|
|
trap 'rm -f "$BEFORE" "$AFTER"' EXIT
|
|
[ "$IS_GIT" -eq 1 ] && git status --porcelain > "$BEFORE"
|
|
|
|
sed -i -E "s/$MATCH/\1[^]/" "$PLAN"
|
|
|
|
if [ "$IS_GIT" -eq 0 ]; then
|
|
echo "==> Kein Git-Repo — Datei geändert, nicht committet."
|
|
exit 0
|
|
fi
|
|
|
|
# Sicherung gegen künftige Ausweitung (siehe UMFANG oben): Nach dem Schreiben
|
|
# darf GENAU eine Datei neu geändert sein — die Plandatei. Träfe es zusätzlich
|
|
# die Legende oder ein fremdes Beispiel, fiele das im Diff kaum auf; hier fällt
|
|
# es sofort auf.
|
|
git status --porcelain > "$AFTER"
|
|
NEU="$(grep -vxF -f "$BEFORE" "$AFTER" | sed -E 's/^.{3}//' || true)"
|
|
if [ "$NEU" != "$PLAN" ]; then
|
|
echo " ! Unerwarteter Umfang — geändert wurde:" >&2
|
|
printf ' %s\n' ${NEU:-"(nichts)"} >&2
|
|
echo " Erwartet war ausschließlich $PLAN. Nichts committet." >&2
|
|
git checkout -- "$PLAN"
|
|
exit 1
|
|
fi
|
|
|
|
{
|
|
echo "docs: Werkbaum-Plan — ${#HITS[@]} Knoten in Produktion"
|
|
echo
|
|
echo "Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4"
|
|
echo "unterscheidet [x] fertig von [^] in Produktion (D30):"
|
|
echo
|
|
printf -- '- %s\n' "${LABELS[@]}"
|
|
} | git commit -q -F - -- "$PLAN"
|
|
|
|
echo "==> Committet: $(git log --oneline -1)"
|
|
echo " Hinweis: nicht gepusht. Ohne Push zeigt der Footer-Versionslink des"
|
|
echo " Deploys auf einen Commit, den GitHub noch nicht kennt."
|