build_deploy.sh
#!/usr/bin/env bash
# ----------------------------------------------------------
# build_and_deploy.sh   (standalone, zero extra dependencies)
#
# 1. Detect Quarto or bookdown project
# 2. Build the site into docs/
# 3. Commit any changes (source + docs/) and push to GitHub
# ----------------------------------------------------------

set -euo pipefail
IFS=$'\n\t'

# ---------- CONFIG ----------
: "${PUSH_BRANCH:=main}"    # branch to push to (e.g., 'gh-pages')
: "${SITE_DIR:=docs}"       # output folder served by GitHub Pages

# ---------- DRY RUN ----------
DRY_RUN=false
while getopts "n" opt; do
  [[ $opt == "n" ]] && DRY_RUN=true
done

# ---------- LOGGING ---------
log()   { printf "\033[0;32m%s\033[0m\n" "$1"; }
error() { printf "\033[0;31m%s\033[0m\n" "$1" >&2; }

# ---------- CLEANUP HANDLER --
cleanup_common_r() {
  [[ ${COMMON_CREATED:-0} -eq 1 ]] && rm -f _common.R
}
trap cleanup_common_r EXIT

# ---------- HELPERS ---------
ensure_common_r() {
  if [[ ! -f "_common.R" ]]; then
    log "Creating stub _common.R for bookdown"
    cat > _common.R <<'EOF'
# Stub generated by build_and_deploy.sh
# Add global knitr options or library() calls here if desired.
EOF
    COMMON_CREATED=1
  else
    COMMON_CREATED=0
  fi
}

# ---------- BUILD FUNCTION --
build_site() {
  log "Building site..."
  start=$SECONDS

  # ensure bookdown is installed for rendering
  Rscript --vanilla -e 'if (!requireNamespace("bookdown", quietly=TRUE)) install.packages("bookdown")'

  if [[ -f "_quarto.yml" || -f "quarto.yml" ]]; then
    log "Detected Quarto project"
    quarto render .

  elif [[ -f "_bookdown.yml" ]] || grep -q "bookdown::" *.Rmd 2>/dev/null; then
    log "Detected bookdown project"
    ensure_common_r
    Rscript --vanilla -e "bookdown::render_book('.', output_dir='${SITE_DIR}')"

    # move default _book output
    if [[ -d "_book" ]]; then
      rm -rf "${SITE_DIR}"
      mv _book "${SITE_DIR}"
    fi

  else
    error "No Quarto or bookdown config found; aborting."
    exit 1
  fi

  duration=$(( SECONDS - start ))
  log "Build completed in ${duration}s"
}

# ---------- GIT FUNCTION ----
commit_and_push() {
  log "Staging all changes..."
  git add -A

  if $DRY_RUN; then
    git diff --staged
    log "Dry run: no commit or push performed."
    exit 0
  fi

  if ! git diff --cached --quiet; then
    local msg="${*:-rebuild on $(date '+%Y-%m-%d %H:%M:%S')}"
    log "Committing: ${msg}"
    git commit -m "${msg}"
    log "Pushing to ${PUSH_BRANCH}..."
    git push origin "${PUSH_BRANCH}"
  else
    log "Nothing to commit; working tree clean."
  fi
}

# ---------- MAIN -----------
build_site
commit_and_push "$@"
log "Done."
