mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 21:25:27 +02:00
a1236bb269
New binary for uploading screenshots to gstack.gg with pre-upload compression (sips/ImageMagick), slug sanitization, and auth token refresh. Outputs proxy URL to stdout for PR embedding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
160 lines
5.5 KiB
Bash
Executable File
160 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# gstack-screenshot-upload — upload a screenshot to gstack.gg
|
|
#
|
|
# Usage:
|
|
# gstack-screenshot-upload <file> [--repo-slug X] [--branch X] [--viewport X]
|
|
#
|
|
# Uploads a PNG to gstack.gg and prints the proxy URL (with watermark) to stdout.
|
|
# All diagnostics go to stderr. Exit 0 = success, 1 = error.
|
|
#
|
|
# Env overrides (for testing):
|
|
# GSTACK_STATE_DIR — override ~/.gstack state directory
|
|
# GSTACK_DIR — override auto-detected gstack root
|
|
# GSTACK_WEB_URL — override gstack.gg URL
|
|
set -euo pipefail
|
|
|
|
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
|
|
|
# Source config
|
|
if [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
|
. "$GSTACK_DIR/supabase/config.sh"
|
|
fi
|
|
WEB_URL="${GSTACK_WEB_URL:-https://gstack.gg}"
|
|
|
|
# ─── Parse args ───────────────────────────────────────────────────
|
|
FILE=""
|
|
REPO_SLUG=""
|
|
BRANCH=""
|
|
VIEWPORT=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--repo-slug) REPO_SLUG="$2"; shift 2 ;;
|
|
--branch) BRANCH="$2"; shift 2 ;;
|
|
--viewport) VIEWPORT="$2"; shift 2 ;;
|
|
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
*) FILE="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$FILE" ]; then
|
|
echo "Usage: gstack-screenshot-upload <file.png> [--repo-slug X] [--branch X] [--viewport X]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$FILE" ]; then
|
|
echo "Error: file not found: $FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Validate PNG ─────────────────────────────────────────────────
|
|
MIME=$(file --mime-type -b "$FILE" 2>/dev/null || echo "unknown")
|
|
if [ "$MIME" != "image/png" ]; then
|
|
echo "Error: only PNG files are supported (got $MIME)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Slugify helper ───────────────────────────────────────────────
|
|
slugify() {
|
|
echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's|/|-|g; s|[^a-z0-9._-]||g'
|
|
}
|
|
|
|
[ -n "$REPO_SLUG" ] && REPO_SLUG="$(slugify "$REPO_SLUG")"
|
|
[ -n "$BRANCH" ] && BRANCH="$(slugify "$BRANCH")"
|
|
|
|
# ─── Pre-upload compression ──────────────────────────────────────
|
|
FILE_SIZE=$(wc -c < "$FILE" | tr -d ' ')
|
|
UPLOAD_FILE="$FILE"
|
|
|
|
if [ "$FILE_SIZE" -gt 2097152 ]; then # > 2MB
|
|
echo "File is $(( FILE_SIZE / 1024 ))KB — compressing..." >&2
|
|
TMPFILE="$(mktemp /tmp/gstack-compress-XXXXXX.png)"
|
|
|
|
if command -v sips >/dev/null 2>&1; then
|
|
# macOS: sips resize to max 1920px wide
|
|
cp "$FILE" "$TMPFILE"
|
|
sips --resampleWidth 1920 "$TMPFILE" >/dev/null 2>&1 && UPLOAD_FILE="$TMPFILE"
|
|
elif command -v magick >/dev/null 2>&1; then
|
|
# ImageMagick 7+
|
|
magick "$FILE" -resize '1920x>' "$TMPFILE" 2>/dev/null && UPLOAD_FILE="$TMPFILE"
|
|
elif command -v convert >/dev/null 2>&1; then
|
|
# ImageMagick 6
|
|
convert "$FILE" -resize '1920x>' "$TMPFILE" 2>/dev/null && UPLOAD_FILE="$TMPFILE"
|
|
else
|
|
echo "Warning: no resize tool available (sips/magick/convert), uploading raw" >&2
|
|
fi
|
|
|
|
if [ "$UPLOAD_FILE" = "$TMPFILE" ]; then
|
|
NEW_SIZE=$(wc -c < "$TMPFILE" | tr -d ' ')
|
|
echo "Compressed: $(( FILE_SIZE / 1024 ))KB → $(( NEW_SIZE / 1024 ))KB" >&2
|
|
fi
|
|
fi
|
|
|
|
# ─── Check file size limit ────────────────────────────────────────
|
|
FINAL_SIZE=$(wc -c < "$UPLOAD_FILE" | tr -d ' ')
|
|
if [ "$FINAL_SIZE" -gt 10485760 ]; then # 10MB
|
|
echo "Error: file too large ($(( FINAL_SIZE / 1024 ))KB, max 10MB)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Get auth token ───────────────────────────────────────────────
|
|
if ! "$GSTACK_DIR/bin/gstack-auth-refresh" --check >/dev/null 2>&1; then
|
|
echo "Error: not authenticated. Run: gstack-auth" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ACCESS_TOKEN="$("$GSTACK_DIR/bin/gstack-auth-refresh" 2>/dev/null)"
|
|
if [ -z "$ACCESS_TOKEN" ]; then
|
|
echo "Error: failed to get auth token" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Upload ───────────────────────────────────────────────────────
|
|
HTTP_RESPONSE="$(curl -s -w "\n%{http_code}" \
|
|
--max-time 30 \
|
|
-X POST "${WEB_URL}/api/images/upload" \
|
|
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
-F "file=@${UPLOAD_FILE}" \
|
|
-F "repo_slug=${REPO_SLUG}" \
|
|
-F "branch=${BRANCH}" \
|
|
-F "viewport=${VIEWPORT}" \
|
|
2>/dev/null || echo -e "\n000")"
|
|
|
|
HTTP_CODE="$(echo "$HTTP_RESPONSE" | tail -1)"
|
|
HTTP_BODY="$(echo "$HTTP_RESPONSE" | sed '$d')"
|
|
|
|
# Clean up temp file
|
|
[ "$UPLOAD_FILE" != "$FILE" ] && rm -f "$UPLOAD_FILE" 2>/dev/null
|
|
|
|
case "$HTTP_CODE" in
|
|
2*)
|
|
# Extract proxy URL from response JSON
|
|
URL="$(echo "$HTTP_BODY" | jq -r '.url' 2>/dev/null || echo "")"
|
|
if [ -n "$URL" ] && [ "$URL" != "null" ]; then
|
|
echo "$URL" # stdout: proxy URL only
|
|
else
|
|
echo "Error: upload succeeded but no URL in response" >&2
|
|
echo "$HTTP_BODY" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
401)
|
|
echo "Error: authentication failed (401). Re-run: gstack-auth" >&2
|
|
exit 1
|
|
;;
|
|
413)
|
|
echo "Error: file too large (413)" >&2
|
|
exit 1
|
|
;;
|
|
415)
|
|
echo "Error: unsupported file type (415). Only PNG supported." >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo "Error: upload failed (HTTP ${HTTP_CODE})" >&2
|
|
[ -n "$HTTP_BODY" ] && echo "$HTTP_BODY" >&2
|
|
exit 1
|
|
;;
|
|
esac
|