#!/usr/bin/env bash set -euo pipefail repo_root="$(git rev-parse --show-toplevel)" cd "$repo_root" if [[ -f .env ]]; then set -a # shellcheck source=/dev/null . ./.env set +a fi project="Setlist/Setlist.csproj" manifest="Setlist/manifest.json" assembly_info="Setlist/Properties/AssemblyInfo.cs" version="${1:-$(jq -r '.version' "$manifest")}" tag="v${version}" game_version="$(jq -r '.gameVersion' "$manifest")" require() { if ! command -v "$1" >/dev/null 2>&1; then echo "error: required command not found: $1" >&2 exit 1 fi } require curl require dotnet require git require jq require rg require zip if [[ -z "${GITEA_AGENT_TOKEN:-}" ]]; then echo "error: GITEA_AGENT_TOKEN is not set; add it to .env or export it" >&2 exit 1 fi manifest_version="$(jq -r '.version' "$manifest")" if [[ "$version" != "$manifest_version" ]]; then echo "error: requested version $version does not match manifest version $manifest_version" >&2 exit 1 fi if ! rg -q "\\[assembly: AssemblyVersion\\(\"${version}\\.0\"\\)\\]" "$assembly_info"; then echo "error: AssemblyVersion does not match ${version}.0" >&2 exit 1 fi if ! rg -q "\\[assembly: AssemblyFileVersion\\(\"${version}\\.0\"\\)\\]" "$assembly_info"; then echo "error: AssemblyFileVersion does not match ${version}.0" >&2 exit 1 fi if ! git diff --quiet --exit-code -- . ':(exclude)Setlist/bin' ':(exclude)Setlist/obj'; then echo "error: tracked worktree changes are present; commit them before releasing" >&2 exit 1 fi branch="$(git symbolic-ref --quiet --short HEAD)" if [[ -z "$branch" ]]; then echo "error: releases must be run from a branch, not detached HEAD" >&2 exit 1 fi origin_url="$(git remote get-url origin)" case "$origin_url" in git@*:*|gitea@*:*) host_part="${origin_url%%:*}" host="${host_part#*@}" repo_path="${origin_url#*:}" ;; ssh://*@*/*) without_scheme="${origin_url#ssh://}" without_user="${without_scheme#*@}" host="${without_user%%/*}" repo_path="${without_user#*/}" ;; http://*/*|https://*/*) without_scheme="${origin_url#*://}" host="${without_scheme%%/*}" repo_path="${without_scheme#*/}" ;; *) echo "error: cannot parse origin URL: $origin_url" >&2 exit 1 ;; esac repo_path="${repo_path%.git}" api_base="${GITEA_API_BASE:-https://${host}/api/v1}" echo "Restoring $project" dotnet restore "$project" echo "Building $project Release" dotnet build "$project" -c Release short_sha="$(git rev-parse --short=7 HEAD)" asset_name="Setlist-${version}-bs${game_version}-${short_sha}.zip" asset_dir="Setlist/bin/Release/zip" asset_path="${asset_dir}/${asset_name}" package_root="$(mktemp -d)" response_body="$(mktemp)" request_body="$(mktemp)" curl_config="$(mktemp)" cleanup() { rm -rf "$package_root" rm -f "$response_body" "$request_body" "$curl_config" } trap cleanup EXIT chmod 600 "$curl_config" { printf 'silent\n' printf 'show-error\n' printf 'header = "Authorization: token %s"\n' "$GITEA_AGENT_TOKEN" } > "$curl_config" mkdir -p "$asset_dir" "$package_root/Plugins" cp Setlist/bin/Release/Setlist.dll "$package_root/Plugins/Setlist.dll" cp Setlist/bin/Release/Setlist.pdb "$package_root/Plugins/Setlist.pdb" rm -f "$asset_path" (cd "$package_root" && zip -qr "$repo_root/$asset_path" Plugins) echo "Packaged $asset_path" sha256sum "$asset_path" if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then tag_target="$(git rev-list -n 1 "$tag")" head_target="$(git rev-parse HEAD)" if [[ "$tag_target" != "$head_target" ]]; then echo "error: local tag $tag points at $tag_target, not HEAD $head_target" >&2 exit 1 fi else git tag -a "$tag" -m "Setlist $tag" fi echo "Pushing $branch and $tag to origin" git push origin "HEAD:${branch}" git push origin "$tag" release_url="${api_base}/repos/${repo_path}/releases/tags/${tag}" status="$(curl --config "$curl_config" -o "$response_body" -w '%{http_code}' "$release_url" || true)" if [[ "$status" == "200" ]]; then release_id="$(jq -r '.id' "$response_body")" echo "Using existing Gitea release $tag" elif [[ "$status" == "404" ]]; then jq -n \ --arg tag "$tag" \ --arg name "$tag" \ --arg body "Release ${tag} for Beat Saber ${game_version}." \ --arg target "$branch" \ '{tag_name:$tag, target_commitish:$target, name:$name, body:$body, draft:false, prerelease:false}' \ > "$request_body" status="$(curl --config "$curl_config" -X POST -H 'Content-Type: application/json' --data @"$request_body" -o "$response_body" -w '%{http_code}' "${api_base}/repos/${repo_path}/releases" || true)" if [[ "$status" != "201" ]]; then echo "error: failed to create release; Gitea returned HTTP $status" >&2 cat "$response_body" >&2 exit 1 fi release_id="$(jq -r '.id' "$response_body")" echo "Created Gitea release $tag" else echo "error: failed to inspect release; Gitea returned HTTP $status" >&2 cat "$response_body" >&2 exit 1 fi assets_url="${api_base}/repos/${repo_path}/releases/${release_id}/assets" status="$(curl --config "$curl_config" -o "$response_body" -w '%{http_code}' "$assets_url" || true)" if [[ "$status" != "200" ]]; then echo "error: failed to list release assets; Gitea returned HTTP $status" >&2 cat "$response_body" >&2 exit 1 fi existing_asset_id="$(jq -r --arg name "$asset_name" '.[] | select(.name == $name) | .id' "$response_body" | head -n 1)" if [[ -n "$existing_asset_id" ]]; then echo "Deleting existing asset $asset_name" status="$(curl --config "$curl_config" -X DELETE -o "$response_body" -w '%{http_code}' "${assets_url}/${existing_asset_id}" || true)" if [[ "$status" != "204" ]]; then echo "error: failed to delete existing asset; Gitea returned HTTP $status" >&2 cat "$response_body" >&2 exit 1 fi fi status="$(curl --config "$curl_config" -X POST -H 'Content-Type: application/zip' --data-binary @"$asset_path" -o "$response_body" -w '%{http_code}' "${assets_url}?name=${asset_name}" || true)" if [[ "$status" != "201" ]]; then echo "error: failed to upload release asset; Gitea returned HTTP $status" >&2 cat "$response_body" >&2 exit 1 fi html_url="$(jq -r '.browser_download_url // .download_url // empty' "$response_body")" echo "Uploaded $asset_name" if [[ -n "$html_url" ]]; then echo "$html_url" fi