File size: 8,439 Bytes
88d2f2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/env bash
# =============================================================================
# scripts/deploy_to_hf.sh β€” one-command deploy to Hugging Face Spaces.
# =============================================================================
# Strategy: snapshot the current main commit into a fresh orphan branch, swap
# README.md for the HF-flavored version (with the YAML frontmatter Spaces
# needs), force-push that single-commit branch as HF's `main`, then clean up.
#
# Why an orphan branch (not a regular merge / push of main)?
#   1. HF Spaces requires YAML frontmatter at the top of README.md so it
#      knows to launch a Docker container. GitHub's full README has none β€”
#      we only want the swap to live on the deploy branch, not on main.
#   2. main's git history is ~1000+ commits. HF pushes carry the full
#      reachable history, and any historical commit that ever held a
#      binary >10 MB would fail HF's pre-receive hook. Orphan = 1 commit,
#      no history to litigate.
#
# Prerequisites:
#   * `hf` remote already configured (run once:
#       git remote add hf https://huggingface.co/spaces/messili/polyglot-alpha)
#   * `hf auth login` already done (token cached by huggingface_hub CLI).
#   * Working tree is clean on main, or untracked-only.
# -----------------------------------------------------------------------------
set -euo pipefail

# ---- color/log helpers ------------------------------------------------------
if [[ -t 1 ]]; then
    BLUE=$'\033[34m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RED=$'\033[31m'; RESET=$'\033[0m'
else
    BLUE=''; GREEN=''; YELLOW=''; RED=''; RESET=''
fi
log()  { printf '%s[deploy]%s %s\n' "$BLUE" "$RESET" "$*"; }
ok()   { printf '%s[deploy]%s %s\n' "$GREEN" "$RESET" "$*"; }
warn() { printf '%s[deploy]%s %s\n' "$YELLOW" "$RESET" "$*" >&2; }
die()  { printf '%s[deploy]%s %s\n' "$RED" "$RESET" "$*" >&2; exit 1; }

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"

# ---- preflight checks -------------------------------------------------------
log "preflight checks"
git remote get-url hf > /dev/null 2>&1 || die "hf remote not configured. Run: git remote add hf https://huggingface.co/spaces/messili/polyglot-alpha"
[[ -f deploy/hf-readme.md ]] || die "deploy/hf-readme.md missing β€” HF needs the YAML frontmatter version"
[[ -f Dockerfile ]]          || die "Dockerfile missing β€” HF Spaces is a Docker SDK Space"
[[ -f deploy/nginx.conf ]]   || die "deploy/nginx.conf missing β€” required by Dockerfile"
[[ -f deploy/entrypoint.sh ]] || die "deploy/entrypoint.sh missing β€” required by Dockerfile"

# Working tree state: tracked changes block deploy, untracked are fine.
if ! git diff-index --quiet HEAD --; then
    die "working tree has uncommitted tracked changes; commit or stash first"
fi

# Contract ABIs (contracts/out/*.json) are not tracked on main (excluded by
# contracts/.gitignore from Foundry), but the backend NEEDS them at runtime
# to know the deployed contract interfaces. Build them now so the orphan
# snapshot includes a freshly compiled set.
command -v forge >/dev/null 2>&1 || die "forge not installed β€” install Foundry: https://book.getfoundry.sh/getting-started/installation"
log "running forge build to refresh contracts/out/ (compiled ABIs)"
( cd contracts && forge build --silent ) || die "forge build failed β€” fix contracts before deploying"
[[ -d contracts/out ]] || die "forge build succeeded but contracts/out/ missing β€” something is very wrong"

ORIGINAL_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if [[ "$ORIGINAL_BRANCH" != "main" ]]; then
    warn "currently on '$ORIGINAL_BRANCH' (not main) β€” will checkout main"
    git checkout main
fi

MAIN_SHA_SHORT="$(git rev-parse --short HEAD)"
TMP_BRANCH="hf-deploy-$(date +%Y%m%d-%H%M%S)"
log "deploying main @ $MAIN_SHA_SHORT  β†’  hf:main  (via temp orphan '$TMP_BRANCH')"

# ---- cleanup trap -----------------------------------------------------------
# If anything below fails, restore the user to their starting branch, restore
# contracts/.git if we moved it, and delete the temp orphan branch so they
# don't end up stuck on a half-built orphan.
CONTRACTS_GIT_MOVED=0
cleanup() {
    local exit_code=$?
    # Restore contracts/.git if we renamed it.
    if [[ "$CONTRACTS_GIT_MOVED" -eq 1 && -d contracts/.git.deploy-tmp ]]; then
        mv contracts/.git.deploy-tmp contracts/.git
    fi
    # Switch off temp branch and delete it.
    local current
    current="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
    if [[ "$current" == "$TMP_BRANCH" || "$current" == "HEAD" ]]; then
        git checkout --force "$ORIGINAL_BRANCH" 2>/dev/null || git checkout --force main 2>/dev/null || true
    fi
    if [[ -n "${TMP_BRANCH:-}" ]] && git show-ref --verify --quiet "refs/heads/$TMP_BRANCH"; then
        git branch -D "$TMP_BRANCH" > /dev/null 2>&1 || true
    fi
    if [[ $exit_code -ne 0 ]]; then
        warn "deploy aborted (exit $exit_code)"
    fi
    exit $exit_code
}
trap cleanup EXIT

# ---- build the orphan snapshot ---------------------------------------------
log "creating orphan branch '$TMP_BRANCH'"
git checkout --orphan "$TMP_BRANCH"
# `git checkout --orphan` keeps the working tree but stages everything. We
# want to start from a clean stage, then re-add everything filtered by
# .gitignore β€” so a stray binary in working tree doesn't sneak through.
git rm -rf --cached . > /dev/null 2>&1 || true

# Foundry leaves a nested `.git` in `contracts/` (its own submodule lockfile
# tracking). When the outer repo runs `git add -A`, it sees this as a
# (broken) submodule and aborts with "does not have a commit checked out".
# Temporarily rename it; the cleanup trap restores it on the way out.
if [[ -d contracts/.git ]]; then
    log "temporarily renaming contracts/.git (foundry nested) so git add -A can recurse"
    mv contracts/.git contracts/.git.deploy-tmp
    CONTRACTS_GIT_MOVED=1
fi

git add -A

log "swapping README for HF-flavored version (deploy/hf-readme.md β†’ README.md)"
cp deploy/hf-readme.md README.md
git add README.md

# contracts/out (compiled ABI JSON) is in .gitignore on main, but HF Spaces
# needs the ABIs at runtime β€” force-add the subdir into the orphan snapshot.
if [[ -d contracts/out ]]; then
    log "force-adding contracts/out/ (ABIs needed by backend, ignored on main)"
    git add -f contracts/out/
fi

# Drop dev-facing assets HF Spaces doesn't need and whose binaries trip
# HF's pre-receive hook (e.g. submission/diagrams/*.png are GitHub hackathon
# docs, never served by the Docker container). We `git rm --cached` them
# from the orphan index β€” the working tree copies stay so main is untouched
# when the cleanup trap checks us back out.
HF_EXCLUDE_PATHS=(
    submission              # hackathon write-up β€” GitHub-only
    docs                    # design docs β€” GitHub-only
    examples                # operator example scripts β€” GitHub-only
    contracts/lib           # foundry submodules β€” only needed for forge build
    contracts/cache         # foundry compile cache
    contracts/broadcast     # foundry deploy logs
)
for path in "${HF_EXCLUDE_PATHS[@]}"; do
    if git ls-files --cached "$path" 2>/dev/null | head -1 | grep -q .; then
        git rm -r --cached --quiet "$path" > /dev/null 2>&1 || true
    fi
done

# Single commit β€” HF only ever sees this one.
COMMIT_MSG="deploy: main@${MAIN_SHA_SHORT} β†’ HF Spaces ($(date -u +%Y-%m-%dT%H:%MZ))"
git commit --quiet -m "$COMMIT_MSG"
ok "snapshot committed: $(git rev-parse --short HEAD)"

# ---- push to HF -------------------------------------------------------------
log "force-pushing to hf:main (HF Space will auto-rebuild on receipt)"
if ! git push hf "$TMP_BRANCH:main" --force 2>&1; then
    die "push to HF failed β€” see error above. Common causes: stale auth ('hf auth login'), Space settings (sdk: docker required), or large files."
fi
ok "pushed β†’ https://huggingface.co/spaces/messili/polyglot-alpha"

# ---- restore main, swap README back -----------------------------------------
log "restoring '$ORIGINAL_BRANCH' (your README.md never moved on main)"
git checkout --force "$ORIGINAL_BRANCH"

# Cleanup trap deletes the temp branch on exit.
ok "deploy complete"
echo
echo "  HF Space: https://messili-polyglot-alpha.hf.space/"
echo "  Build progress: https://huggingface.co/spaces/messili/polyglot-alpha"
echo "  Cold rebuild ~3-5 min; warm rebuild ~30-60 s."