packages feed

clod-0.1.13: bin/release

#!/bin/bash
# Comprehensive release script for clod
# Combines man page generation, Hackage release, and Homebrew formula update

set -e  # Exit on any error

cd "$(dirname "$0")/.."
PROJECT_ROOT=$(pwd)

# Extract current version from cabal file
CURRENT_VERSION=$(grep "^version:" clod.cabal | sed 's/version: *//')
echo "Current version: $CURRENT_VERSION"

# Calculate default new version (increment last number)
if [[ $CURRENT_VERSION =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
  MAJOR="${BASH_REMATCH[1]}"
  MINOR="${BASH_REMATCH[2]}"
  PATCH="${BASH_REMATCH[3]}"
  DEFAULT_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
else
  # Fall back to simple increment if version format is unexpected
  DEFAULT_VERSION="$CURRENT_VERSION+1"
fi

# Ask user for new version
read -p "Enter new version [$DEFAULT_VERSION]: " VERSION
VERSION=${VERSION:-$DEFAULT_VERSION}

echo "=== Starting comprehensive release process for clod $VERSION ==="

# Update version in cabal file
echo "Updating version in clod.cabal..."
sed -i '' "s/^version:[ ]*$CURRENT_VERSION/version:             $VERSION/" clod.cabal

# Update CHANGELOG.md with new version entry
echo "Updating CHANGELOG.md..."
TODAY=$(date +"%Y-%m-%d")

# Check if a changelog entry for this version already exists
if ! grep -q "^## \[$VERSION\]" CHANGELOG.md; then
  # Insert new version entry at the top of the changelog (after the header)
  sed -i '' "1,/^# Changelog/ s/^# Changelog/# Changelog\n\n## [$VERSION] - $TODAY\n\n- Add your changes here\n\nThis release was created through human-AI pair programming, with Claude as the primary code author and Fuzz Leonard providing guidance, architectural decisions, and final review.\n/" CHANGELOG.md
  
  # Open CHANGELOG.md for editing
  echo "Opening CHANGELOG.md for editing. Please add your release notes."
  echo "Press Enter to continue once you've saved the changes."
  ${EDITOR:-nano} CHANGELOG.md
  read -p "Press Enter to continue with the release process..."
fi

# Step 1: Generate man pages
echo "=== Generating man pages ==="

# Inline implementation of generate-man-pages.sh
# Ensure pandoc is available
if ! command -v pandoc &> /dev/null; then
    echo "Error: pandoc is required to generate man pages"
    echo "Please install pandoc: https://pandoc.org/installing.html"
    exit 1
fi

# Make sure the source man directory exists
mkdir -p "$PROJECT_ROOT/man"

# Generate clod(1) - Command reference
echo "Generating clod(1).md source file..."
cat > "$PROJECT_ROOT/man/clod.1.md" << EOF
% CLOD(1) Clod $VERSION
% Fuzz Leonard & Claude <ink@fuzz.ink>
% March 2025

# NAME

clod - Claude Loader for preparing files for Claude AI's Project Knowledge

# SYNOPSIS

**clod** [*OPTIONS*]

# DESCRIPTION

Clod is a utility for preparing and uploading files to Claude AI's Project Knowledge feature. 
It tracks file changes, respects .gitignore and .clodignore patterns, and optimizes filenames 
for Claude's UI.

# OPTIONS

**--all**, **-a**
: Process all files (respecting .gitignore and .clodignore)

**--test**, **-t**
: Run in test mode (no prompts, useful for CI)

**--staging-dir** *DIR*, **-d** *DIR*
: Specify a directory for test mode (only used with --test)

**--verbose**, **-v**
: Enable verbose output

**--flush**, **-f**
: Remove missing entries from the database

**--last**, **-l**
: Reuse the previous staging directory

**--help**
: Show help information

**--version**
: Show version information

# EXAMPLES

Run clod (first run processes all files, subsequent runs process only modified files):
    clod

Force processing of all files:
    clod --all

Run in test mode with an optional test directory:
    clod --test --staging-dir /path/to/test/dir
    
Reuse the previous staging directory:
    clod --last
    
Remove missing entries from the database:
    clod --flush

# ENVIRONMENT VARIABLES

**CLOD_DIR**
: Override the default .clod directory name

**CLODIGNORE**
: Override the default .clodignore filename

# FILES

**.clodignore**
: Pattern file similar to .gitignore for excluding files

**.clod/database.dhall**
: Database of file checksums and metadata

# SEE ALSO

**clod(7)** for information about project instructions and safeguards.
**clod(8)** for a complete workflow guide to using clod with Claude AI.
EOF

# Generate clod(7) - Project instructions and safeguards
if [ -f "$PROJECT_ROOT/project-instructions.md" ] && [ -f "$PROJECT_ROOT/guardrails.md" ]; then
  echo "Generating clod(7).md source file..."
  
  # Create the header section
  cat > "$PROJECT_ROOT/man/clod.7.md" << EOF
% CLOD(7) Clod $VERSION
% Fuzz Leonard & Claude <ink@fuzz.ink>
% March 2025

# NAME

clod - project instructions and safeguards for Claude AI integration

# DESCRIPTION

This man page contains guidance on how to structure project instructions for Claude AI
and implement safeguards when using clod with Claude AI's Project Knowledge feature.
# PROJECT INSTRUCTIONS
EOF

  # Append project-instructions.md content
  cat "$PROJECT_ROOT/project-instructions.md" >> "$PROJECT_ROOT/man/clod.7.md"

  # Append guardrails.md content
  echo "" >> "$PROJECT_ROOT/man/clod.7.md"
  echo "# SAFEGUARDS" >> "$PROJECT_ROOT/man/clod.7.md"
  cat "$PROJECT_ROOT/guardrails.md" >> "$PROJECT_ROOT/man/clod.7.md"
else
  echo "Warning: Cannot generate clod(7).md, source files missing"
fi

# Generate clod(8) - Complete workflow guide
if [ -f "$PROJECT_ROOT/HUMAN.md" ]; then
  echo "Generating clod(8).md source file..."
  
  # Create the header section
  cat > "$PROJECT_ROOT/man/clod.8.md" << EOF
% CLOD(8) Clod $VERSION
% Fuzz Leonard & Claude <ink@fuzz.ink>
% March 2025

# NAME

clod - complete workflow guide for using clod with Claude AI

# DESCRIPTION

This man page contains a comprehensive guide to using clod with Claude AI,
including best practices, workflow details, and integration tips.

# ABOUT CLAUDE'S INVOLVEMENT

This project represents a novel approach to software development, where Claude
(the AI assistant from Anthropic) served as the primary programmer, implementing 
nearly 100% of the codebase based on guidance from Fuzz Leonard. This
human-AI collaboration model leverages each collaborator's strengths, with
the human providing vision, requirements and architectural decisions, and the
AI handling implementation details, testing, and most documentation.
EOF

  # Append HUMAN.md content
  cat "$PROJECT_ROOT/HUMAN.md" >> "$PROJECT_ROOT/man/clod.8.md"
else
  echo "Warning: Cannot generate clod(8).md, HUMAN.md missing"
fi

echo "Man page markdown generation completed"

# Commit version bump changes and man pages together
echo "=== Committing version bump and man page changes ==="
echo "Do you want to commit these changes? [y/N]"
read -r version_response
if [[ "$version_response" =~ ^[Yy] ]]; then
  git add clod.cabal CHANGELOG.md man/
  git commit -m "Bump version to $VERSION and update man pages"
  
  echo "Do you want to push the commit to origin? [y/N]"
  read -r push_version_response
  if [[ "$push_version_response" =~ ^[Yy] ]]; then
    git push origin
  fi
fi

# Step 2: Verify tests pass
echo "=== Running tests ==="
cabal test

# Step 3: Generate documentation
echo "=== Building documentation for Hackage ==="
cabal haddock --haddock-for-hackage

# Step 4: Create source distribution
echo "=== Creating source distribution ==="
cabal sdist

# Step 5: Check package
echo "=== Checking package ==="
cabal check

# Step 6: Test build
echo "=== Testing build ==="
cabal build --disable-documentation

# Step 7: Build man pages for verification
echo "=== Building man pages for verification ==="
"$PROJECT_ROOT/bin/install-man-pages.sh" /tmp/clod-man-test

# Step 8: Verify man pages were generated correctly (no commit needed, already done earlier)
echo "=== Verifying man pages ==="
ls -la "$PROJECT_ROOT/man/" | grep -E "\.md$"

# Step 9: Create tag
echo "=== Creating Git tag ==="
echo "Do you want to create git tag v$VERSION? [y/N]"
read -r response
if [[ "$response" =~ ^[Yy] ]]; then
  # Check if tag already exists
  if git rev-parse "v$VERSION" >/dev/null 2>&1; then
    echo "Tag v$VERSION already exists!"
    echo "Do you want to force update it? [y/N]"
    read -r force_tag
    if [[ "$force_tag" =~ ^[Yy] ]]; then
      git tag -fa "v$VERSION" -m "Release version $VERSION"
      echo "Tag v$VERSION updated."
    else
      echo "Skipping tag creation."
    fi
  else
    git tag -a "v$VERSION" -m "Release version $VERSION"
    echo "Tag v$VERSION created."
  fi
  
  echo "Do you want to push the tag to origin? [y/N]"
  read -r push_response
  if [[ "$push_response" =~ ^[Yy] ]]; then
    git push origin "v$VERSION"
  fi
fi

# Step 10: Generate documentation package for Hackage
echo "=== Generating Hackage documentation package ==="
echo "Generating documentation package for Hackage..."
cabal haddock --haddock-for-hackage --enable-documentation

# Step 11: Upload to Hackage (manual step)
echo "=== Ready to upload to Hackage ==="
echo "The following commands will upload the package and documentation to Hackage:"
echo
echo "  cabal upload --publish dist-newstyle/sdist/clod-$VERSION.tar.gz"
echo "  curl --http1.1 for documentation (using HTTP 1.1 to work around Hackage networking issues)"
echo

echo "Do you want to upload to Hackage now? [y/N]"
read -r upload_response
if [[ "$upload_response" =~ ^[Yy] ]]; then
  echo "Uploading package to Hackage..."
  cabal upload --publish "dist-newstyle/sdist/clod-$VERSION.tar.gz"
  
  echo "Please enter your Hackage username for documentation upload:"
  read -r username
  
  echo "Uploading documentation for clod-$VERSION"
  echo "You will be prompted for your Hackage password."
  # Must force HTTP 1.1 to get around networking issues
  curl --http1.1 -X PUT --data-binary "@dist-newstyle/clod-$VERSION-docs.tar.gz" \
    -H 'Content-Type: application/x-tar' \
    -H 'Content-Encoding: gzip' \
    --user "$username" \
    "https://hackage.haskell.org/package/clod-$VERSION/docs"
else
  echo "Skipping Hackage upload. Run the commands manually when ready."
  exit 0
fi

# Step 12: Wait for Hackage to process the package
echo "=== Waiting for Hackage to process the package ==="
echo "Waiting for Hackage to process the package (10 seconds)..."
sleep 10

# Step 13: Calculate SHA256 for Homebrew formula
echo "=== Updating Homebrew formula ==="
echo "Calculating SHA256 for Hackage package..."
HACKAGE_URL="https://hackage.haskell.org/package/clod-$VERSION/clod-$VERSION.tar.gz"

# Try to download and calculate SHA up to 3 times with increasing delays
MAX_ATTEMPTS=3
ATTEMPT=1
SHA256=""

while [ $ATTEMPT -le $MAX_ATTEMPTS ] && [ -z "$SHA256" ]; do
  echo "Attempt $ATTEMPT of $MAX_ATTEMPTS to calculate SHA256..."
  
  # Download the package and calculate SHA256
  SHA256=$(curl -sL "$HACKAGE_URL" | shasum -a 256 | cut -d ' ' -f 1)
  
  if [ -z "$SHA256" ] || [ "$SHA256" = "da39a3ee5e6b4b0d3255bfef95601890afd80709" ]; then
    # Empty SHA or SHA of empty file (package not available yet)
    WAIT_TIME=$((ATTEMPT * 10))
    echo "Package not available yet. Waiting $WAIT_TIME seconds before retry..."
    sleep $WAIT_TIME
    ATTEMPT=$((ATTEMPT + 1))
  else
    # Validate that the SHA256 looks legitimate (64 hex chars)
    if [[ "$SHA256" =~ ^[0-9a-f]{64}$ ]]; then
      echo "Valid SHA256 obtained: $SHA256"
      break
    else
      echo "Invalid SHA256 obtained. Retrying..."
      SHA256=""
      sleep 5
      ATTEMPT=$((ATTEMPT + 1))
    fi
  fi
done

if [ -z "$SHA256" ]; then
  echo "Error: Failed to calculate SHA256 after $MAX_ATTEMPTS attempts."
  echo "The package might not be available on Hackage yet."
  echo "You will need to update the formula manually with:"
  echo "  curl -sL $HACKAGE_URL | shasum -a 256"
  exit 1
fi

# Verify the package is accessible with expected size
PACKAGE_SIZE=$(curl -sI "$HACKAGE_URL" | grep -i "Content-Length" | awk '{print $2}' | tr -d '\r')
if [ -z "$PACKAGE_SIZE" ] || [ "$PACKAGE_SIZE" -lt 1000 ]; then
  echo "Warning: Package seems too small or inaccessible ($PACKAGE_SIZE bytes)."
  echo "SHA256 might be incorrect. Proceed with caution."
  echo "Press Enter to continue or Ctrl+C to abort and fix manually."
  read -r
fi

# Step 14: Update Homebrew formula
FORMULA_PATH="../homebrew-tap/Formula/clod.rb"
if [ ! -f "$FORMULA_PATH" ]; then
  echo "Error: Homebrew formula not found at $FORMULA_PATH"
  exit 1
fi

echo "Updating Homebrew formula with new version $VERSION and SHA256 $SHA256..."

# Update version and SHA in the formula file
# First update the version in both the URL and the comment
sed -i '' "s|clod-[0-9][0-9.]*\/clod-[0-9][0-9.]*\.tar\.gz|clod-$VERSION\/clod-$VERSION.tar.gz|g" "$FORMULA_PATH"
# Then update the SHA256
sed -i '' "s|sha256 \"[a-f0-9]\+\"|sha256 \"$SHA256\"|g" "$FORMULA_PATH"

# Verify the URL format is correct (making sure it has .tar.gz extension)
if ! grep -q "\.tar\.gz\"" "$FORMULA_PATH"; then
  echo "ERROR: Formula URL format issue detected. Please check $FORMULA_PATH manually."
  cat "$FORMULA_PATH" | grep -A 2 "url"
  exit 1
fi

# Step 15: Commit and push Homebrew formula update
echo "=== Committing Homebrew formula update ==="
echo "Do you want to commit and push the updated Homebrew formula? [y/N]"
read -r brew_response
if [[ "$brew_response" =~ ^[Yy] ]]; then
  (
    cd "../homebrew-tap"
    git add "Formula/clod.rb"
    git commit -m "Update clod formula to version $VERSION"
    
    echo "Do you want to push the formula update to origin? [y/N]"
    read -r push_brew_response
    if [[ "$push_brew_response" =~ ^[Yy] ]]; then
      git push origin
    fi
  )
fi

echo "=== Release process complete! ==="
echo "Version $VERSION has been released to Hackage and the Homebrew formula has been updated."
echo "Remember to test the Homebrew installation with: brew install --build-from-source ./homebrew-tap/Formula/clod.rb"

# Alternative Haskell implementation note:
# This script could be rewritten in Haskell using libraries like:
# - process: For executing external commands
# - turtle: A shell scripting library
# - optparse-applicative: For command line option parsing
# - filepath/directory: For file path manipulation
# - bytestring/text: For text processing
#
# Benefits would include:
# - Type safety
# - Better error handling
# - Integration with the rest of the Haskell codebase
# - Potential for more modular design