🚀 First official release of ShotScreen with complete feature set: ✨ Core Features: - Advanced screenshot capture system - Multi-monitor support - Professional UI/UX design - Automated update system with Sparkle - Apple notarized & code signed 🛠 Technical Excellence: - Native Swift macOS application - Professional build & deployment pipeline - Comprehensive error handling - Memory optimized performance 📦 Distribution Ready: - Professional DMG packaging - Apple notarization complete - No security warnings for users - Ready for public distribution This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
320 lines
10 KiB
Bash
Executable File
320 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# 🚀 SUPER HUFTERPROOF ShotScreen Release Script V2
|
|
# Zero HTML, Zero Problems, 100% Bulletproof
|
|
# 🎯 NEW: Auto-increment version detection!
|
|
#
|
|
# Usage:
|
|
# ./release_hufterproof_v2.sh # Auto-increment from current version
|
|
# ./release_hufterproof_v2.sh 1.15 # Use specific version
|
|
|
|
set -e
|
|
|
|
# Colors
|
|
GREEN='\033[0;32m'
|
|
BLUE='\033[0;34m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
PURPLE='\033[0;35m'
|
|
NC='\033[0m'
|
|
|
|
# Config
|
|
GITEA_URL="https://git.plet.i234.me"
|
|
RELEASES_REPO="Nick/shotscreen"
|
|
|
|
echo -e "${PURPLE}🚀 SUPER HUFTERPROOF Release Script V2${NC}"
|
|
echo -e "${PURPLE}=====================================${NC}"
|
|
|
|
# Get current version from Info.plist and auto-increment
|
|
get_next_version() {
|
|
if [ -f "Info.plist" ]; then
|
|
CURRENT_VERSION=$(plutil -extract CFBundleShortVersionString raw Info.plist 2>/dev/null || echo "1.0")
|
|
echo -e "${BLUE}🔍 Current version: $CURRENT_VERSION${NC}"
|
|
|
|
# Split version into parts (e.g., "1.12" -> major=1, minor=12)
|
|
IFS='.' read -r MAJOR MINOR <<< "$CURRENT_VERSION"
|
|
|
|
# Increment minor version
|
|
NEXT_MINOR=$((MINOR + 1))
|
|
SUGGESTED_VERSION="$MAJOR.$NEXT_MINOR"
|
|
|
|
echo -e "${GREEN}🚀 Suggested next version: $SUGGESTED_VERSION${NC}"
|
|
return 0
|
|
else
|
|
SUGGESTED_VERSION="1.0"
|
|
echo -e "${YELLOW}⚠️ No Info.plist found, suggesting: $SUGGESTED_VERSION${NC}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Determine version
|
|
NEW_VERSION="$1"
|
|
if [ -z "$NEW_VERSION" ]; then
|
|
get_next_version
|
|
echo -e "${YELLOW}Press ENTER to use suggested version ($SUGGESTED_VERSION) or type custom version:${NC}"
|
|
read -r USER_INPUT
|
|
|
|
if [ -z "$USER_INPUT" ]; then
|
|
NEW_VERSION="$SUGGESTED_VERSION"
|
|
echo -e "${GREEN}✅ Using suggested version: $NEW_VERSION${NC}"
|
|
else
|
|
NEW_VERSION="$USER_INPUT"
|
|
echo -e "${BLUE}✅ Using custom version: $NEW_VERSION${NC}"
|
|
fi
|
|
fi
|
|
|
|
echo -e "${BLUE}📋 Building version: $NEW_VERSION${NC}"
|
|
|
|
# Update Info.plist
|
|
echo -e "${BLUE}📝 Updating Info.plist...${NC}"
|
|
plutil -replace CFBundleVersion -string "$NEW_VERSION" Info.plist
|
|
plutil -replace CFBundleShortVersionString -string "$NEW_VERSION" Info.plist
|
|
|
|
# Update build_release_signed.sh with new version
|
|
echo -e "${BLUE}📝 Updating build script version...${NC}"
|
|
sed -i '' "s/APP_VERSION=\".*\"/APP_VERSION=\"$NEW_VERSION\"/" build_release_signed.sh
|
|
|
|
# Build app with Developer ID signing and notarization
|
|
echo -e "${BLUE}🔨 Building app with Developer ID signing...${NC}"
|
|
if ! ./build_release_signed.sh; then
|
|
echo -e "${RED}❌ Build failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Check DMG exists
|
|
DMG_PATH="./dist/ShotScreen-$NEW_VERSION.dmg"
|
|
if [ ! -f "$DMG_PATH" ]; then
|
|
echo -e "${RED}❌ DMG not found: $DMG_PATH${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Get signature and size
|
|
echo -e "${BLUE}🔐 Getting signature...${NC}"
|
|
SIG_OUTPUT=$(./.build/artifacts/sparkle/Sparkle/bin/sign_update "$DMG_PATH")
|
|
SIGNATURE=$(echo "$SIG_OUTPUT" | grep -o 'sparkle:edSignature="[^"]*"' | cut -d'"' -f2)
|
|
DMG_SIZE=$(stat -f%z "$DMG_PATH")
|
|
|
|
if [ -z "$SIGNATURE" ]; then
|
|
echo -e "${RED}❌ Signature generation failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✅ Signature: ${SIGNATURE:0:20}...${NC}"
|
|
echo -e "${GREEN}✅ Size: $DMG_SIZE bytes${NC}"
|
|
|
|
# Get release notes from TXT file (preserve original formatting)
|
|
RELEASE_NOTES=$(awk '
|
|
/^ShotScreen [0-9]/ {
|
|
if (count++ > 0) exit
|
|
next
|
|
}
|
|
/^=+$/ { next }
|
|
/^[A-Za-z]/ && !/^ShotScreen/ && NF > 0 {
|
|
print $0
|
|
}
|
|
/^ / && NF > 0 {
|
|
print $0
|
|
}
|
|
' release_notes.txt)
|
|
|
|
if [ -z "$RELEASE_NOTES" ]; then
|
|
RELEASE_NOTES="Release version $NEW_VERSION"
|
|
fi
|
|
|
|
# Create Gitea release
|
|
echo -e "${BLUE}📦 Creating Gitea release...${NC}"
|
|
|
|
# Format release notes beautifully for Gitea (markdown format)
|
|
FORMATTED_NOTES="## 🚀 ShotScreen $NEW_VERSION Features
|
|
|
|
$RELEASE_NOTES"
|
|
|
|
# Clean JSON (proper escaping for multiline text)
|
|
CLEAN_NOTES=$(printf "%s" "$FORMATTED_NOTES" | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
|
|
|
|
JSON_PAYLOAD="{
|
|
\"tag_name\": \"v$NEW_VERSION\",
|
|
\"name\": \"ShotScreen v$NEW_VERSION\",
|
|
\"body\": \"$CLEAN_NOTES\",
|
|
\"draft\": false,
|
|
\"prerelease\": false
|
|
}"
|
|
|
|
RESPONSE=$(curl -s -X POST \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" \
|
|
"$GITEA_URL/api/v1/repos/$RELEASES_REPO/releases")
|
|
|
|
RELEASE_ID=$(echo "$RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
|
|
|
if [ -z "$RELEASE_ID" ]; then
|
|
echo -e "${YELLOW}⚠️ Checking for existing release...${NC}"
|
|
EXISTING=$(curl -s "$GITEA_URL/api/v1/repos/$RELEASES_REPO/releases/tags/v$NEW_VERSION")
|
|
RELEASE_ID=$(echo "$EXISTING" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
|
fi
|
|
|
|
if [ -z "$RELEASE_ID" ]; then
|
|
echo -e "${RED}❌ Could not create/find release${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✅ Release ID: $RELEASE_ID${NC}"
|
|
|
|
# Upload DMG
|
|
echo -e "${BLUE}📤 Uploading DMG...${NC}"
|
|
UPLOAD_RESPONSE=$(curl -s -X POST \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
-F "attachment=@$DMG_PATH" \
|
|
"$GITEA_URL/api/v1/repos/$RELEASES_REPO/releases/$RELEASE_ID/assets")
|
|
|
|
ASSET_ID=$(echo "$UPLOAD_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
|
|
|
if [ -z "$ASSET_ID" ]; then
|
|
echo -e "${RED}❌ DMG upload failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✅ DMG uploaded${NC}"
|
|
|
|
# Create super simple appcast (no HTML, just plain text)
|
|
echo -e "${BLUE}📄 Creating simple appcast...${NC}"
|
|
|
|
# Convert plain text to simple HTML for appcast
|
|
HTML_NOTES=$(echo "$RELEASE_NOTES" | sed 's/^- /<li>/' | sed 's/$/<\/li>/')
|
|
if [[ "$HTML_NOTES" == *"<li>"* ]]; then
|
|
HTML_NOTES="<ul>$HTML_NOTES</ul>"
|
|
fi
|
|
|
|
# Clean up any old appcast first
|
|
rm -f appcast.xml
|
|
|
|
echo -e "${BLUE}📄 Generating appcast for version $NEW_VERSION...${NC}"
|
|
cat > appcast.xml << EOF
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
|
<channel>
|
|
<title>ShotScreen Updates</title>
|
|
<link>$GITEA_URL/$RELEASES_REPO/raw/branch/main/appcast.xml</link>
|
|
<description>ShotScreen Updates</description>
|
|
<language>en</language>
|
|
|
|
<item>
|
|
<title>ShotScreen $NEW_VERSION</title>
|
|
<description><![CDATA[
|
|
<h2>ShotScreen $NEW_VERSION</h2>
|
|
$HTML_NOTES
|
|
]]></description>
|
|
<pubDate>$(date -R)</pubDate>
|
|
<enclosure url="$GITEA_URL/$RELEASES_REPO/releases/download/v$NEW_VERSION/ShotScreen-$NEW_VERSION.dmg"
|
|
sparkle:version="$NEW_VERSION"
|
|
sparkle:shortVersionString="$NEW_VERSION"
|
|
length="$DMG_SIZE"
|
|
type="application/octet-stream"
|
|
sparkle:edSignature="$SIGNATURE" />
|
|
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
|
|
</item>
|
|
</channel>
|
|
</rss>
|
|
EOF
|
|
|
|
# Validate appcast
|
|
if ! xmllint --noout appcast.xml 2>/dev/null; then
|
|
echo -e "${RED}❌ Appcast validation failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✅ Valid appcast created${NC}"
|
|
|
|
# Debug: Show what we're about to deploy
|
|
echo -e "${BLUE}🔍 DEBUG: Appcast content preview:${NC}"
|
|
echo "Version: $(grep 'sparkle:version=' appcast.xml | head -1)"
|
|
echo "URL: $(grep 'enclosure url=' appcast.xml | head -1 | cut -d'"' -f2)"
|
|
|
|
# Deploy appcast
|
|
echo -e "${BLUE}🚀 Deploying appcast...${NC}"
|
|
|
|
RELEASES_DIR="../shotscreen"
|
|
|
|
# Clean up any existing problematic repository
|
|
if [ -d "$RELEASES_DIR" ]; then
|
|
echo -e "${YELLOW}🧹 Cleaning up existing repository...${NC}"
|
|
rm -rf "$RELEASES_DIR"
|
|
fi
|
|
|
|
# Create fresh directory and clone
|
|
mkdir -p "$RELEASES_DIR"
|
|
cd "$RELEASES_DIR"
|
|
|
|
echo -e "${BLUE}📥 Fresh clone from remote...${NC}"
|
|
git clone "$GITEA_URL/$RELEASES_REPO.git" . || {
|
|
echo -e "${YELLOW}⚠️ Remote repository doesn't exist or clone failed, creating new repo...${NC}"
|
|
git init
|
|
git remote add origin "$GITEA_URL/$RELEASES_REPO.git"
|
|
}
|
|
|
|
# Configure git for this repository
|
|
git config pull.rebase false # Use merge instead of rebase
|
|
git config user.name "ShotScreen Release Bot" || true
|
|
git config user.email "releases@shotscreen.app" || true
|
|
|
|
# Backup old appcast for comparison
|
|
if [ -f "appcast.xml" ]; then
|
|
echo -e "${BLUE}📋 Backing up old appcast...${NC}"
|
|
cp "appcast.xml" "appcast.xml.backup"
|
|
echo "Old version: $(grep 'sparkle:version=' "appcast.xml" | head -1)"
|
|
fi
|
|
|
|
# Copy new appcast
|
|
cd - > /dev/null
|
|
cp appcast.xml "$RELEASES_DIR/"
|
|
cd "$RELEASES_DIR"
|
|
|
|
# Add and commit
|
|
git add appcast.xml
|
|
git commit -m "Deploy appcast for v$NEW_VERSION" || true
|
|
|
|
# Push with force if needed (since we're authoritative for appcast)
|
|
echo -e "${BLUE}📤 Pushing appcast to remote...${NC}"
|
|
if ! git push origin main; then
|
|
echo -e "${YELLOW}⚠️ Normal push failed, force pushing appcast update...${NC}"
|
|
git push origin main --force
|
|
fi
|
|
|
|
cd - > /dev/null
|
|
|
|
# Test everything
|
|
echo -e "${BLUE}🧪 Testing...${NC}"
|
|
sleep 2
|
|
|
|
# Test appcast download
|
|
if ! curl -s "$GITEA_URL/$RELEASES_REPO/raw/branch/main/appcast.xml" | grep -q "$NEW_VERSION"; then
|
|
echo -e "${RED}❌ Appcast test failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Test DMG download
|
|
if ! curl -I -s "$GITEA_URL/$RELEASES_REPO/releases/download/v$NEW_VERSION/ShotScreen-$NEW_VERSION.dmg" | grep -q "200"; then
|
|
echo -e "${RED}❌ DMG test failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Git operations
|
|
echo -e "${BLUE}📝 Git operations...${NC}"
|
|
git add .
|
|
git commit -m "Release v$NEW_VERSION" || true
|
|
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION" || true
|
|
git push -u origin main || git push origin main --force
|
|
git push origin "v$NEW_VERSION" || true
|
|
|
|
echo
|
|
echo -e "${GREEN}🎉 SUPER HUFTERPROOF RELEASE COMPLETE! 🎉${NC}"
|
|
echo -e "${GREEN}======================================${NC}"
|
|
echo -e "${GREEN}✅ Version: $NEW_VERSION${NC}"
|
|
echo -e "${GREEN}✅ DMG Size: $DMG_SIZE bytes${NC}"
|
|
echo -e "${GREEN}✅ All tests: PASSED${NC}"
|
|
echo
|
|
echo -e "${BLUE}🌐 Release: $GITEA_URL/$RELEASES_REPO/releases/tag/v$NEW_VERSION${NC}"
|
|
echo -e "${BLUE}📡 Appcast: $GITEA_URL/$RELEASES_REPO/raw/branch/main/appcast.xml${NC}"
|
|
echo
|
|
echo -e "${YELLOW}🎯 Ready to test updates on other computer!${NC}" |