/Hugo-Update

/hugo-update is a Claude Code slash command for this blog. It reads the target Hugo version from .hugo-version and syncs the HUGO_VERSION environment variable in Cloudflare Pages — verifying a local build first, then updating preview and waiting for human confirmation before updating production. All Cloudflare API calls are handled by scripts/hugo-update.sh.

StepAction
Check current stateRuns scripts/hugo-update.sh status to show preview and production versions vs. the target
Verify local buildStarts hugo serve locally to confirm the site builds with the target version
Update previewRuns scripts/hugo-update.sh preview — patches the preview env var and triggers a deployment
Verify previewPresents the preview URL for human verification before touching production
Update productionRuns scripts/hugo-update.sh production — patches the production env var
SummaryReports what changed; new version takes effect on next production deployment

Command Definition Link to heading

The content below is included directly from .claude/commands/hugo-update.md at build time — edit that file to update both this page and Claude Code’s behavior simultaneously.

This file is published at /hugo-update/ via Hugo’s readFile shortcode. Changes here are automatically reflected on the page at next build.

Updates the HUGO_VERSION environment variable in Cloudflare Pages by reading the target version from .hugo-version and applying it to preview, then production after human verification. All Cloudflare API interactions are handled by scripts/hugo-update.sh.

How It Works Link to heading

A single bash script — scripts/hugo-update.sh — handles all Cloudflare API calls. The script fetches the API token from 1Password CLI (op) once per invocation, discovers the Cloudflare account and Pages project via API (no hardcoded IDs), and performs the requested action.

./scripts/hugo-update.sh status      # Show current vs target versions
./scripts/hugo-update.sh preview     # Update preview env var and trigger a build
./scripts/hugo-update.sh production  # Update production env var

Prerequisites Link to heading

Workflow Link to heading

Step 1: Check current state Link to heading

./scripts/hugo-update.sh status

Show the output to the user. If the script exits with “Already up to date”, stop.

Step 2: Verify local build Link to heading

Start the local development server to confirm the site builds with the target Hugo version:

hugo serve --minify --enableGitInfo --baseURL http://localhost:1313

Wait for the user to confirm the local build looks correct before continuing.

Step 3: Update preview Link to heading

./scripts/hugo-update.sh preview

Show the preview URL from the output. Ask the user to verify the preview deployment looks correct before proceeding to production.

Step 4: Update production Link to heading

After the user confirms the preview is correct:

./scripts/hugo-update.sh production

Report the result. The new Hugo version takes effect on the next production deployment (merge to main).

Step 5: Summary Link to heading

Report what changed: preview and production HUGO_VERSION before and after.

GitHub Actions Pipeline Link to heading

A companion workflow — .github/workflows/hugo-update.yml — runs weekly and opens a pull request automatically when a new Hugo release is available. The /hugo-update command handles the Cloudflare side of that PR: syncing the HUGO_VERSION environment variable to match the updated .hugo-version file.

Script Link to heading

The shell script that handles all Cloudflare API calls (scripts/hugo-update.sh):

#!/usr/bin/env bash
set -euo pipefail

HUGO_VERSION_FILE=".hugo-version"

die() {
  echo "ERROR: $*" >&2
  exit 1
}

usage() {
  echo "Usage: $(basename "$0") <status|preview|production>" >&2
  echo "" >&2
  echo "  status      Show current vs target HUGO_VERSION in Cloudflare Pages" >&2
  echo "  preview     Update preview env var and trigger a preview deployment" >&2
  echo "  production  Update production env var (takes effect on next main deployment)" >&2
  exit 1
}

read_target_version() {
  [[ -f "$HUGO_VERSION_FILE" ]] || die ".hugo-version not found"
  local version
  version=$(tr -d '[:space:]' < "$HUGO_VERSION_FILE")
  [[ -n "$version" ]] || die ".hugo-version is empty"
  echo "$version"
}

fetch_token() {
  local token
  token=$(op read 'op://claude/Cloudflare Workers/cloudflare_api_token' 2>/dev/null) || \
    die "Failed to read token from 1Password. Is 'op' installed and authenticated?"
  [[ -n "$token" ]] || die "Token from 1Password is empty"
  echo "$token"
}

discover_account() {
  local token="$1"
  local response count
  response=$(curl -sf https://api.cloudflare.com/client/v4/accounts \
    -H "Authorization: Bearer $token") || die "Failed to reach Cloudflare API"
  count=$(echo "$response" | jq '.result | length')
  [[ "$count" -eq 1 ]] || die "Expected 1 Cloudflare account, found $count"
  echo "$response" | jq -r '.result[0].id'
}

discover_project() {
  local token="$1"
  local account_id="$2"
  local response count
  response=$(curl -sf "https://api.cloudflare.com/client/v4/accounts/$account_id/pages/projects" \
    -H "Authorization: Bearer $token") || die "Failed to list Pages projects"
  count=$(echo "$response" | jq '.result | length')
  [[ "$count" -eq 1 ]] || die "Expected 1 Pages project, found $count"
  echo "$response" | jq -r '.result[0].name'
}

get_versions() {
  local token="$1"
  local account_id="$2"
  local project="$3"
  local response
  response=$(curl -sf "https://api.cloudflare.com/client/v4/accounts/$account_id/pages/projects/$project" \
    -H "Authorization: Bearer $token") || die "Failed to fetch project details"
  echo "$response" | jq -r '{
      preview:    (.result.deployment_configs.preview.env_vars.HUGO_VERSION.value    // "unset"),
      production: (.result.deployment_configs.production.env_vars.HUGO_VERSION.value // "unset")
    }'
}

patch_env_var() {
  local token="$1"
  local account_id="$2"
  local project="$3"
  local env="$4"
  local version="$5"
  local result success
  result=$(curl -sf -X PATCH \
    "https://api.cloudflare.com/client/v4/accounts/$account_id/pages/projects/$project" \
    -H "Authorization: Bearer $token" \
    -H "Content-Type: application/json" \
    --data "$(jq -n \
      --arg env "$env" \
      --arg ver "$version" \
      '{deployment_configs: {($env): {env_vars: {HUGO_VERSION: {value: $ver, type: "plain_text"}}}}}'
    )") || die "PATCH request failed for $env environment"
  success=$(echo "$result" | jq -r '.success')
  [[ "$success" == "true" ]] || die "Update failed: $(echo "$result" | jq -c '.errors')"
}

trigger_deployment() {
  local token="$1"
  local account_id="$2"
  local project="$3"
  local branch="$4"
  local version="$5"
  local response
  response=$(curl -sf -X POST \
    "https://api.cloudflare.com/client/v4/accounts/$account_id/pages/projects/$project/deployments" \
    -H "Authorization: Bearer $token" \
    -H "Content-Type: application/json" \
    --data "$(jq -n --arg branch "$branch" --arg ver "$version" \
      '{branch: $branch, env_vars: {HUGO_VERSION: {value: $ver, type: "plain_text"}}}')") || \
    die "Failed to trigger deployment"
  echo "$response"
}

cmd_status() {
  local target token account_id project versions preview_ver production_ver
  target=$(read_target_version)
  token=$(fetch_token)
  account_id=$(discover_account "$token")
  project=$(discover_project "$token" "$account_id")
  versions=$(get_versions "$token" "$account_id" "$project")
  preview_ver=$(echo "$versions" | jq -r '.preview')
  production_ver=$(echo "$versions" | jq -r '.production')

  printf "%-12s %-10s %s\n" "Environment" "Current"       "Target"
  printf "%-12s %-10s %s\n" "-----------" "-------"       "------"
  printf "%-12s %-10s %s\n" "preview"     "$preview_ver"    "$target"
  printf "%-12s %-10s %s\n" "production"  "$production_ver" "$target"

  if [[ "$preview_ver" == "$target" && "$production_ver" == "$target" ]]; then
    echo ""
    echo "Already up to date."
    exit 2
  fi
}

cmd_preview() {
  local target token account_id project versions preview_ver branch response deploy_url deploy_id deploy_hugo dashboard_url
  target=$(read_target_version)
  token=$(fetch_token)
  account_id=$(discover_account "$token")
  project=$(discover_project "$token" "$account_id")
  versions=$(get_versions "$token" "$account_id" "$project")
  preview_ver=$(echo "$versions" | jq -r '.preview')

  if [[ "$preview_ver" == "$target" ]]; then
    echo "Preview already at Hugo $target."
    exit 2
  fi

  patch_env_var "$token" "$account_id" "$project" "preview" "$target"

  branch=$(git branch --show-current)
  response=$(trigger_deployment "$token" "$account_id" "$project" "$branch" "$target")

  deploy_url=$(echo "$response" | jq -r '.result.url')
  deploy_id=$(echo "$response" | jq -r '.result.id')
  deploy_hugo=$(echo "$response" | jq -r '.result.env_vars.HUGO_VERSION.value // "unset"')
  dashboard_url="https://dash.cloudflare.com/$account_id/pages/view/$project/$deploy_id"

  echo "Updated preview: $preview_ver$target"
  echo "Dashboard:  $dashboard_url"
  echo "Preview:    $deploy_url"

  if [[ "$deploy_hugo" != "$target" ]]; then
    echo "WARNING: Deployment env var is '$deploy_hugo', expected '$target'" >&2
    exit 1
  fi
  echo "Verified:   deployment will use Hugo $deploy_hugo"
}

cmd_production() {
  local target token account_id project versions production_ver
  target=$(read_target_version)
  token=$(fetch_token)
  account_id=$(discover_account "$token")
  project=$(discover_project "$token" "$account_id")
  versions=$(get_versions "$token" "$account_id" "$project")
  production_ver=$(echo "$versions" | jq -r '.production')

  if [[ "$production_ver" == "$target" ]]; then
    echo "Production already at Hugo $target."
    exit 2
  fi

  patch_env_var "$token" "$account_id" "$project" "production" "$target"
  echo "Updated production: $production_ver$target"
  echo "Hugo $target takes effect on the next production deployment (merge to main)."
}

case "${1:-}" in
  status)     cmd_status ;;
  preview)    cmd_preview ;;
  production) cmd_production ;;
  *)          usage ;;
esac