Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 35 additions & 53 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,88 +1,70 @@
name: release

on:
push:
branches: [main]
workflow_dispatch:
inputs:
bump:
description: "Version bump (auto|patch|minor|major)"
required: true
default: "auto"
type: choice
options: [auto, patch, minor, major]

permissions:
contents: write
pull-requests: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- id: rp
uses: googleapis/release-please-action@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

- if: ${{ steps.rp.outputs.release_created == 'true' }}
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Bump version and update CHANGELOG
id: bump
env:
BUMP: ${{ inputs.bump }}
RELEASE_NOTES_PATH: ${{ runner.temp }}/release-notes.md
run: |
set -euo pipefail
out=$(./scripts/release.sh --bump "$BUMP")
echo "$out"
echo "$out" >> "$GITHUB_OUTPUT"

- if: ${{ steps.rp.outputs.release_created == 'true' }}
uses: kdheepak/panvimdoc@main
- uses: kdheepak/panvimdoc@main
with:
vimdoc: nyan
version: "Neovim >= 0.8.0"
demojify: true
treesitter: true

- if: ${{ steps.rp.outputs.release_created == 'true' }}
name: Commit vimdoc and move tag
- name: Commit, tag, push
env:
TAG: ${{ steps.rp.outputs.tag_name }}
NEXT_VERSION: ${{ steps.bump.outputs.NEXT_VERSION }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if ! git diff --quiet -- doc/; then
git add doc/
git commit -m "chore(release): regenerate vimdoc for ${TAG}"
git push origin HEAD:main
git tag -f "${TAG}"
git push origin "${TAG}" --force
fi

- if: ${{ steps.rp.outputs.release_created == 'true' }}
id: changelog
env:
VERSION: ${{ steps.rp.outputs.version }}
run: |
set -euo pipefail
tmp=$(mktemp)
awk -v ver="$VERSION" '
BEGIN { in_section = 0 }
/^## / {
if (in_section) exit
if ($0 ~ "^## \\[?" ver) { in_section = 1; next }
}
in_section { print }
' CHANGELOG.md > "$tmp"
{
echo "body<<CHANGELOG_EOF"
cat "$tmp"
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
git add version.txt CHANGELOG.md doc/
git commit -m "chore(release): v${NEXT_VERSION}"
git tag "v${NEXT_VERSION}"
git push origin HEAD:main
git push origin "v${NEXT_VERSION}"

- if: ${{ steps.rp.outputs.release_created == 'true' }}
name: Create GitHub release
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.rp.outputs.tag_name }}
BODY: ${{ steps.changelog.outputs.body }}
NEXT_VERSION: ${{ steps.bump.outputs.NEXT_VERSION }}
NOTES_FILE: ${{ steps.bump.outputs.RELEASE_NOTES_PATH }}
run: |
set -euo pipefail
gh release create "${TAG}" \
--title "${TAG}" \
--notes "${BODY}"
gh release create "v${NEXT_VERSION}" \
--title "v${NEXT_VERSION}" \
--notes-file "${NOTES_FILE}"

- if: ${{ steps.rp.outputs.release_created == 'true' }}
name: LuaRocks upload
- name: LuaRocks upload
uses: nvim-neorocks/luarocks-tag-release@v7
env:
LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}
3 changes: 0 additions & 3 deletions .release-please-manifest.json

This file was deleted.

28 changes: 0 additions & 28 deletions release-please-config.json

This file was deleted.

117 changes: 117 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Compute next semver from conventional commits since the last v* tag,
# update version.txt, and prepend a categorised entry to CHANGELOG.md.
# Pure file mutation — does not git add, commit, tag, or push.
#
# Usage:
# scripts/release.sh [--dry-run] [--bump auto|patch|minor|major]
#
# Outputs (for CI consumption):
# prints NEXT_VERSION=x.y.z on stdout
# prints BUMP_KIND=major|minor|patch|none on stdout
# exits 1 if no releasable commits found (and bump=auto)

set -euo pipefail

cd "$(dirname "$0")/.."

DRY_RUN=0
BUMP_MODE="auto"

while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--bump) BUMP_MODE="${2:?--bump requires a value}"; shift ;;
--bump=*) BUMP_MODE="${1#--bump=}" ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
shift
done

case "$BUMP_MODE" in
auto|patch|minor|major) ;;
*) echo "invalid --bump: $BUMP_MODE (expected auto|patch|minor|major)" >&2; exit 2 ;;
esac

current=$(cat version.txt)
prev_tag=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || git rev-list --max-parents=0 HEAD)

if [ "$BUMP_MODE" = "auto" ]; then
bump="none"
while IFS= read -r line; do
case "$line" in
*"BREAKING CHANGE"*|*"!:"*) bump="major" ;;
feat:*|feat\(*\):*) [ "$bump" != "major" ] && bump="minor" ;;
fix:*|fix\(*\):*|perf:*|perf\(*\):*) [ "$bump" = "none" ] && bump="patch" ;;
esac
done < <(git log "${prev_tag}..HEAD" --pretty=format:"%s" --no-merges)
else
bump="$BUMP_MODE"
fi

if [ "$bump" = "none" ]; then
echo "BUMP_KIND=none"
echo "no feat/fix/perf commits since ${prev_tag}; nothing to release" >&2
exit 1
fi

IFS='.' read -r major minor patch <<< "$current"
case "$bump" in
major) next="$((major + 1)).0.0" ;;
minor) next="${major}.$((minor + 1)).0" ;;
patch) next="${major}.${minor}.$((patch + 1))" ;;
esac

repo_url=$(git config --get remote.origin.url | sed -E 's|\.git$||; s|^git@github.com:|https://github.com/|')
today=$(date -u +%Y-%m-%d)

section() {
local title="$1"; shift
local pattern="$1"
local lines
lines=$(git log "${prev_tag}..HEAD" --no-merges --pretty=format:"%h%x09%s" \
| awk -F'\t' -v p="$pattern" '$2 ~ p { sub(/^[a-z]+(\([^)]*\))?!?:[[:space:]]*/, "", $2); print $1 "\t" $2 }')
[ -z "$lines" ] && return
printf '\n### %s\n\n' "$title"
while IFS=$'\t' read -r sha msg; do
printf '* %s ([%s](%s/commit/%s))\n' "$msg" "$sha" "$repo_url" "$sha"
done <<< "$lines"
}

entry=$(
printf '## [%s](%s/compare/v%s...v%s) (%s)\n' "$next" "$repo_url" "$current" "$next" "$today"
section "Features" '^feat[(!:]'
section "Bug Fixes" '^fix[(!:]'
section "Performance" '^perf[(!:]'
section "Reverts" '^revert[(!:]'
section "Code Refactoring" '^refactor[(!:]'
section "Tests" '^test[(!:]'
section "Build System" '^build[(!:]'
section "Continuous Integration" '^ci[(!:]'
section "Documentation" '^docs[(!:]'
)

echo "BUMP_KIND=${bump}"
echo "NEXT_VERSION=${next}"

if [ "$DRY_RUN" = "1" ]; then
echo "--- dry run: changelog entry ---" >&2
printf '%s\n' "$entry" >&2
exit 0
fi

printf '%s\n' "$next" > version.txt

tmp=$(mktemp)
{
head -n 1 CHANGELOG.md
echo
printf '%s\n' "$entry"
tail -n +2 CHANGELOG.md
} > "$tmp"
mv "$tmp" CHANGELOG.md

# Emit changelog body (without leading "## [..](...)" heading) for downstream GH-release notes.
notes_path="${RELEASE_NOTES_PATH:-$(mktemp)}"
printf '%s\n' "$entry" | tail -n +2 > "$notes_path"
echo "RELEASE_NOTES_PATH=${notes_path}"
39 changes: 3 additions & 36 deletions scripts/test_release.sh
Original file line number Diff line number Diff line change
@@ -1,40 +1,7 @@
#!/usr/bin/env bash
# Local simulation of the release-please bump + CHANGELOG generation.
# Does not push, tag, or call gh; prints what the next release would look like.
# Dry run of the release script: prints next version and changelog preview
# without writing version.txt or CHANGELOG.md.
set -euo pipefail

cd "$(dirname "$0")/.."

current=$(cat version.txt)
prev_tag=$(git describe --tags --abbrev=0 2>/dev/null || git rev-list --max-parents=0 HEAD)

# Walk commits since the last tag. Bump rules:
# feat!: / fix!: / BREAKING CHANGE: -> major
# feat: -> minor
# fix: / perf: -> patch
# anything else -> no bump
bump="none"
while IFS= read -r line; do
case "$line" in
*"BREAKING CHANGE"*|*"!:"*) bump="major" ;;
feat:*|feat\(*\):*) [ "$bump" != "major" ] && bump="minor" ;;
fix:*|fix\(*\):*|perf:*|perf\(*\):*) [ "$bump" = "none" ] && bump="patch" ;;
esac
done < <(git log "${prev_tag}..HEAD" --pretty=format:"%s" --no-merges)

IFS='.' read -r major minor patch <<< "$current"
case "$bump" in
major) next="$((major + 1)).0.0" ;;
minor) next="${major}.$((minor + 1)).0" ;;
patch) next="${major}.${minor}.$((patch + 1))" ;;
none) next="(no release — no feat/fix/perf commits since ${prev_tag})" ;;
esac

echo "current: ${current}"
echo "previous: ${prev_tag}"
echo "bump kind: ${bump}"
echo "next: ${next}"
echo
echo "--- changelog preview ---"
git log "${prev_tag}..HEAD" --pretty=format:"- %s" --no-merges || true
echo
exec ./scripts/release.sh --dry-run "$@"
Loading