Created
March 19, 2026 05:48
-
-
Save drnic/bc639e36c04d263927f7fd4409447bea to your computer and use it in GitHub Desktop.
Wrapper for https://github.com/basecamp/gh-signoff
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Install gh CLI + gh-signoff extension for ephemeral environments (e.g. Claude Code web). | |
| # gh auto-authenticates when GITHUB_TOKEN env var is set. | |
| # Map GITHUB_API_KEY → GITHUB_TOKEN if only the former is set (Claude Code web uses GITHUB_API_KEY). | |
| if [ -z "${GITHUB_TOKEN:-}" ] && [ -n "${GITHUB_API_KEY:-}" ]; then | |
| export GITHUB_TOKEN="$GITHUB_API_KEY" | |
| fi | |
| # Derive GH_REPO from the git remote if not already set. | |
| # Needed when the remote uses a proxy (e.g. Claude Code web) and gh can't | |
| # detect the GitHub host automatically. | |
| if [ -z "${GH_REPO:-}" ]; then | |
| remote_url=$(git remote get-url origin 2>/dev/null || true) | |
| if [[ "$remote_url" =~ /git/([^/]+/[^/]+)(\.git)?$ ]]; then | |
| export GH_REPO="${BASH_REMATCH[1]}" | |
| elif [[ "$remote_url" =~ github\.com[:/]([^/]+/[^/]+?)(\.git)?$ ]]; then | |
| export GH_REPO="${BASH_REMATCH[1]}" | |
| fi | |
| fi | |
| if ! command -v gh &>/dev/null; then | |
| echo "==> Installing gh CLI..." | |
| mkdir -p "$HOME/bin" | |
| VERSION=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | jq -r .tag_name | tr -d v) | |
| curl -sL "https://github.com/cli/cli/releases/download/v${VERSION}/gh_${VERSION}_linux_amd64.tar.gz" \ | |
| | tar xz -C "$HOME/bin" --strip-components=2 "gh_${VERSION}_linux_amd64/bin/gh" | |
| export PATH="$HOME/bin:$PATH" | |
| fi | |
| if ! gh extension list 2>/dev/null | grep -q signoff; then | |
| echo "==> Installing gh-signoff..." | |
| gh extension install basecamp/gh-signoff | |
| fi | |
| echo "==> gh signoff ready: $(gh signoff version)" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Run checks locally, push, and sign off. | |
| # Usage: bin/signoff [--parallel] [--force] [checks|test|e2e|all] | |
| # | |
| # Requires commits to be pushed before signoff (GitHub needs the SHA). | |
| # Label "checks" (not "check") avoids collision with gh-signoff's | |
| # built-in "check" subcommand. | |
| # | |
| # --parallel Run all jobs concurrently (useful on multi-core machines). | |
| # Default is sequential, which is safer on constrained environments | |
| # like Claude Code web (1 CPU). | |
| # --force Skip turbo cache — actually re-run every job. | |
| DIR="$(cd "$(dirname "$0")/.." && pwd)" | |
| cd "$DIR" | |
| # Ensure gh CLI + gh-signoff extension are installed. | |
| # Source (not exec) so PATH additions propagate to this shell. | |
| source "$DIR/bin/setup-signoff" | |
| parallel=false | |
| force="" | |
| while [[ "${1:-}" == --* ]]; do | |
| case "$1" in | |
| --parallel) parallel=true; shift ;; | |
| --force) force="--force"; shift ;; | |
| *) echo "Unknown flag: $1"; exit 1 ;; | |
| esac | |
| done | |
| label="${1:-all}" | |
| ensure_pushed() { | |
| local branch | |
| branch=$(git rev-parse --abbrev-ref HEAD) | |
| if [ -z "$(git config "branch.${branch}.remote" 2>/dev/null)" ]; then | |
| echo "==> Pushing new branch ${branch}..." | |
| git push -u origin "$branch" | |
| elif ! git diff --quiet "@{push}" HEAD 2>/dev/null; then | |
| echo "==> Pushing unpushed commits..." | |
| git push | |
| fi | |
| } | |
| case "$label" in | |
| checks) | |
| pnpm check $force && pnpm lint $force && ensure_pushed && gh signoff checks | |
| ;; | |
| test) | |
| "$DIR/bin/setup" | |
| pnpm test $force && pnpm test:integration $force && ensure_pushed && gh signoff test | |
| ;; | |
| e2e) | |
| "$DIR/bin/setup" | |
| pnpm test:e2e && ensure_pushed && gh signoff e2e | |
| ;; | |
| all) | |
| "$DIR/bin/setup" | |
| # Sign off each category individually so passing groups are reported | |
| # even when a later group fails. | |
| ensure_pushed | |
| if $parallel; then | |
| # In parallel mode with --force, wipe the turbo cache and rebuild once | |
| # up front. Passing --force to each parallel turbo invocation causes | |
| # multiple instances to force-rebuild shared deps concurrently, which | |
| # races on dist/ directories. Wiping the cache achieves the same | |
| # "no stale results" goal without the race. | |
| if [ -n "$force" ]; then | |
| echo "==> Clearing turbo cache and rebuilding..." | |
| rm -rf node_modules/.cache/turbo | |
| pnpm build | |
| fi | |
| tmpdir=$(mktemp -d) | |
| trap 'rm -rf "$tmpdir"' EXIT | |
| jobs=(check lint test integration e2e) | |
| cmds=( | |
| "pnpm check" | |
| "pnpm lint" | |
| "pnpm test" | |
| "pnpm test:integration" | |
| "pnpm test:e2e" | |
| ) | |
| echo "==> Running checks, lint, test, integration, e2e in parallel..." | |
| num_jobs=${#jobs[@]} | |
| for name in "${jobs[@]}"; do | |
| echo " - $name" | |
| done | |
| # Launch all jobs in background, recording exit status to files. | |
| pids=() | |
| for i in "${!jobs[@]}"; do | |
| (${cmds[$i]} > "$tmpdir/${jobs[$i]}.log" 2>&1; echo $? > "$tmpdir/${jobs[$i]}.rc") & | |
| pids+=($!) | |
| done | |
| # Wait for each job individually and update its line as it finishes. | |
| failed=0 | |
| for i in "${!jobs[@]}"; do | |
| if wait "${pids[$i]}" 2>/dev/null; then | |
| icon="✓" | |
| else | |
| icon="✗" | |
| failed=1 | |
| fi | |
| # Move up to this job's line (line i, counting from bottom = num_jobs - i) | |
| lines_up=$(( num_jobs - i )) | |
| printf '\033[%dA\r %s %s\033[K\033[%dB\r' "$lines_up" "$icon" "${jobs[$i]}" "$lines_up" | |
| done | |
| # Show logs for any failed jobs. | |
| for name in "${jobs[@]}"; do | |
| rc=$(cat "$tmpdir/$name.rc" 2>/dev/null || echo 1) | |
| if [ "$rc" -ne 0 ]; then | |
| echo "" | |
| echo "==> $name output:" | |
| cat "$tmpdir/$name.log" | |
| fi | |
| done | |
| # Sign off each category based on its constituent jobs. | |
| # "checks" = check + lint, "test" = test + integration, "e2e" = e2e. | |
| check_rc=$(cat "$tmpdir/check.rc" 2>/dev/null || echo 1) | |
| lint_rc=$(cat "$tmpdir/lint.rc" 2>/dev/null || echo 1) | |
| test_rc=$(cat "$tmpdir/test.rc" 2>/dev/null || echo 1) | |
| integration_rc=$(cat "$tmpdir/integration.rc" 2>/dev/null || echo 1) | |
| e2e_rc=$(cat "$tmpdir/e2e.rc" 2>/dev/null || echo 1) | |
| signoff_labels=() | |
| if [ "$check_rc" -eq 0 ] && [ "$lint_rc" -eq 0 ]; then | |
| signoff_labels+=(checks) | |
| fi | |
| if [ "$test_rc" -eq 0 ] && [ "$integration_rc" -eq 0 ]; then | |
| signoff_labels+=(test) | |
| fi | |
| if [ "$e2e_rc" -eq 0 ]; then | |
| signoff_labels+=(e2e) | |
| fi | |
| if [ ${#signoff_labels[@]} -gt 0 ]; then | |
| echo "" | |
| echo "==> Signing off: ${signoff_labels[*]}" | |
| gh signoff "${signoff_labels[@]}" | |
| fi | |
| if [ "$failed" -ne 0 ]; then | |
| echo "" | |
| echo "==> Some jobs failed." | |
| exit 1 | |
| fi | |
| else | |
| failed=0 | |
| if pnpm check $force && pnpm lint $force; then | |
| echo "==> Signing off: checks" | |
| gh signoff checks | |
| else | |
| failed=1 | |
| fi | |
| if pnpm test $force && pnpm test:integration $force; then | |
| echo "==> Signing off: test" | |
| gh signoff test | |
| else | |
| failed=1 | |
| fi | |
| if pnpm test:e2e; then | |
| echo "==> Signing off: e2e" | |
| gh signoff e2e | |
| else | |
| failed=1 | |
| fi | |
| if [ "$failed" -ne 0 ]; then | |
| echo "==> Some jobs failed." | |
| exit 1 | |
| fi | |
| fi | |
| ;; | |
| *) | |
| echo "Usage: bin/signoff [--parallel] [--force] [checks|test|e2e|all]" | |
| exit 1 | |
| ;; | |
| esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment