From 2f725fdd1465e1ff73c0b3f5719bf88f98952df9 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Tue, 18 Aug 2026 17:12:16 +0200 Subject: [PATCH] CI publishes the release itself The flow is now: a vX.Y.Z tag starts the pipeline, the pipeline creates the release with the Linux and Windows packages in it, and the macOS package is pushed on top from a Mac with make release. scripts/ci-upload.sh therefore creates the release when the tag has none, taking its body from RELEASE_NOTES.md, instead of requiring one to exist. Also carries a one-off diagnostic in the check step: whether Woodpecker hands steps a forge credential of their own. If it does, the release step needs no secret. Co-Authored-By: Claude Opus 5 (1M context) --- .woodpecker.yaml | 3 +++ scripts/ci-upload.sh | 30 ++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index b324ff0..eb439ab 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -23,6 +23,9 @@ steps: image: *node_image commands: - node --version + # One-off diagnostic: does Woodpecker hand steps a forge credential of their own? + # If it does, the release step needs no secret at all. + - 'echo "netrc user: ${CI_NETRC_USERNAME:+present}, password: ${CI_NETRC_PASSWORD:+present}"' - npm ci - npm run typecheck - npm run lint diff --git a/scripts/ci-upload.sh b/scripts/ci-upload.sh index f81f285..0a202ed 100755 --- a/scripts/ci-upload.sh +++ b/scripts/ci-upload.sh @@ -9,14 +9,17 @@ # GITEA_TOKEN=… scripts/ci-upload.sh every package in dist/ # GITEA_TOKEN=… scripts/ci-upload.sh dist/one.deb just these # -# Assumes the release for the tag exists. It does: the tag is cut on the machine that -# builds and publishes the macOS package, and that is what creates the release. +# Creates the release when the tag has none, with RELEASE_NOTES.md as its body. That is +# the flow: a `vX.Y.Z` tag starts this pipeline, which publishes the release with the +# Linux and Windows packages in it, and the macOS package is pushed on top afterwards by +# `make release` from a Mac. set -eu FORGE="${FORGE_API:-https://git.teletypegames.org/api/v1}" REPO="${REPO:-${CI_REPO:-}}" TAG="${TAG:-${CI_COMMIT_TAG:-}}" DIST="${DIST:-dist}" +NOTES="${NOTES:-RELEASE_NOTES.md}" say() { echo "[ci-upload] $*"; } die() { echo "[ci-upload] error: $*" >&2; exit 1; } @@ -46,8 +49,27 @@ fi [ -s "$LIST" ] || die "no Linux or Windows packages in $DIST" say "$REPO $TAG" -release_id="$(api GET "/repos/$REPO/releases/tags/$TAG" | jq -r '.id // empty')" -[ -n "$release_id" ] || die "no release for $TAG — cut the release first, then re-run this build" + +# `curl -f` fails on the 404 a missing release answers, so the lookup is allowed to +# fail and judged by what came back rather than by its exit status. +release_id="$(curl -sS -H "Authorization: token $GITEA_TOKEN" \ + "$FORGE/repos/$REPO/releases/tags/$TAG" | jq -r '.id // empty')" + +if [ -z "$release_id" ]; then + say "no release for $TAG yet — creating it" + title="$(jq -r '(.productName // .name) + " " + (.version)' package.json)" + notes='' + [ -f "$NOTES" ] && notes="$(cat "$NOTES")" + # The body goes through jq rather than string concatenation: release notes are + # markdown with quotes and newlines in them. + payload="$(jq -n --arg tag "$TAG" --arg title "$title" --arg body "$notes" \ + '{tag_name: $tag, name: $title, body: $body, draft: false, prerelease: false}')" + release_id="$(api POST "/repos/$REPO/releases" \ + -H 'Content-Type: application/json' -d "$payload" | jq -r '.id // empty')" + [ -n "$release_id" ] || die "the release for $TAG could not be created" +else + say "the release already exists" +fi while IFS= read -r asset; do [ -n "$asset" ] || continue