From c6a21e7254fe817a2eab6a82fb7e099782934c02 Mon Sep 17 00:00:00 2001 From: Luiz Costa Date: Wed, 24 Sep 2025 00:38:36 -0300 Subject: [PATCH] add the release script --- Makefile | 7 +- scripts/create-release.sh | 373 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 379 insertions(+), 1 deletion(-) create mode 100755 scripts/create-release.sh diff --git a/Makefile b/Makefile index 6c5808a..ea099d1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Ghost MCP Development Makefile -.PHONY: help install install-local deps-install-python deps-install-dev deps-deps-install-uv install-pip venv start-ghost stop-ghost restart-ghost setup-tokens test test-unit test-integration test-coverage test-fast test-parallel test-e2e test-connection clean-test run dev format lint clean logs status check-deps setup docs +.PHONY: help install install-local deps-install-python deps-install-dev deps-deps-install-uv install-pip venv start-ghost stop-ghost restart-ghost setup-tokens test test-unit test-integration test-coverage test-fast test-parallel test-e2e test-connection clean-test run dev format lint clean logs status check-deps setup release docs .PHONY: help help: ## Show this help message @@ -238,6 +238,11 @@ setup: deps-deps-install-uv deps-install-python start-ghost setup-tokens ## Comp @echo " make run # Run the MCP server" @echo " make status # Check system status" +# Release +release: ## Create a new GitHub release with the current package version + @echo "🚀 Creating GitHub release..." + ./scripts/create-release.sh + # Documentation docs: ## Show important URLs and information @echo "📚 Ghost MCP Documentation" diff --git a/scripts/create-release.sh b/scripts/create-release.sh new file mode 100755 index 0000000..bd10d8f --- /dev/null +++ b/scripts/create-release.sh @@ -0,0 +1,373 @@ +#!/bin/bash + +# create-release.sh +# Create a new GitHub release with the same version as the Python package. +# +# How to use: +# This script reads the version from pyproject.toml and creates a GitHub release. +# It checks that the 'github' remote exists and the version doesn't already exist. +# +# Example: +# $ ./scripts/create-release.sh +# +# Requirements: +# - git remote named 'github' must be configured +# - gh CLI tool must be installed and authenticated +# - Version in pyproject.toml must not already exist as a git tag +# +# NOTE: +# Version tags and releases are immutable. If the version already exists, +# the script will fail with an error. + +# START + + +# Constants +declare -r SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +declare -r PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +declare -r PYPROJECT_FILE="$PROJECT_ROOT/pyproject.toml" + + +# Helpers +if [ -z "$TERM" ] || [ "$TERM" == "dumb" ]; then + tput() { + return 0 + } +fi +if ! type tput >/dev/null 2>&1; then + tput() { + return 0 + } +fi +function log_info() { + local CYAN=$(tput setaf 6) + local NC=$(tput sgr0) + echo "${CYAN}[INFO ]${NC} $*" 1>&2 +} +function log_warning() { + local YELLOW=$(tput setaf 3) + local NC=$(tput sgr0) + echo "${YELLOW}[WARNING]${NC} $*" 1>&2 +} +function log_debug() { + local PURPLE=$(tput setaf 5) + local NC=$(tput sgr0) + echo "${PURPLE}[DEBUG ]${NC} $*" 1>&2 +} +function log_error() { + local RED=$(tput setaf 1) + local NC=$(tput sgr0) + echo "${RED}[ERROR ]${NC} $*" 1>&2 +} +function log_success() { + local GREEN=$(tput setaf 2) + local NC=$(tput sgr0) + echo "${GREEN}[SUCCESS]${NC} $*" 1>&2 +} +function log_title() { + local GREEN=$(tput setaf 2) + local BOLD=$(tput bold) + local NC=$(tput sgr0) + echo 1>&2 + echo "${GREEN}${BOLD}---- $* ----${NC}" 1>&2 +} +function h_run() { + local ORANGE=$(tput setaf 3) + local NC=$(tput sgr0) + echo "${ORANGE}\$ ${NC}$*" 1>&2 + eval "$*" +} +function err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} +function print_help() { + # Prints help section from the top of the file + # + # It stops until it finds the '# START' line + + echo "HELP:" + while read -r LINE; do + if [[ "${LINE}" == "#!/bin/bash" ]] || [[ "${LINE}" == "" ]]; then + continue + fi + if [[ "${LINE}" == "# START" ]]; then + return + fi + echo "${LINE}" | sed 's/^# / /g' | sed 's/^#//g' + done <${BASH_SOURCE[0]} +} + + +# Functions +function check_requirements() { + # Check if all required tools and configurations are available + # Returns: + # 0: All requirements met + # 1: Missing requirements + + log_title "Checking Requirements" + + # Check if gh CLI is installed + if ! command -v gh >/dev/null 2>&1; then + log_error "GitHub CLI (gh) is not installed or not in PATH" + log_info "Install it from: https://cli.github.com/" + return 1 + fi + log_info "✅ GitHub CLI found: $(gh --version | head -n1)" + + # Check if gh is authenticated + if ! gh auth status >/dev/null 2>&1; then + log_error "GitHub CLI is not authenticated" + log_info "Run: gh auth login" + return 1 + fi + log_info "✅ GitHub CLI authenticated" + + # Check if github remote exists + if ! git remote get-url github >/dev/null 2>&1; then + log_error "Git remote 'github' is not configured" + log_info "Configure it with: git remote add github " + return 1 + fi + local github_url + github_url=$(git remote get-url github) + log_info "✅ GitHub remote configured: $github_url" + + # Check if pyproject.toml exists + if [[ ! -f "$PYPROJECT_FILE" ]]; then + log_error "pyproject.toml not found at: $PYPROJECT_FILE" + return 1 + fi + log_info "✅ pyproject.toml found" + + return 0 +} + +function get_package_version() { + # Extract version from pyproject.toml + # Returns: + # Version string from pyproject.toml + + if [[ ! -f "$PYPROJECT_FILE" ]]; then + log_error "pyproject.toml not found" + return 1 + fi + + # Extract version using grep and sed + local version + version=$(grep '^version = ' "$PYPROJECT_FILE" | sed 's/version = "\([^"]*\)"/\1/') + + if [[ -z "$version" ]]; then + log_error "Could not extract version from pyproject.toml" + return 1 + fi + + echo "$version" + return 0 +} + +function ensure_github_main_branch() { + # Ensure we're working from the github/main branch + # Returns: + # 0: Successfully on github/main + # 1: Failed to switch to github/main + + log_title "Ensuring GitHub Main Branch" + + # Fetch latest from github remote + log_info "Fetching latest from github remote..." + if ! h_run "git fetch github"; then + log_error "Failed to fetch from github remote" + return 1 + fi + + # Get current branch + local current_branch + current_branch=$(git branch --show-current) + log_info "Current branch: $current_branch" + + # Check if we're already on a branch tracking github/main + local upstream + upstream=$(git rev-parse --abbrev-ref @{upstream} 2>/dev/null || echo "") + + if [[ "$upstream" != "github/main" ]]; then + log_warning "Not on a branch tracking github/main (current upstream: ${upstream:-none})" + + # Check if we have uncommitted changes + if ! git diff-index --quiet HEAD --; then + log_error "You have uncommitted changes. Please commit or stash them before creating a release." + git status --short + return 1 + fi + + # Switch to github/main + log_info "Switching to github/main..." + if ! h_run "git checkout -B release-temp github/main"; then + log_error "Failed to checkout github/main" + return 1 + fi + log_info "✅ Now on branch tracking github/main" + else + # We're on a branch tracking github/main, make sure it's up to date + log_info "Updating branch to match github/main..." + if ! h_run "git reset --hard github/main"; then + log_error "Failed to reset to github/main" + return 1 + fi + log_info "✅ Branch updated to match github/main" + fi + + return 0 +} + +function check_version_exists() { + # Check if version already exists as a git tag + # Arguments: + # 1: VERSION - version to check + # Returns: + # 0: Version does not exist (safe to create) + # 1: Version already exists + + local VERSION=$1 + + if [[ -z "$VERSION" ]]; then + log_error "Version argument is required" + return 1 + fi + + # Fetch tags from github remote to ensure we have latest + log_info "Fetching tags from github remote..." + if ! h_run "git fetch github --tags"; then + log_error "Failed to fetch tags from github remote" + return 1 + fi + + # Check if tag exists + if git tag -l | grep -q "^v${VERSION}$"; then + log_error "Version v${VERSION} already exists as a git tag" + log_info "Existing tags:" + git tag -l | grep -E "^v[0-9]" | sort -V | tail -5 + return 1 + fi + + log_info "✅ Version v${VERSION} does not exist yet" + return 0 +} + +function create_github_release() { + # Create GitHub release with tag + # Arguments: + # 1: VERSION - version to release + # Returns: + # 0: Release created successfully + # 1: Failed to create release + + local VERSION=$1 + local TAG="v${VERSION}" + + if [[ -z "$VERSION" ]]; then + log_error "Version argument is required" + return 1 + fi + + log_title "Creating GitHub Release" + + # Create and push tag + log_info "Creating tag: $TAG" + if ! h_run "git tag $TAG"; then + log_error "Failed to create tag $TAG" + return 1 + fi + + log_info "Pushing tag to github remote..." + if ! h_run "git push github $TAG"; then + log_error "Failed to push tag to github remote" + # Clean up local tag + git tag -d "$TAG" 2>/dev/null || true + return 1 + fi + + # Create GitHub release + log_info "Creating GitHub release..." + local release_notes="Release version $VERSION + +This release includes all changes from the latest commits. + +## Installation + +\`\`\`bash +pip install ghost-mcp==$VERSION +\`\`\` + +## What's Changed + +See the [commit history](https://github.com/thenets/ghost-mcp/commits/v$VERSION) for detailed changes." + + if ! h_run "gh release create $TAG --title \"Release $VERSION\" --notes \"$release_notes\""; then + log_error "Failed to create GitHub release" + # Clean up tag + git push github --delete "$TAG" 2>/dev/null || true + git tag -d "$TAG" 2>/dev/null || true + return 1 + fi + + log_success "🎉 GitHub release v$VERSION created successfully!" + log_info "Release URL: https://github.com/thenets/ghost-mcp/releases/tag/$TAG" + + return 0 +} + + +# Main +if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then + # Script is being invoked directly instead of being sourced + + # Handle help + if [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then + print_help + exit 0 + fi + + # Change to project root + cd "$PROJECT_ROOT" || { + log_error "Failed to change to project root: $PROJECT_ROOT" + exit 1 + } + + log_title "Ghost MCP Release Creator" + + # Check all requirements + if ! check_requirements; then + log_error "Requirements check failed" + exit 1 + fi + + # Ensure we're on github/main branch + if ! ensure_github_main_branch; then + log_error "Failed to ensure github/main branch" + exit 1 + fi + + # Get package version + log_info "Reading version from pyproject.toml..." + VERSION=$(get_package_version) + if [[ $? -ne 0 ]] || [[ -z "$VERSION" ]]; then + log_error "Failed to get package version" + exit 1 + fi + log_info "Package version: $VERSION" + + # Check if version already exists + if ! check_version_exists "$VERSION"; then + log_error "Version check failed" + exit 1 + fi + + # Create the release + if ! create_github_release "$VERSION"; then + log_error "Failed to create GitHub release" + exit 1 + fi + + log_success "Release creation completed successfully!" +fi \ No newline at end of file