Files
Dmitriy Fofanov c4c5b6da25
Релиз: архив исходников / Упаковка и публикация релиза (push) Successful in 4s
fix(release): Gitea 1.22 API использует name/body вместо title/note
Корневая причина всех пустых релизов: Gitea 1.22.1 API
принимает поля 'name'/'body' для POST и PATCH,
а 'title'/'note' молча игнорируются.

- POST CreateRelease: title/note  name/body
- PATCH EditRelease: title/note  name/body
- Добавлено логирование ответа PATCH для диагностики
- concurrency group предотвращает параллельные run'ы
2026-02-25 16:52:34 +03:00

417 lines
17 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: "Релиз: архив исходников"
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Тег релиза (например: v2.2.0)"
required: true
permissions:
contents: write
concurrency:
group: release-${{ github.event.inputs.tag || github.ref_name }}
cancel-in-progress: true
env:
DIST_DIR: dist
jobs:
release:
name: "Упаковка и публикация релиза"
runs-on: native
steps:
- name: "Получение исходного кода"
shell: bash
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
TOKEN="${GIT_TOKEN:-$GITEA_TOKEN}"
SERVER="${{ github.server_url }}"
REPO="${{ github.repository }}"
HOST="${SERVER#https://}"
HOST="${HOST#http://}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${GITHUB_REF_NAME}"
fi
if [ -z "${TAG}" ]; then
echo "ОШИБКА: не удалось определить тег релиза."
exit 1
fi
echo ">>> Клонирование ${REPO} (тег: ${TAG})"
if [ -n "${TOKEN}" ]; then
if [ "${HOST}" = "github.com" ]; then
CLONE_URL="https://x-access-token:${TOKEN}@${HOST}/${REPO}.git"
else
CLONE_URL="https://${TOKEN}@${HOST}/${REPO}.git"
fi
else
CLONE_URL="${SERVER}/${REPO}.git"
fi
# Check if tag exists on the remote
TAG_EXISTS=false
if git ls-remote --exit-code --tags "${CLONE_URL}" "refs/tags/${TAG}" >/dev/null 2>&1; then
TAG_EXISTS=true
fi
if [ "${TAG_EXISTS}" = "true" ]; then
echo ">>> Тег ${TAG} найден, клонирование..."
git clone --branch "${TAG}" --depth 200 "${CLONE_URL}" . 2>&1 | grep -F -v "${TOKEN:-}" || true
else
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
if [ -z "${DEFAULT_BRANCH}" ] || [ "${DEFAULT_BRANCH}" = "null" ]; then
DEFAULT_BRANCH="master"
fi
echo ">>> Тег ${TAG} не найден, клонирование ветки ${DEFAULT_BRANCH}..."
git clone --branch "${DEFAULT_BRANCH}" --depth 200 "${CLONE_URL}" . 2>&1 | grep -F -v "${TOKEN:-}" || true
fi
# Fetch full tag history for proper prev_tag detection
git fetch --tags --force 2>/dev/null || true
git fetch --deepen=500 2>/dev/null || true
# If tag doesn't exist locally, create and push it
if [ "${TAG_EXISTS}" = "false" ] && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo ">>> Создание тега ${TAG} на текущем коммите ($(git rev-parse --short HEAD))..."
git tag "${TAG}"
git push origin "${TAG}" 2>&1 | grep -F -v "${TOKEN:-}" || true
echo ">>> Тег ${TAG} создан и отправлен"
fi
echo "RELEASE_TAG=${TAG}" >> "$GITHUB_ENV"
echo ">>> Исходный код получен: $(git log --oneline -1 2>/dev/null || echo 'N/A')"
- name: "Подготовка метаданных релиза"
id: meta
shell: bash
run: |
set -euo pipefail
TAG="${RELEASE_TAG}"
VERSION="${TAG#v}"
PREV_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "prev_tag=${PREV_TAG}" >> "$GITHUB_OUTPUT"
echo "══════════════════════════════════════"
echo " Тег: ${TAG}"
echo " Версия: ${VERSION}"
echo " Пред. тег: ${PREV_TAG:-<первый релиз>}"
echo " Коммит: ${GITHUB_SHA:0:8}"
echo "══════════════════════════════════════"
- name: "Создание архивов исходного кода"
shell: bash
run: |
set -euo pipefail
TAG="${{ steps.meta.outputs.tag }}"
REPO_NAME="${GITHUB_REPOSITORY##*/}"
ARCHIVE_BASE="${REPO_NAME}-${TAG}"
rm -rf .release-work "${DIST_DIR}"
mkdir -p .release-work "${DIST_DIR}"
if command -v rsync >/dev/null 2>&1; then
rsync -a \
--exclude '.git/' \
--exclude '.src-repo/' \
--exclude '.wiki-repo/' \
--exclude '.release-work/' \
--exclude "${DIST_DIR}/" \
./ ".release-work/${ARCHIVE_BASE}/"
else
cp -a . ".release-work/${ARCHIVE_BASE}"
rm -rf \
".release-work/${ARCHIVE_BASE}/.git" \
".release-work/${ARCHIVE_BASE}/.src-repo" \
".release-work/${ARCHIVE_BASE}/.wiki-repo" \
".release-work/${ARCHIVE_BASE}/.release-work" \
".release-work/${ARCHIVE_BASE}/${DIST_DIR}"
fi
tar -czf "${DIST_DIR}/${ARCHIVE_BASE}.tar.gz" -C .release-work "${ARCHIVE_BASE}"
if command -v zip >/dev/null 2>&1; then
(
cd .release-work
zip -qr "../${DIST_DIR}/${ARCHIVE_BASE}.zip" "${ARCHIVE_BASE}"
)
else
ARCHIVE_BASE="${ARCHIVE_BASE}" DIST_DIR="${DIST_DIR}" python3 - <<'PY'
import os
import zipfile
base = os.environ["ARCHIVE_BASE"]
src = os.path.join(".release-work", base)
dst = os.path.join(os.environ["DIST_DIR"], f"{base}.zip")
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zf:
for root, _, files in os.walk(src):
for file_name in files:
full = os.path.join(root, file_name)
arc = os.path.relpath(full, ".release-work")
zf.write(full, arc)
PY
fi
sha256sum "${DIST_DIR}"/* > "${DIST_DIR}/checksums-sha256.txt"
echo "ARCHIVE_BASE=${ARCHIVE_BASE}" >> "$GITHUB_ENV"
echo ">>> Артефакты релиза:"
ls -lh "${DIST_DIR}"
- name: "Генерация описания релиза"
shell: bash
run: |
set -euo pipefail
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
PREV_TAG="${{ steps.meta.outputs.prev_tag }}"
DATE_UTC=$(date -u '+%Y-%m-%d %H:%M UTC')
CHANGELOG_FILE=""
if [ -f "wiki/Changelog.md" ]; then
CHANGELOG_FILE="wiki/Changelog.md"
elif [ -f "docs/ru/CHANGELOG.md" ]; then
CHANGELOG_FILE="docs/ru/CHANGELOG.md"
elif [ -f "docs/en/CHANGELOG_EN.md" ]; then
CHANGELOG_FILE="docs/en/CHANGELOG_EN.md"
fi
CHANGELOG_SECTION=""
if [ -n "${CHANGELOG_FILE}" ]; then
# Поддерживаем оба формата заголовков: "## [VERSION]" и "### [VERSION] - DATE"
CHANGELOG_SECTION=$(awk -v ver="${VERSION}" '
$0 ~ "^#{2,3} \\[" ver "\\]" { in_section=1; next }
/^#{2,3} \[/ && in_section { exit }
in_section { print }
' "${CHANGELOG_FILE}")
fi
COMMITS=""
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
if [ -n "${PREV_TAG}" ]; then
COMMITS=$(git log --pretty=format:'- %s (%h)' "${PREV_TAG}..${TAG}" --no-merges | head -50)
else
COMMITS=$(git log --pretty=format:'- %s (%h)' --no-merges | head -50)
fi
else
echo ">>> Предупреждение: .git недоступен, список коммитов будет пропущен"
fi
{
echo "## Release ${TAG}"
echo ""
echo "Релиз скриптового проекта без компиляции бинарников."
echo ""
echo "### Что входит в релиз"
echo "- Исходный код в форматах: ${ARCHIVE_BASE}.tar.gz, ${ARCHIVE_BASE}.zip"
echo "- Контрольные суммы: checksums-sha256.txt"
echo ""
if [ -n "${CHANGELOG_SECTION}" ]; then
echo "### Что сделано (из CHANGELOG)"
echo "${CHANGELOG_SECTION}"
echo ""
fi
echo "### Коммиты релиза"
if [ -n "${COMMITS}" ]; then
echo "${COMMITS}"
else
echo "- Недоступно (runner без .git)"
fi
echo ""
echo "---"
echo ""
echo "- Тег: ${TAG}"
echo "- Коммит: ${GITHUB_SHA}"
echo "- Дата (UTC): ${DATE_UTC}"
} > /tmp/release_body.md
echo ">>> Сформировано описание релиза: /tmp/release_body.md"
- name: "Создание релиза и загрузка архивов"
shell: bash
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
TOKEN="${GIT_TOKEN:-$GITEA_TOKEN}"
if [ -z "${TOKEN}" ]; then
echo "ОШИБКА: не задан секрет GIT_TOKEN (или GITEA_TOKEN)."
exit 1
fi
SERVER="${{ github.server_url }}"
REPO="${{ github.repository }}"
HOST="${SERVER#https://}"
HOST="${HOST#http://}"
TAG="${{ steps.meta.outputs.tag }}"
# Set up API URLs and auth header early (needed for tag check below)
if [ "${HOST}" = "github.com" ]; then
CREATE_URL="https://api.github.com/repos/${REPO}/releases"
AUTH_HEADER="Authorization: Bearer ${TOKEN}"
EXTRA_HEADERS=(-H "Accept: application/vnd.github+json")
else
CREATE_URL="${SERVER}/api/v1/repos/${REPO}/releases"
AUTH_HEADER="Authorization: token ${TOKEN}"
EXTRA_HEADERS=()
fi
TARGET_COMMITISH="${RELEASE_REF:-${{ github.event.repository.default_branch }}}"
if [ -z "${TARGET_COMMITISH}" ] || [ "${TARGET_COMMITISH}" = "null" ]; then
TARGET_COMMITISH="master"
fi
if [ -n "${GITHUB_SHA:-}" ]; then
TARGET_COMMITISH="${GITHUB_SHA}"
fi
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
TARGET_COMMITISH="$(git rev-list -n 1 "${TAG}" 2>/dev/null || echo "${TARGET_COMMITISH}")"
fi
# Ensure the tag exists on the remote (Gitea requires it before creating a release)
if [ "${HOST}" != "github.com" ]; then
TAG_HTTP_CODE=$(curl -sS -o /dev/null -w '%{http_code}' \
-H "${AUTH_HEADER}" \
"${SERVER}/api/v1/repos/${REPO}/tags/${TAG}")
if [ "${TAG_HTTP_CODE}" != "200" ]; then
echo ">>> Тег ${TAG} не найден на сервере (HTTP ${TAG_HTTP_CODE}), создание через API..."
TAG_CREATE_RESP=$(curl -sS -X POST \
-H "${AUTH_HEADER}" \
-H "Content-Type: application/json" \
"${SERVER}/api/v1/repos/${REPO}/tags" \
-d "{\"tag_name\":\"${TAG}\",\"target\":\"${TARGET_COMMITISH}\"}")
echo ">>> Ответ создания тега: ${TAG_CREATE_RESP}"
else
echo ">>> Тег ${TAG} существует на сервере"
fi
fi
BODY_JSON=$(python3 - <<'PY'
import json
with open('/tmp/release_body.md', 'r', encoding='utf-8') as f:
print(json.dumps(f.read(), ensure_ascii=False))
PY
)
RELEASE_TITLE="${TAG}"
if [ "${HOST}" = "github.com" ]; then
RELEASE_PAYLOAD="{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${TARGET_COMMITISH}\",\"name\":\"${RELEASE_TITLE}\",\"body\":${BODY_JSON},\"draft\":false,\"prerelease\":false}"
else
RELEASE_PAYLOAD="{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${TARGET_COMMITISH}\",\"name\":\"${RELEASE_TITLE}\",\"body\":${BODY_JSON},\"draft\":false,\"prerelease\":false}"
fi
echo ">>> Создание релиза для тега ${TAG}..."
RELEASE_JSON=$(curl -sS -X POST \
-H "${AUTH_HEADER}" \
-H "Content-Type: application/json" \
"${EXTRA_HEADERS[@]}" \
"${CREATE_URL}" \
-d "${RELEASE_PAYLOAD}")
if ! printf '%s' "${RELEASE_JSON}" | python3 -c "import sys, json; j=json.load(sys.stdin); print(j.get('id',''));" >/tmp/release_id.txt 2>/dev/null; then
echo "ОШИБКА: API вернул не-JSON при создании релиза"
echo "Ответ: ${RELEASE_JSON}"
exit 1
fi
RELEASE_ID=$(cat /tmp/release_id.txt)
rm -f /tmp/release_id.txt
# If release already exists (e.g. Gitea auto-created it from tag push),
# find it by tag
if [ -z "${RELEASE_ID}" ] && [ "${HOST}" != "github.com" ]; then
echo ">>> Релиз не создан (возможно уже существует), ищем по тегу ${TAG}..."
RELEASE_JSON=$(curl -sS \
-H "${AUTH_HEADER}" \
"${SERVER}/api/v1/repos/${REPO}/releases/tags/${TAG}")
if printf '%s' "${RELEASE_JSON}" | python3 -c "import sys, json; j=json.load(sys.stdin); print(j.get('id',''));" >/tmp/release_id.txt 2>/dev/null; then
RELEASE_ID=$(cat /tmp/release_id.txt)
fi
rm -f /tmp/release_id.txt
fi
if [ -z "${RELEASE_ID}" ]; then
echo "ОШИБКА: не удалось получить ID релиза"
echo "${RELEASE_JSON}"
exit 1
fi
# Always PATCH the release to guarantee name + body are set
# (handles race condition when parallel run creates empty release)
echo ">>> Обновление релиза (ID: ${RELEASE_ID}) — name + body..."
PATCH_PAYLOAD="{\"name\":\"${RELEASE_TITLE}\",\"body\":${BODY_JSON}}"
if [ "${HOST}" = "github.com" ]; then
PATCH_URL="https://api.github.com/repos/${REPO}/releases/${RELEASE_ID}"
else
PATCH_URL="${SERVER}/api/v1/repos/${REPO}/releases/${RELEASE_ID}"
fi
PATCH_RESP=$(curl -sS -X PATCH \
-H "${AUTH_HEADER}" \
-H "Content-Type: application/json" \
"${EXTRA_HEADERS[@]}" \
"${PATCH_URL}" \
-d "${PATCH_PAYLOAD}")
PATCH_NAME=$(printf '%s' "${PATCH_RESP}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('name',''))" 2>/dev/null || echo '')
echo ">>> Релиз обновлён (name: ${PATCH_NAME})"
if [ "${HOST}" = "github.com" ]; then
UPLOAD_BASE=$(printf '%s' "${RELEASE_JSON}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('upload_url','').split('{')[0])")
RELEASE_URL="https://github.com/${REPO}/releases/tag/${TAG}"
else
UPLOAD_BASE="${SERVER}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets"
RELEASE_URL="${SERVER}/${REPO}/releases/tag/${TAG}"
fi
echo ">>> Загрузка файлов в релиз ${TAG}"
for file in "${DIST_DIR}"/*; do
[ -f "${file}" ] || continue
name=$(basename "${file}")
if [ "${HOST}" = "github.com" ]; then
curl -sf -X POST \
-H "${AUTH_HEADER}" \
-H "Content-Type: application/octet-stream" \
"${EXTRA_HEADERS[@]}" \
--data-binary @"${file}" \
"${UPLOAD_BASE}?name=${name}" >/dev/null
else
curl -sf -X POST \
-H "${AUTH_HEADER}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${file}" \
"${UPLOAD_BASE}?name=${name}" >/dev/null
fi
echo " ✓ ${name}"
done
echo ""
echo ">>> Релиз опубликован: ${RELEASE_URL}"