Skip to content

Edit Existing GitHub Releases (One-Time) #2

Edit Existing GitHub Releases (One-Time)

Edit Existing GitHub Releases (One-Time) #2

Workflow file for this run

name: Edit Existing GitHub Releases (One-Time)
on:
workflow_dispatch:
inputs:
start_version:
description: 'Start editing from this version (e.g., 0.1.0)'
required: false
default: '0.1.0' # Adjust this to the earliest version you want to edit
end_version:
description: 'End editing at this version (e.g., 0.21.0). Leave empty for all versions from start_version.'
required: false
default: '' # Leave empty to process all versions from start_version onwards
changelog_commit_ref:
description: 'Commit hash or branch where the full changelog.mdx exists (e.g., master or a specific hash). Defaults to current ref.'
required: false
# IMPORTANT: Use the commit hash where your changelog.mdx has all the correct release notes.
# If your 'master' branch has the complete changelog, 'master' is fine.
# If the files were at a specific older commit, use that hash (e.g., '96ead5ee413f83fc58796f1661791122cf1a7f60')
default: 'master' # Change this if your full changelog is on a different branch or specific commit
jobs:
edit_releases:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout repository (for changelog)
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.changelog_commit_ref }}
fetch-depth: 0 # Get full history for accurate changelog parsing
- name: Install GitHub CLI
run: |
sudo apt-get update
sudo apt-get install -y gh
- name: Set up Node.js for Changelog Parsing
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Create release body parser script
id: create_parser_script
run: |
cat << 'EOF' > parse_changelog.js
const fs = require('fs');
const path = require('path');
const changelogPath = path.join(process.env.GITHUB_WORKSPACE, 'documentation', 'docs', 'pages', 'docs', 'changelog.mdx');
const rawVersion = process.argv[2]; // Version string passed (e.g., "0.1.0", "0.21.0")
// Determine the exact header format from your changelog
// Your changelog uses "# 0.x.x-beta" for most entries.
// For 0.1.0, it's just "# 0.1.0"
let headerToMatch = '';
if (rawVersion === '0.1.0') {
headerToMatch = `# ${rawVersion}`; // Matches exactly for 0.1.0
} else {
headerToMatch = `# ${rawVersion}-beta`; // Matches most other beta versions
}
console.log(`Attempting to parse changelog for header: "${headerToMatch}"`);
try {
const changelogContent = fs.readFileSync(changelogPath, 'utf8');
const releasesSection = changelogContent.split(/^## Releases/m)[1];
if (!releasesSection) {
console.error("Could not find '## Releases' section in changelog.");
process.exit(1);
}
// Regex to find the section for the specific version
// It matches the header and captures everything until the next similar header or end of string
const releasePattern = new RegExp(`^${headerToMatch}\\s*-\\s*\\d+(?:st|nd|rd|th) \\w+ \\d{4}([\\s\\S]*?)(?:\\n# \\d+\\.\\d+\\.\\d+|$|^## |$)`, 'm');
const match = releasesSection.match(releasePattern);
let releaseNotes = "No specific features/bug fixes mentioned in changelog.";
if (match && match[1]) {
releaseNotes = match[1].trim();
// Clean up specific lines that are just headers or blank lines from your template
releaseNotes = releaseNotes
.replace(/^(github branch - https:\/\/github\.com\/joshstevens19\/rindexer\/tree\/release\/.*)$/gm, '')
.replace(/^- (linux|mac|windows) binary - https:\/\/(github|rindexer\.xyz).*$/gm, '')
.replace(/^### Features\n-------------------------------------------------$/gm, '### Features')
.replace(/^### Bug fixes\n-------------------------------------------------$/gm, '### Bug fixes')
.replace(/^### Breaking changes\n-------------------------------------------------$/gm, '### Breaking changes')
.replace(/^-+\s*$/gm, '') // Remove horizontal rules
.replace(/^\s*[\r\n]+/gm, '') // Remove empty lines
.trim();
if (releaseNotes === '') {
releaseNotes = "No specific features/bug fixes mentioned in changelog.";
}
}
// Encode the output to handle multi-line strings correctly in GitHub Actions
const encodedNotes = releaseNotes.replace(/%/g, '%25').replace(/\n/g, '%0A').replace(/\r/g, '%0D');
console.log(`::set-output name=release_body::${encodedNotes}`);
} catch (error) {
console.error(`Error reading or parsing changelog: ${error.message}`);
process.exit(1);
}
EOF
chmod +x parse_changelog.js
- name: Get all existing release tags
id: get_tags
run: |
# Get all tags that start with 'v' and look like versions, then sort them.
# This ensures we only process valid version tags that GitHub created.
ALL_TAGS=$(gh api --paginate "/repos/${{ github.repository }}/tags" --jq '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/^v//' | sort -V | paste -sd ' ' -)
echo "Found tags: $ALL_TAGS"
echo "all_versions=$ALL_TAGS" >> $GITHUB_OUTPUT
- name: Edit Releases
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
START_VERSION: ${{ github.event.inputs.start_version }}
END_VERSION: ${{ github.event.inputs.end_version }}
run: |
#!/bin/bash
set -e
ALL_VERSIONS="${{ steps.get_tags.outputs.all_versions }}"
# Filter versions based on optional inputs
FILTERED_VERSIONS=""
# Function for version comparison using sort -V (semantic versioning)
version_ge() {
local v1="$1"
local v2="$2"
[[ "$v1" = "$v2" ]] || [[ "$(printf '%s\n' "$v1" "$v2" | sort -V | head -n1)" = "$v2" ]]
}
version_le() {
local v1="$1"
local v2="$2"
[[ "$v1" = "$v2" ]] || [[ "$(printf '%s\n' "$v1" "$v2" | sort -V | tail -n1)" = "$v2" ]]
}
for ver in $ALL_VERSIONS; do
if [[ -n "$START_VERSION" ]]; then
if ! version_ge "$ver" "$START_VERSION"; then
continue # Skip if less than start_version
fi
fi
if [[ -n "$END_VERSION" ]]; then
if ! version_le "$ver" "$END_VERSION"; then
continue # Skip if greater than end_version
fi
fi
FILTERED_VERSIONS+=" $ver"
done
if [ -z "$FILTERED_VERSIONS" ]; then
echo "No versions to edit after applying filters. Exiting."
exit 0
fi
echo "Editing versions: $FILTERED_VERSIONS"
for VERSION in $FILTERED_VERSIONS; do
echo "--- Processing version: $VERSION ---"
TAG="v$VERSION"
# Construct the base installation instructions as a multi-line string within the script
INSTALL_INSTRUCTIONS="## Installation
\`\`\`bash
# Latest version
curl -L https://rindexer.xyz/install.sh | bash
# Specific version
curl -L https://rindexer.xyz/install.sh | bash -s -- --version $VERSION
\`\`\`"
# Call Node.js script to get release body.
RAW_CHANGELOG_BODY=$(node parse_changelog.js "$VERSION")
# Decode the URL-encoded string from the Node.js script
CLEANED_CHANGELOG_BODY=$(echo -e "${RAW_CHANGELOG_BODY//%/\\x}" | sed 's/%0A/\n/g; s/%0D/\r/g')
# Combine all parts for the full release body
FULL_RELEASE_BODY="${INSTALL_INSTRUCTIONS}\n\n${CLEANED_CHANGELOG_BODY}\n\nSee full changelog for details: https://github.com/joshstevens19/rindexer/blob/master/documentation/docs/pages/docs/changelog.mdx"
echo "Attempting to edit release $TAG..."
gh release edit "$TAG" \
--prerelease=false \
--notes "$FULL_RELEASE_BODY" \
--target master # Assuming your release tags point to master/main
# --draft=false # Uncomment this if you accidentally created drafts and want to publish them
echo "Successfully edited release $TAG."
done