#!/usr/bin/env bash # Builds and signs a WP-LIT-MCP release from the committed state; with --publish it also tags and publishes it. # # bin/release.sh dry run: checks, tests, build, signature, ZIP checks – publishes nothing # bin/release.sh --publish additionally: git tag + push, Gitea release with ZIP and .sig, check of the result # # Needs: bsdtar, composer, curl, jq, php (sodium). For --publish a Gitea token with write access to the repository in # $GITEA_TOKEN. The signing key (php bin/release-sign.php keygen) lives outside the repository. # # Environment: LIT_MCP_RELEASE_KEY (default ~/.config/wp-lit-mcp/release.key), LIT_MCP_DIVI_CHECK (command that must # succeed before a release, default: ddev wp --user=janniclas lit-mcp divi-check). set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$root" repo_url="https://git.loosen-it.de/janniclas.loosen/WP-LIT-MCP" api="https://git.loosen-it.de/api/v1/repos/janniclas.loosen/WP-LIT-MCP" key="${LIT_MCP_RELEASE_KEY:-$HOME/.config/wp-lit-mcp/release.key}" divi_check="${LIT_MCP_DIVI_CHECK:-ddev wp --user=janniclas lit-mcp divi-check}" publish=0 [[ "${1:-}" == "--publish" ]] && publish=1 fail() { echo "ABBRUCH: $*" >&2; exit 1; } step() { echo "==> $*"; } # --- 1. The three version places agree, the tree is committed, nothing is released twice ---------------------------- step "Version und Zustand prüfen" version="$(sed -n "s/.*const VERSION = '\(.*\)';/\1/p" src/Plugin.php)" [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "Plugin::VERSION ist keine Version x.y.z: '$version'" [[ "$(sed -n 's/^ \* Version: //p' wp-lit-mcp.php)" == "$version" ]] || fail "Header-Version in wp-lit-mcp.php weicht ab" [[ "$(sed -n 's/^Stable tag: //p' readme.txt)" == "$version" ]] || fail "Stable tag in readme.txt weicht ab" grep -q "^= $version =\$" readme.txt || fail "readme.txt hat keinen Changelog-Eintrag für $version" [[ -z "$(git status --porcelain)" ]] || fail "Arbeitsbaum nicht sauber: erst committen, das ZIP entsteht aus HEAD" ! git rev-parse -q --verify "refs/tags/v$version" >/dev/null || fail "Tag v$version existiert schon" [[ -r "$key" ]] || fail "Signierschlüssel fehlt: $key (php bin/release-sign.php keygen )" if [[ $publish -eq 1 ]]; then [[ -n "${GITEA_TOKEN:-}" ]] || fail "GITEA_TOKEN ist nicht gesetzt" fi # --- 2. Tests and the Divi check must be green ------------------------------------------------------------------------ step "Tests" composer test step "Divi-Prüfung ($divi_check)" eval "$divi_check" || fail "Divi-Prüfung nicht grün: kein Release" # --- 3. Build from the commit: git archive honours export-ignore, then production dependencies ---------------------- step "ZIP bauen" work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT mkdir "$work/wp-lit-mcp" git archive HEAD | tar -x -C "$work/wp-lit-mcp" (cd "$work/wp-lit-mcp" && composer install --no-dev --optimize-autoloader --no-interaction --quiet) name="wp-lit-mcp-$version.zip" mkdir -p dist zip="$root/dist/$name" rm -f "$zip" "$zip.sig" (cd "$work" && bsdtar --format zip -cf "$zip" wp-lit-mcp) # --- 4. Look into the ZIP like WordPress will: one folder, the plugin, its autoloader, the right version -------------- step "ZIP prüfen" entries="$(bsdtar -tf "$zip")" if grep -qv '^wp-lit-mcp/' <<<"$entries"; then fail "ZIP enthält Einträge außerhalb von wp-lit-mcp/"; fi for need in wp-lit-mcp/wp-lit-mcp.php wp-lit-mcp/vendor/autoload_packages.php; do grep -qx "$need" <<<"$entries" || fail "Im ZIP fehlt $need" done if grep -qE '^wp-lit-mcp/(tests|bin|docs/superpowers|\.git)(/|$)' <<<"$entries"; then fail "Entwicklungsdateien im ZIP" fi [[ "$(bsdtar -xOf "$zip" wp-lit-mcp/wp-lit-mcp.php | sed -n 's/^ \* Version: //p')" == "$version" ]] \ || fail "Version im ZIP stimmt nicht mit $version überein" # --- 5. Sign, and prove the signature against the key built into the plugin ------------------------------------------- step "Signieren" php bin/release-sign.php sign "$zip" "$key" > "$zip.sig" php bin/release-sign.php verify "$zip" "$zip.sig" >/dev/null || fail "Signatur passt nicht zum Schlüssel im Plugin (PackageSignature::RELEASE_KEY)" if [[ $publish -eq 0 ]]; then echo "Probelauf in Ordnung: dist/$name und dist/$name.sig" echo "Veröffentlichen: GITEA_TOKEN=… bin/release.sh --publish" exit 0 fi # --- 6. Publish --------------------------------------------------------------------------------------------------------- step "Tag v$version setzen und pushen" git tag -a "v$version" -m "WP-LIT-MCP $version" git push origin "v$version" step "Release auf Gitea anlegen" auth=(-H "Authorization: token $GITEA_TOKEN") notes="$(awk -v header="= $version =" '$0 == header { on = 1; next } /^= / { on = 0 } on' readme.txt)" release_id="$(curl -fsS "${auth[@]}" -H 'Content-Type: application/json' \ -d "$(jq -n --arg tag "v$version" --arg body "$notes" '{tag_name: $tag, name: $tag, body: $body}')" \ "$api/releases" | jq -r .id)" for file in "$zip" "$zip.sig"; do curl -fsS "${auth[@]}" -F "attachment=@$file" "$api/releases/$release_id/assets?name=$(basename "$file")" >/dev/null done # --- 7. Check what a customer site will see: latest release, public asset URLs, a signature that verifies ------------ step "Ergebnis prüfen (ohne Token, wie eine Kundenseite)" latest="$(curl -fsS "$api/releases/latest")" [[ "$(jq -r .tag_name <<<"$latest")" == "v$version" ]] || fail "releases/latest liefert nicht v$version" asset_url() { jq -r --arg n "$1" '.assets[] | select(.name == $n) | .browser_download_url' <<<"$latest"; } zip_url="$(asset_url "$name")" sig_url="$(asset_url "$name.sig")" for url in "$zip_url" "$sig_url"; do [[ "$url" == "$repo_url/releases/download/"* ]] || fail "Asset-URL nicht wie erwartet: '$url'" done mkdir "$work/check" curl -fsSL "$zip_url" -o "$work/check/$name" curl -fsSL "$sig_url" -o "$work/check/$name.sig" php bin/release-sign.php verify "$work/check/$name" "$work/check/$name.sig" >/dev/null \ || fail "Heruntergeladenes Release besteht die Signaturprüfung nicht" echo "Release v$version veröffentlicht: $repo_url/releases/tag/v$version"