CI publishes the release itself
ci/woodpecker/push/woodpecker Pipeline is pending
ci/woodpecker/manual/woodpecker Pipeline was successful

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) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 17:12:16 +02:00
co-authored by Claude Opus 5
parent 526c67b069
commit 2f725fdd14
2 changed files with 29 additions and 4 deletions
+3
View File
@@ -23,6 +23,9 @@ steps:
image: *node_image image: *node_image
commands: commands:
- node --version - 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 ci
- npm run typecheck - npm run typecheck
- npm run lint - npm run lint
+26 -4
View File
@@ -9,14 +9,17 @@
# GITEA_TOKEN=… scripts/ci-upload.sh every package in dist/ # GITEA_TOKEN=… scripts/ci-upload.sh every package in dist/
# GITEA_TOKEN=… scripts/ci-upload.sh dist/one.deb just these # 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 # Creates the release when the tag has none, with RELEASE_NOTES.md as its body. That is
# builds and publishes the macOS package, and that is what creates the release. # 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 set -eu
FORGE="${FORGE_API:-https://git.teletypegames.org/api/v1}" FORGE="${FORGE_API:-https://git.teletypegames.org/api/v1}"
REPO="${REPO:-${CI_REPO:-}}" REPO="${REPO:-${CI_REPO:-}}"
TAG="${TAG:-${CI_COMMIT_TAG:-}}" TAG="${TAG:-${CI_COMMIT_TAG:-}}"
DIST="${DIST:-dist}" DIST="${DIST:-dist}"
NOTES="${NOTES:-RELEASE_NOTES.md}"
say() { echo "[ci-upload] $*"; } say() { echo "[ci-upload] $*"; }
die() { echo "[ci-upload] error: $*" >&2; exit 1; } die() { echo "[ci-upload] error: $*" >&2; exit 1; }
@@ -46,8 +49,27 @@ fi
[ -s "$LIST" ] || die "no Linux or Windows packages in $DIST" [ -s "$LIST" ] || die "no Linux or Windows packages in $DIST"
say "$REPO $TAG" 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 while IFS= read -r asset; do
[ -n "$asset" ] || continue [ -n "$asset" ] || continue