# Creating a Release

Complete workflow: verify PRs are merged, create a tag, create a release with changelog, upload assets, close the milestone, and optionally notify.

## 1. Verify All PRs Are Merged

Check that all PRs targeting the release branch for this milestone are merged:

```bash
MILESTONE_ID="{milestone_id}"

# List open PRs for the milestone (should be empty)
OPEN_PRS=$(curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=open&milestone=${MILESTONE_ID}" \
  -H "Authorization: token ${FORGEJO_PAT}")
OPEN_COUNT=$(echo "$OPEN_PRS" | jq length)

if [ "$OPEN_COUNT" -gt 0 ]; then
  echo "WARNING: ${OPEN_COUNT} open PRs remaining for this milestone:"
  echo "$OPEN_PRS" | jq -r '.[] | "  #\(.number) \(.title) (\(.user.login))"'
  exit 1
fi

echo "All PRs merged for milestone."
```

## 2. Generate Changelog from Merged PRs

```bash
# List closed/merged PRs for the milestone
MERGED_PRS=$(curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=closed&milestone=${MILESTONE_ID}&limit=50" \
  -H "Authorization: token ${FORGEJO_PAT}")

# Build changelog grouped by label
CHANGELOG=""
CHANGELOG+="## What's Changed\n\n"

# Features
FEATURES=$(echo "$MERGED_PRS" | jq -r '[.[] | select(.merged == true) | select(.labels[]?.name == "type/feature")] | .[] | "- \(.title) (#\(.number)) @\(.user.login)"')
if [ -n "$FEATURES" ]; then
  CHANGELOG+="### Features\n\n${FEATURES}\n\n"
fi

# Bug fixes
FIXES=$(echo "$MERGED_PRS" | jq -r '[.[] | select(.merged == true) | select(.labels[]?.name == "type/bug")] | .[] | "- \(.title) (#\(.number)) @\(.user.login)"')
if [ -n "$FIXES" ]; then
  CHANGELOG+="### Bug Fixes\n\n${FIXES}\n\n"
fi

# Everything else
OTHER=$(echo "$MERGED_PRS" | jq -r '[.[] | select(.merged == true) | select((.labels[]?.name | test("type/feature|type/bug")) | not)] | .[] | "- \(.title) (#\(.number)) @\(.user.login)"')
if [ -n "$OTHER" ]; then
  CHANGELOG+="### Other Changes\n\n${OTHER}\n\n"
fi

echo -e "$CHANGELOG"
```

## 3. Create Tag

Forgejo creates tags implicitly when creating a release. If you need the tag to exist first:

```bash
# Create a lightweight tag via the API
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/tags" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_name": "v1.2.0",
    "target": "{branch_or_sha}",
    "message": "Release v1.2.0"
  }'
```

## 4. Create Release

```bash
RELEASE=$(curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/releases" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_name": "v1.2.0",
    "target_commitish": "{branch_or_sha}",
    "name": "v1.2.0",
    "body": "'"$(echo -e "$CHANGELOG" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')"'",
    "draft": false,
    "prerelease": false
  }')
RELEASE_ID=$(echo "$RELEASE" | jq -r '.id')
echo "Created release ID: ${RELEASE_ID}"
```

## 5. Upload Release Assets

```bash
# Upload a binary
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/releases/${RELEASE_ID}/assets?name={asset_name}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @./{asset_file}

# Upload a checksum file
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/releases/${RELEASE_ID}/assets?name={filename}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: text/plain" \
  --data-binary @./dist/checksums.txt

# List uploaded assets
curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/releases/${RELEASE_ID}/assets" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq '.[] | {name, size, download_count, browser_download_url}'
```

## 6. Close Milestone

```bash
curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/milestones/${MILESTONE_ID}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"state": "closed"}'
```

## 7. Notify via Webhook (Optional)

If you have webhooks configured, they fire automatically on release creation. To trigger a custom notification:

```bash
# List existing webhooks
curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/hooks" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq '.[] | {id, type, url: .config.url, events}'

# Create a webhook for release events (one-time setup)
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/hooks" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "forgejo",
    "config": {
      "url": "https://your-webhook-endpoint.example.com/release-notify",
      "content_type": "json",
      "secret": "{webhook_secret}"
    },
    "events": ["release"],
    "active": true
  }'

# Test a webhook
HOOK_ID="{hook_id}"
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/hooks/${HOOK_ID}/tests" \
  -H "Authorization: token ${FORGEJO_PAT}"
```

## Complete Script

```bash
#!/bin/bash
set -euo pipefail

OWNER="{owner}"
REPO="{repo}"
MILESTONE_ID="{milestone_id}"
TAG="{tag}"
TARGET="{branch}"

echo "=== Release ${TAG} ==="

# 1. Verify no open PRs
OPEN_COUNT=$(curl -s "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls?state=open&milestone=${MILESTONE_ID}" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq length)
if [ "$OPEN_COUNT" -gt 0 ]; then
  echo "ERROR: ${OPEN_COUNT} open PRs remain. Aborting."
  exit 1
fi
echo "All PRs merged."

# 2. Generate changelog
MERGED_PRS=$(curl -s "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls?state=closed&milestone=${MILESTONE_ID}&limit=50" \
  -H "Authorization: token ${FORGEJO_PAT}")
CHANGELOG="## What's Changed\n\n"
CHANGELOG+=$(echo "$MERGED_PRS" | jq -r '[.[] | select(.merged == true)] | .[] | "- \(.title) (#\(.number)) @\(.user.login)"')
echo -e "$CHANGELOG"

# 3. Create release (tag is created automatically)
BODY=$(echo -e "$CHANGELOG" | jq -Rsa .)
RELEASE=$(curl -s -X POST "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/releases" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_name": "'"${TAG}"'",
    "target_commitish": "'"${TARGET}"'",
    "name": "'"${TAG}"'",
    "body": '"${BODY}"',
    "draft": false,
    "prerelease": false
  }')
RELEASE_ID=$(echo "$RELEASE" | jq -r '.id')
echo "Created release ${TAG} (ID: ${RELEASE_ID})"

# 4. Upload assets (if they exist)
if [ -d "./dist" ]; then
  for file in ./dist/*; do
    NAME=$(basename "$file")
    echo "Uploading ${NAME}..."
    curl -s -X POST "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}/assets?name=${NAME}" \
      -H "Authorization: token ${FORGEJO_PAT}" \
      -H "Content-Type: application/octet-stream" \
      --data-binary @"${file}"
  done
fi

# 5. Close milestone
curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/milestones/${MILESTONE_ID}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"state": "closed"}'
echo "Milestone closed."

echo "=== Release ${TAG} complete ==="
echo "URL: ${FORGEJO_URL}/${OWNER}/${REPO}/releases/tag/${TAG}"
```
