fix: generate changelog via Python subprocess for reliability
Local Build and Deploy / deploy (push) Successful in 1m26s

Previous approaches using temp files or env vars failed in Gitea runner.
Python now runs git commands directly via subprocess, reads stdout,
and builds the JSON payload — no intermediate files or shell escaping.
This commit is contained in:
openhands committed 2026-07-20 15:21:12 +02:00
1 parent 80bf3bb0d5
commit 57ef6289ff
1 file changed
+75 -51
+75 -51
View File
@@ -7,7 +7,7 @@ jobs:
release: release:
runs-on: shell runs-on: shell
steps: steps:
- name: Checkout, build & release - name: Build & release
env: env:
VERSION: ${{ gitea.ref_name }} VERSION: ${{ gitea.ref_name }}
GITEA_API: ${{ gitea.api_url }} GITEA_API: ${{ gitea.api_url }}
@@ -15,90 +15,114 @@ jobs:
run: | run: |
set -e set -e
# Use the local checkout (has full history + HEAD)
LOCAL="/var/www/atom-nexst" LOCAL="/var/www/atom-nexst"
# ── Generate changelog ──────────────────────────────────────── # Shallow clone for build
PREV_TAG="$(git -C "$LOCAL" tag --sort=-creatordate | head -2 | tail -1 || echo '')"
echo "=== Debug: VERSION=$VERSION PREV_TAG=$PREV_TAG ==="
if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$VERSION" ]; then
RANGE="${PREV_TAG}..${VERSION}"
echo "=== Range: $RANGE ==="
git -C "$LOCAL" log --oneline --no-decorate "${RANGE}" > /tmp/changelog.txt
else
# First release or only one tag — show all commits up to this tag
echo "=== First release or only one tag, showing all commits ==="
git -C "$LOCAL" log --oneline --no-decorate "${VERSION}" > /tmp/changelog.txt
fi
echo "=== Changelog ==="
cat /tmp/changelog.txt
echo "=== End changelog ==="
# ── Build ─────────────────────────────────────────────────────
SRC="/tmp/epicnext-release" SRC="/tmp/epicnext-release"
rm -rf "$SRC" rm -rf "$SRC"
git clone --branch "${VERSION}" --depth 1 \ git clone --branch "${VERSION}" --depth 1 \
/docker/gitea/gitea/git/repositories/remco/epicnext-cms.git \ /docker/gitea/gitea/git/repositories/remco/epicnext-cms.git \
"$SRC" "$SRC"
cd "$SRC"
if [ -f /var/www/atom-nexst/.env ]; then if [ -f /var/www/atom-nexst/.env ]; then
cp /var/www/atom-nexst/.env "$SRC/.env" cp /var/www/atom-nexst/.env "$SRC/.env"
fi fi
cd "$SRC"
pnpm install --frozen-lockfile pnpm install --frozen-lockfile
pnpm prisma:generate pnpm prisma:generate
export SKIP_ENV_VALIDATION=1 export SKIP_ENV_VALIDATION=1
pnpm build pnpm build
# ── Create release (Python handles JSON properly) ───────────── # Create release — Python reads changelog directly from git
python3 << 'PYEOF' python3 << 'PYEOF'
import json, os, urllib.request import json, os, subprocess, urllib.request, urllib.error
version = os.environ['VERSION'] version = os.environ["VERSION"]
api = os.environ['GITEA_API'] api = os.environ["GITEA_API"]
repo = os.environ['GITEA_REPO'] repo = os.environ["GITEA_REPO"]
token = "${{ secrets.GITEA_TOKEN }}" token = "${{ secrets.GITEA_TOKEN }}"
local = "/var/www/atom-nexst"
with open('/tmp/changelog.txt') as f: # Get previous tag
notes = f.read().strip() tags = subprocess.run(
["git", "-C", local, "tag", "--sort=-creatordate"],
capture_output=True, text=True, check=False,
).stdout.strip().split("\n")
tags = [t for t in tags if t]
if not notes: prev_tag = tags[1] if len(tags) > 1 else ""
notes = "Initial release" print(f"Tags: {tags}")
print(f"Version: {version}, Previous: {prev_tag}")
if prev_tag and prev_tag != version:
r = subprocess.run(
["git", "-C", local, "log", "--oneline", "--no-decorate", f"{prev_tag}..{version}"],
capture_output=True, text=True, check=False,
)
notes_raw = r.stdout.strip()
print(f"Range: {prev_tag}..{version}, stderr: {r.stderr.strip()}")
else:
r = subprocess.run(
["git", "-C", local, "log", "--oneline", "--no-decorate", version],
capture_output=True, text=True, check=False,
)
notes_raw = r.stdout.strip()
print(f"Single tag {version}, stderr: {r.stderr.strip()}")
if not notes_raw:
notes_raw = "Initial release"
print(f"Notes ({len(notes_raw)} chars):")
print(notes_raw)
print("=== End notes ===")
body = f"""## EpicNext-CMS {version} body = f"""## EpicNext-CMS {version}
### Changes ### Changes
{notes} {notes_raw}
--- ---
*Automated release from Gitea Actions* *Automated release from Gitea Actions*
""" """
payload = json.dumps({ payload = json.dumps({
'tag_name': version, "tag_name": version,
'name': version, "name": version,
'body': body, "body": body,
'draft': False, "draft": False,
'prerelease': False, "prerelease": False,
}).encode('utf-8') }).encode("utf-8")
req = urllib.request.Request( def api_call(method, path, data=None):
f'{api}/repos/{repo}/releases', url = f"{api}/repos/{repo}/{path}"
data=payload, print(f"API {method} {url}")
headers={ req = urllib.request.Request(
'Authorization': f'token {token}', url, data=data,
'Content-Type': 'application/json', headers={
}, "Authorization": f"token {token}",
method='POST', "Content-Type": "application/json",
) },
method=method,
)
resp = urllib.request.urlopen(req)
return json.loads(resp.read().decode("utf-8"))
try: try:
resp = urllib.request.urlopen(req) result = api_call("POST", "releases", payload)
result = json.loads(resp.read().decode('utf-8'))
print(f"Release created: {result.get('html_url', result.get('id', '?'))}") print(f"Release created: {result.get('html_url', result.get('id', '?'))}")
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
print(f"API error {e.code}: {e.read().decode('utf-8')}") err_body = e.read().decode("utf-8")
raise print(f"POST error {e.code}: {err_body}")
if e.code == 409:
releases = api_call("GET", "releases")
for rel in releases:
if rel["tag_name"] == version:
result = api_call("PATCH", f"releases/{rel['id']}", payload)
print(f"Release updated: {result.get('html_url', result.get('id', '?'))}")
break
else:
print(f"ERROR: no existing release found for {version}")
raise
else:
raise
PYEOF PYEOF