#!/bin/bash : <<'COMMENT' Create the release page on Codeberg for a tag 01-release.tag.sh already pushed. ./02-release.publish.sh create it ./02-release.publish.sh --dry-run show what would be sent, send nothing This does NOT publish the module. A Go module is published by its tag being reachable, and `go get codeberg.org/turbo-editors/turbo-core@TAG` already works the moment 01 has run. What this adds is the page a person reads: the notes, and somewhere to link to. There is deliberately no 03 or 04 here. Those build and attach binaries, and a library has none β€” its artefact is the tag. COMMENT # `set -e` is safe here, unlike in the editors' own 02, which uses the # `read -r -d '' DATA` idiom β€” that always exits non-zero by design, so adding # -e there kills the script on its first line. The JSON below is built with jq # instead, which both avoids that trap and escapes the values properly. set -euo pipefail dry_run=false case "${1:-}" in --dry-run) dry_run=true ;; "") ;; *) echo "usage: $0 [--dry-run]" exit 1 ;; esac for tool in curl jq; do if ! command -v "${tool}" >/dev/null; then echo "❌ ${tool} is needed and is not installed" exit 1 fi done if [ ! -f release.env ]; then echo "❌ release.env is missing" echo "πŸ’‘ It holds the version and the repository:" echo ' TAG="v0.1.0"' echo ' ABOUT="The Turbo editor library"' echo ' OWNER="turbo-editors"' echo ' REPO="turbo-core"' exit 1 fi set -o allexport # shellcheck source=/dev/null source release.env # The token lives in its own file so that release.env can be pasted into an # issue without leaking it. Both are gitignored by *.env. if [ -f turbo-core.token.env ]; then # shellcheck source=/dev/null source turbo-core.token.env fi set +o allexport : "${TAG:?TAG is not set in release.env}" : "${OWNER:?OWNER is not set in release.env}" : "${REPO:?REPO is not set in release.env}" ABOUT="${ABOUT:-${TAG}}" if [ -z "${TOKEN:-}" ]; then echo "❌ TOKEN is not set" echo "πŸ’‘ Put it in turbo-core.token.env (gitignored):" echo ' TOKEN=your-codeberg-application-token' echo " Codeberg β†’ Settings β†’ Applications β†’ Generate token, scope: repository" exit 1 fi # A release page for a tag the remote has never seen is a page nobody can use: # the download links 404 and `go get` fails. Ask origin rather than trusting the # local ref, which is exactly the state 01 exists to get out of. # "Not there" and "could not ask" are different answers and must not be # conflated: ls-remote exits 2 when the ref is absent and 128 when it cannot # reach the remote at all. Treating the second as the first refuses a perfectly # good release from any machine with no key loaded. set +e git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1 lookup=$? set -e case ${lookup} in 0) ;; 2) echo "❌ ${TAG} is not on origin" echo "πŸ’‘ Run ./01-release.tag.sh first β€” it pushes the branch, then the tag." exit 1 ;; *) echo "⚠️ cannot reach origin to check that ${TAG} is pushed; carrying on." echo " If it is not, Codeberg will refuse the release below." ;; esac # The body is the notes plus the two lines a reader actually needs. A library's # release page whose only content is its own version number tells nobody how to # use it. # # The links are absolute: a release page is not inside the repository tree, so a # relative path from it 404s. module="$(go list -m)" repo_url="https://codeberg.org/${OWNER}/${REPO}" body="$( cat <<-EOM ${ABOUT} \`\`\`bash go get ${module}@${TAG} \`\`\` Documentation: [English](${repo_url}/src/tag/${TAG}/docs/en/README.md) Β· [FranΓ§ais](${repo_url}/src/tag/${TAG}/docs/fr/README.md) EOM )" # jq builds the JSON, so a quote, a newline or a backtick in ABOUT cannot break # the request β€” which hand-written JSON in a heredoc does silently. payload="$(jq -n \ --arg tag "${TAG}" \ --arg name "${TAG}" \ --arg body "${body}" \ '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')" url="https://codeberg.org/api/v1/repos/${OWNER}/${REPO}/releases" if ${dry_run}; then echo "POST ${url}" echo "${payload}" echo "βœ… dry run: nothing was sent" exit 0 fi echo "Creating the release page for ${TAG} on ${OWNER}/${REPO}…" # The status is separated from the body so the script can say which failure this # is. A bare curl prints the API's JSON and leaves the reader to guess. response="$(mktemp)" trap 'rm -f "${response}"' EXIT status="$(curl -sS -o "${response}" -w '%{http_code}' \ -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ "${url}" \ -d "${payload}")" case "${status}" in 201) echo "βœ… ${TAG} published: $(jq -r '.html_url' "${response}")" ;; 409) echo "❌ a release for ${TAG} already exists" echo "πŸ’‘ Delete it on Codeberg, or bump TAG in release.env. A published" echo " version is not worth moving: consumers pin it and the module proxy" echo " caches what it fetched." exit 1 ;; 401 | 403) echo "❌ ${status}: the token was refused" echo "πŸ’‘ Check TOKEN in turbo-core.token.env, and that its scope covers this repository." exit 1 ;; 404) echo "❌ 404: no repository ${OWNER}/${REPO}" echo "πŸ’‘ Check OWNER and REPO in release.env." exit 1 ;; *) echo "❌ ${status} from Codeberg:" cat "${response}" exit 1 ;; esac