Publish releases from two scripts, one per remote
A release meant running the RELEASING.md checklist by hand: tag, push twice, create the GitHub release, then remember the Gitea push. The two scripts do the machine-checkable part of it, with the shared flow in scripts/lib/ so they cannot drift apart: clean worktree, not behind the remote, typecheck, tests and the install.sh syntax check, then the tag at HEAD, the branch push and the release object. Only the release object differs. GitHub creates it with gh, and publishing it is what fires the image build. Gitea gets the same notes through its API, with the token from GITEA_TOKEN or the one tea already stored, since nothing there builds images. Both accept --version (defaulting to package.json), --branch, --yes, --dry-run and --skip-checks, and a rerun finishes a release that failed half way instead of starting over: an existing tag is reused, a pushed ref is skipped and an existing release is left alone. A tag sitting on another commit is refused, since publishing would ship that commit's code.
This commit is contained in:
Executable
+223
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env bash
|
||||
# Publishes a Cobble release to the Gitea mirror.
|
||||
#
|
||||
# scripts/publish-gitea.sh
|
||||
#
|
||||
# Runs the same release gate as the GitHub script, tags HEAD with the
|
||||
# package.json version, pushes main and the tag to the `gitea` remote, then
|
||||
# creates the Gitea release over the API.
|
||||
#
|
||||
# Gitea has no release workflow, so nothing is built here: the image is built
|
||||
# once by .github/workflows/release.yml and mirrored to
|
||||
# git.tben.me/moduletrace/cobble. This script only publishes the source and
|
||||
# the release object.
|
||||
#
|
||||
# The token comes from GITEA_TOKEN, or from the Gitea section of ~/.config/tea
|
||||
# if the tea CLI is installed. It needs the `write:repository` scope on the
|
||||
# repository, which is what the repo scope gives.
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/publish-common.sh
|
||||
. "$SCRIPT_DIR/lib/publish-common.sh"
|
||||
# shellcheck source=lib/publish-release.sh
|
||||
. "$SCRIPT_DIR/lib/publish-release.sh"
|
||||
|
||||
REMOTE="gitea"
|
||||
TEA_CONFIG="${TEA_CONFIG:-$HOME/.config/tea/config.yml}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Publishes a Cobble release to the Gitea mirror.
|
||||
|
||||
Usage: scripts/publish-gitea.sh [options]
|
||||
|
||||
Options:
|
||||
--version VERSION Version to release, with or without the leading v.
|
||||
Defaults to package.json's version.
|
||||
--branch NAME Branch to publish from (default: main).
|
||||
--yes, -y Do not prompt.
|
||||
--dry-run Print what would run, change nothing locally or remotely.
|
||||
--skip-checks Skip typecheck, tests and the install.sh syntax check.
|
||||
-h, --help Show this help.
|
||||
|
||||
The release body is the commit log since the previous tag, the same text the
|
||||
GitHub script publishes, since only GitHub can generate notes itself.
|
||||
|
||||
Environment:
|
||||
GITEA_TOKEN Token with write:repository on the repository.
|
||||
TEA_CONFIG The tea CLI config to read a token from.
|
||||
Default: ~/.config/tea/config.yml
|
||||
DRY_RUN=1, ASSUME_YES=1 Same as --dry-run and --yes.
|
||||
EOF
|
||||
}
|
||||
|
||||
parse_publish_args "$@"
|
||||
if [ "$HELP" = "1" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
reject_unknown_args "$0"
|
||||
|
||||
trap cleanup_notes EXIT
|
||||
|
||||
# MARK:tea-token
|
||||
# Reads the token tea stored for a host, so an existing `tea login` is enough.
|
||||
tea_token_for_host() {
|
||||
local host="$1"
|
||||
local config="${2:-$TEA_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then
|
||||
return 0
|
||||
fi
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 - "$config" "$host" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
path, host = sys.argv[1], sys.argv[2]
|
||||
text = open(path, encoding="utf-8").read()
|
||||
token = None
|
||||
current = None
|
||||
for line in text.splitlines():
|
||||
line = line.rstrip()
|
||||
if line.startswith("- name:"):
|
||||
current = line.split(":", 1)[1].strip().strip('"').strip("'")
|
||||
continue
|
||||
match = re.match(r"\s+url:\s*(.+)$", line)
|
||||
if match:
|
||||
url = match.group(1).strip().strip('"').strip("'")
|
||||
current = re.sub(r"^[a-z]+://", "", url)
|
||||
current = current.split("/")[0].split("@")[-1]
|
||||
continue
|
||||
match = re.match(r"\s+token:\s*(.+)$", line)
|
||||
if match and current == host:
|
||||
token = match.group(1).strip().strip('"').strip("'")
|
||||
if token:
|
||||
print(token)
|
||||
PY
|
||||
return 0
|
||||
fi
|
||||
|
||||
# No python: the token line of the first login block that names the host.
|
||||
awk -v host="$host" '
|
||||
/- name:/ { block = $0; next }
|
||||
/url:/ { block = block " " $0 }
|
||||
/token:/ {
|
||||
if (index(block, host) > 0) {
|
||||
sub(/^[^:]*:[[:space:]]*/, "", $0)
|
||||
gsub(/["'"'"']/, "", $0)
|
||||
print $0
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "$config"
|
||||
}
|
||||
# END:tea-token
|
||||
|
||||
require_repo_root
|
||||
preflight "$REMOTE"
|
||||
resolve_version
|
||||
|
||||
remote_url="$(git remote get-url "$REMOTE")"
|
||||
parse_remote_url "$remote_url"
|
||||
API_URL="${GITEA_API_URL:-https://${REMOTE_HOST}/api/v1}"
|
||||
REPO_SLUG="${REMOTE_OWNER}/${REMOTE_REPO}"
|
||||
COMPARE_URL="https://${REMOTE_HOST}/${REPO_SLUG}/compare/"
|
||||
|
||||
step "Checking the Gitea token"
|
||||
TOKEN="${GITEA_TOKEN:-}"
|
||||
if [ -z "$TOKEN" ]; then
|
||||
TOKEN="$(tea_token_for_host "$REMOTE_HOST" | head -n1)"
|
||||
if [ -n "$TOKEN" ]; then
|
||||
detail "Using the token from $TEA_CONFIG."
|
||||
fi
|
||||
fi
|
||||
if [ -z "$TOKEN" ]; then
|
||||
fail "No Gitea token. Set GITEA_TOKEN, or run 'tea login add --url https://$REMOTE_HOST --token <token>'."
|
||||
fi
|
||||
ok "A token for $REMOTE_HOST is set."
|
||||
|
||||
require_cmd curl "Install curl."
|
||||
|
||||
run_release_checks
|
||||
prepare_tag
|
||||
write_notes_file "$COMPARE_URL"
|
||||
|
||||
step "Publishing $TAG to Gitea ($REPO_SLUG)"
|
||||
printf ' %sremote%s %s\n' "$C_DIM" "$C_RESET" "$remote_url"
|
||||
printf ' %sapi%s %s\n' "$C_DIM" "$C_RESET" "$API_URL"
|
||||
printf ' %snotes%s %s\n' "$C_DIM" "$C_RESET" "$NOTES_FILE"
|
||||
confirm "Publish $TAG to Gitea?"
|
||||
|
||||
push_branch_and_tag "$REMOTE"
|
||||
|
||||
if is_dry_run; then
|
||||
# Nothing is asked of the server during a dry run.
|
||||
release_status="404"
|
||||
else
|
||||
release_status="$(
|
||||
curl -s -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
"$API_URL/repos/$REPO_SLUG/releases/tags/$TAG" || printf '000'
|
||||
)"
|
||||
fi
|
||||
|
||||
if [ "$release_status" = "200" ]; then
|
||||
release_already_exists "The Gitea release"
|
||||
else
|
||||
body="$(cat "$NOTES_FILE")"
|
||||
payload="$(printf '{"tag_name":"%s","target_commitish":"%s","name":"%s","body":"%s","draft":false,"prerelease":false}' \
|
||||
"$TAG" \
|
||||
"$BRANCH" \
|
||||
"$TAG" \
|
||||
"$(json_escape "$body")")"
|
||||
|
||||
# MARK:create-release
|
||||
if is_dry_run; then
|
||||
detail "[dry run] POST $API_URL/repos/$REPO_SLUG/releases"
|
||||
detail "[dry run] $payload"
|
||||
api_status="201"
|
||||
else
|
||||
response_file="$(mktemp -t cobble-gitea-release.XXXXXX)"
|
||||
api_status="$(
|
||||
printf '%s' "$payload" |
|
||||
curl -s -o "$response_file" -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @- \
|
||||
"$API_URL/repos/$REPO_SLUG/releases" || printf '000'
|
||||
)"
|
||||
fi
|
||||
# END:create-release
|
||||
|
||||
case "$api_status" in
|
||||
201 | 200)
|
||||
ok "Created the Gitea release."
|
||||
;;
|
||||
409)
|
||||
release_already_exists "The Gitea release"
|
||||
;;
|
||||
*)
|
||||
if [ -n "${response_file:-}" ] && [ -s "$response_file" ]; then
|
||||
printf '%s\n' "$(cat "$response_file")" >&2
|
||||
fi
|
||||
if [ -n "${response_file:-}" ]; then
|
||||
rm -f "$response_file"
|
||||
fi
|
||||
fail "Gitea answered HTTP $api_status for the release."
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "${response_file:-}" ]; then
|
||||
rm -f "$response_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_release_summary "$REMOTE" "$BRANCH" "https://${REMOTE_HOST}/${REPO_SLUG}/releases/tag/$TAG"
|
||||
printf '\nThe image is built by the GitHub release; mirror it into the Gitea registry with:\n'
|
||||
printf ' docker buildx imagetools create --tag git.tben.me/moduletrace/cobble:latest \\\n'
|
||||
printf ' ghcr.io/moduletrace/cobble:latest\n'
|
||||
Reference in New Issue
Block a user