/hugo-update
/hugo-update is a Claude Code slash command that syncs the HUGO_VERSION environment variable in Cloudflare Pages from .hugo-version. It verifies a local build, updates preview, waits for human confirmation, then updates production.
| Step | Action |
|---|---|
| Check current state | Runs scripts/hugo-update.sh status to show preview and production versions vs. the target |
| Verify local build | Starts hugo serve locally to confirm the site builds with the target version |
| Update preview | Runs scripts/hugo-update.sh preview — patches the preview env var and triggers a deployment |
| Verify preview | Presents the preview URL for human verification before touching production |
| Update production | Runs scripts/hugo-update.sh production — patches the production env var |
| Summary | Reports what changed; new version takes effect on next production deployment |
Command Definition #
The content below is included from .claude/commands/hugo-update.md at build time. Edit that file to update both this page and Claude Code’s behavior.
This file is published at /hugo-update/ via Hugo’s
readFileshortcode. 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 #
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 #
- 1Password CLI (
op) installed and authenticated curlandjqavailable
Workflow #
Step 1: Check current state #
./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 #
Check that the installed Hugo version is at least the target:
hugo version
If the local version is older than the target, run brew upgrade hugo and verify again. A newer local version is fine; Hugo is backward-compatible.
Then start the development server:
hugo serve --minify --enableGitInfo --baseURL http://localhost:1313
Wait for the user to confirm the local build looks correct.
Step 3: Update preview #
./scripts/hugo-update.sh preview
The script outputs a preview URL. Automatically validate the preview deployment returns HTTP 200 using cloudflared:
CF_TOKEN=$(cloudflared access token --app="<preview-url>" 2>/dev/null)
curl -sSf -o /dev/null -w '%{http_code}' -b "CF_Authorization=$CF_TOKEN" "<preview-url>/"
Replace <preview-url> with the URL printed by the script. If cloudflared access login is needed, prompt the user to run it first. Show the preview URL and HTTP status. Ask the user to confirm before proceeding to production.
Step 4: Update production #
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 #
Report what changed: preview and production HUGO_VERSION before and after.
GitHub Actions Pipeline #
.github/workflows/hugo-update.yml runs weekly and opens a pull request when a new Hugo release is available. /hugo-update handles the Cloudflare side: syncing HUGO_VERSION to match .hugo-version.
Script #
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 matches 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"
matches=$(echo "$response" | jq '[.result[] | select(.deployment_configs.production.env_vars.HUGO_VERSION != null)]')
count=$(echo "$matches" | jq 'length')
[[ "$count" -eq 1 ]] || die "Expected 1 Pages project with HUGO_VERSION configured, found $count"
echo "$matches" | jq -r '.[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