#!/usr/bin/env bash # Unico escritor de version + tag + push de un paquete del toolbox. # Interactivo: scripts/release (o `make release`) # Directo: scripts/release [--yes] # Al liberar `commands` sincroniza Config::VERSION con la version del manifiesto. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CONFIG_PHP="$ROOT/packages/commands/src/Toolbox/Config.php" die() { echo "$*" >&2; exit 1; } usage() { echo "Uso: scripts/release [ ] [--yes]" >&2 echo " sin argumentos: modo interactivo (o 'make release')" >&2 } packages() { local f for f in "$ROOT"/packages/*/duckbrain.json; do [ -f "$f" ] && basename "$(dirname "$f")" done } manifest() { echo "$ROOT/packages/$1/duckbrain.json"; } version_of() { php -r '$d=json_decode(file_get_contents($argv[1]),true); echo $d["version"] ?? "";' "$(manifest "$1")" } bump() { php -r ' $v = ltrim($argv[1], "vV"); $p = array_map("intval", array_pad(explode(".", preg_replace("/[-+].*/", "", $v)), 3, 0)); switch ($argv[2]) { case "major": $p[0]++; $p[1] = 0; $p[2] = 0; break; case "minor": $p[1]++; $p[2] = 0; break; default: $p[2]++; } echo implode(".", $p);' "$1" "$2" } ask() { local prompt="$1" reply printf '%s' "$prompt" >&2 read -r reply || die "entrada agotada; pasa los argumentos o correlo en un TTY" printf '%s' "$reply" } ASSUME_YES=0 POSITIONAL=() for arg in "$@"; do case "$arg" in --yes|-y) ASSUME_YES=1 ;; *) POSITIONAL+=("$arg") ;; esac done confirm() { [ "$ASSUME_YES" = "1" ] && return 0 local r r="$(ask "$1 [Y/n] ")" case "${r,,}" in ""|y|yes) return 0 ;; *) return 1 ;; esac } # --- eleccion de paquete ----------------------------------------------------- if [ "${#POSITIONAL[@]}" -ge 1 ]; then PACKAGE="${POSITIONAL[0]}" else mapfile -t PKGS < <(packages) [ "${#PKGS[@]}" -gt 0 ] || die "No hay paquetes en $ROOT/packages/" echo "Paquetes del toolbox:" for i in "${!PKGS[@]}"; do printf ' %2d) %-10s v%s\n' "$((i + 1))" "${PKGS[i]}" "$(version_of "${PKGS[i]}")" done while :; do R="$(ask 'Elegir paquete (numero o nombre): ')" if [[ "$R" =~ ^[0-9]+$ ]] && [ "$R" -ge 1 ] && [ "$R" -le "${#PKGS[@]}" ]; then PACKAGE="${PKGS[R - 1]}" break fi printf '%s\n' "${PKGS[@]}" | grep -qxF "$R" && { PACKAGE="$R"; break; } echo " opcion invalida" >&2 done fi MANIFEST="$(manifest "$PACKAGE")" [ -f "$MANIFEST" ] || die "No existe el paquete '$PACKAGE' ($MANIFEST)" # --- eleccion de incremento --------------------------------------------------- if [ "${#POSITIONAL[@]}" -ge 2 ]; then TYPE="${POSITIONAL[1]}" else while :; do printf 'Incremento para %s (actual v%s):\n 1) patch\n 2) minor\n 3) major\n' "$PACKAGE" "$(version_of "$PACKAGE")" >&2 R="$(ask 'Elegir (numero o nombre): ')" case "$R" in 1|patch) TYPE=patch; break ;; 2|minor) TYPE=minor; break ;; 3|major) TYPE=major; break ;; esac echo " opcion invalida" >&2 done fi case "$TYPE" in patch|minor|major) ;; *) die "Incremento invalido: '$TYPE' (usa patch|minor|major)" ;; esac VERSION="$(version_of "$PACKAGE")" [ -n "$VERSION" ] || die "El manifiesto no declara 'version': $MANIFEST" NEW_VERSION="$(bump "$VERSION" "$TYPE")" TAG="$PACKAGE-v$NEW_VERSION" CURRENT_CONFIG="" [ "$PACKAGE" = "commands" ] && CURRENT_CONFIG="$(grep -oP "const VERSION = '\K[^']+" "$CONFIG_PHP" || true)" LAST_TAG="$(git -C "$ROOT" tag -l "$PACKAGE-v*" --sort=-v:refname | head -1 || true)" if [ -n "$LAST_TAG" ]; then CHANGES="$(git -C "$ROOT" log --oneline "$LAST_TAG"..HEAD -- "packages/$PACKAGE" || true)" else CHANGES="$(git -C "$ROOT" log --oneline HEAD -- "packages/$PACKAGE" || true)" fi # --- resumen y confirmacion --------------------------------------------------- echo "Release: $PACKAGE v$VERSION -> v$NEW_VERSION (tag $TAG)" if [ "$PACKAGE" = "commands" ] && [ "$CURRENT_CONFIG" != "$NEW_VERSION" ]; then echo " Config::VERSION: v$CURRENT_CONFIG -> v$NEW_VERSION (se sincroniza)" fi if [ -z "$CHANGES" ]; then echo "Cambios desde el ultimo release: ninguno" else echo "Cambios desde el ultimo release:" echo "$CHANGES" | sed 's/^/ /' fi confirm "Confirmar release?" || die "Cancelado; sin cambios." # --- aplicar ------------------------------------------------------------------- php -r ' $file = $argv[1]; $data = json_decode(file_get_contents($file), true); $data["version"] = $argv[2]; file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"); ' "$MANIFEST" "$NEW_VERSION" if [ "$PACKAGE" = "commands" ] && [ "$CURRENT_CONFIG" != "$NEW_VERSION" ]; then sed -i "s/const VERSION = '[^']*'/const VERSION = '$NEW_VERSION'/" "$CONFIG_PHP" fi git -C "$ROOT" add -- "$MANIFEST" [ "$PACKAGE" = "commands" ] && git -C "$ROOT" add -- "$CONFIG_PHP" git -C "$ROOT" commit -q -m "release($PACKAGE): v$NEW_VERSION" git -C "$ROOT" tag -a "$TAG" -m "release($PACKAGE): v$NEW_VERSION" echo "OK: commit + tag $TAG creados." if git -C "$ROOT" remote get-url origin >/dev/null 2>&1; then if confirm "Pushear a origin (commit + tag)?"; then git -C "$ROOT" push -q origin HEAD --follow-tags echo "OK: pusheado." else echo "Sin pushear. Cuando quieras: git push origin HEAD --follow-tags" fi else echo "No hay remote origin; el tag queda local." fi