mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
5365f971ef
Signed-off-by: phernandez <paul@basicmachines.co>
195 lines
7.8 KiB
YAML
195 lines
7.8 KiB
YAML
name: release
|
|
|
|
# Manual trigger: Actions → release → Run workflow. Always runs against main
|
|
# (the workflow validates GITHUB_REF). Steps:
|
|
# 1. Compute the new version from the `version` input (patch/minor/major
|
|
# or explicit semver). The *current* version is read from the latest
|
|
# git tag (`v*.*.*`) — NOT from __init__.py. This is robust to PRs that
|
|
# pre-bump __init__.py: the bump always runs from the last released
|
|
# version, not from whatever the working files happen to say.
|
|
# 2. Update __version__ in __init__.py and version in plugin.yaml to match
|
|
# the new tag — bringing the files in sync if a PR pre-bumped them.
|
|
# 3. Commit as `chore(release): vX.Y.Z`, tag, push to main + push the tag.
|
|
# 4. Publish a GitHub Release. Body is the matching `## [X.Y.Z]` block from
|
|
# CHANGELOG.md when present; otherwise auto-generated release notes.
|
|
#
|
|
# Recommended flow: land a PR that adds a `## [X.Y.Z]` section to CHANGELOG.md
|
|
# first, then run this workflow with the matching version so the release notes
|
|
# are the hand-written changelog instead of commit-message-derived notes.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version bump (`patch`, `minor`, `major`) or explicit semver (`0.3.0`)"
|
|
required: true
|
|
default: "patch"
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release:
|
|
name: Tag and Publish GitHub Release
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Validate trigger is main
|
|
run: |
|
|
if [ "$GITHUB_REF" != "refs/heads/main" ]; then
|
|
echo "::error::release must run against main. Got $GITHUB_REF"
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Compute new version
|
|
id: bump
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION_INPUT="${{ github.event.inputs.version }}"
|
|
|
|
# Current = latest released tag (sort by version, descending; pick
|
|
# the first v*.*.* tag). NOT __init__.py — a PR may have pre-bumped
|
|
# the version files, and we don't want to double-bump on top of
|
|
# that. The bump is computed from the last *released* version.
|
|
LAST_TAG=$(git tag --list 'v*.*.*' --sort=-v:refname | head -1 || true)
|
|
if [ -z "$LAST_TAG" ]; then
|
|
# First release in the repo. Seed with 0.0.0 so a `patch` bump
|
|
# yields v0.0.1, `minor` yields v0.1.0, `major` yields v1.0.0.
|
|
# Explicit semver inputs bypass the seed entirely.
|
|
CURRENT="0.0.0"
|
|
echo "No prior v*.*.* tag found; seeding current=0.0.0"
|
|
else
|
|
CURRENT="${LAST_TAG#v}"
|
|
echo "Latest released tag: $LAST_TAG (current=$CURRENT)"
|
|
fi
|
|
|
|
if [[ "$VERSION_INPUT" =~ ^(patch|minor|major)$ ]]; then
|
|
IFS=. read -r MAJ MIN PAT <<< "$CURRENT"
|
|
case "$VERSION_INPUT" in
|
|
major) MAJ=$((MAJ + 1)); MIN=0; PAT=0 ;;
|
|
minor) MIN=$((MIN + 1)); PAT=0 ;;
|
|
patch) PAT=$((PAT + 1)) ;;
|
|
esac
|
|
NEW="${MAJ}.${MIN}.${PAT}"
|
|
elif [[ "$VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
NEW="$VERSION_INPUT"
|
|
else
|
|
echo "::error::Invalid version input: '$VERSION_INPUT'"
|
|
echo "::error::Use patch|minor|major or explicit X.Y.Z"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$NEW" = "$CURRENT" ]; then
|
|
echo "::error::New version equals last released ($NEW). Pick a different version."
|
|
exit 1
|
|
fi
|
|
|
|
# Sanity check: refuse if __init__.py is already ahead of the
|
|
# version we're about to ship. Catches the "PR bumped to 0.5.0 but
|
|
# workflow was asked for a patch that would land 0.1.8" foot-gun
|
|
# before it overwrites the files.
|
|
FILE_VERSION=$(grep -E '^__version__ = ' __init__.py \
|
|
| sed -E 's/^__version__ = "([^"]+)".*/\1/')
|
|
if [ -n "$FILE_VERSION" ] && [ "$FILE_VERSION" != "$CURRENT" ] && [ "$FILE_VERSION" != "$NEW" ]; then
|
|
echo "::error::__init__.py reports version $FILE_VERSION, but the release would ship $NEW (last tag: $CURRENT)."
|
|
echo "::error::Reconcile by picking a version input that matches __init__.py, or roll __init__.py back to $CURRENT."
|
|
exit 1
|
|
fi
|
|
|
|
echo "New version: $NEW"
|
|
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
|
|
echo "version=$NEW" >> "$GITHUB_OUTPUT"
|
|
echo "tag=v$NEW" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Refuse if tag already exists
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${{ steps.bump.outputs.tag }}"
|
|
if git rev-parse --verify "refs/tags/$TAG" >/dev/null 2>&1; then
|
|
echo "::error::Tag $TAG already exists locally. Pick a different version."
|
|
exit 1
|
|
fi
|
|
if git ls-remote --tags origin "$TAG" | grep -q "refs/tags/$TAG$"; then
|
|
echo "::error::Tag $TAG already exists on origin. Pick a different version."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Update version files
|
|
run: |
|
|
set -euo pipefail
|
|
NEW="${{ steps.bump.outputs.version }}"
|
|
sed -i -E "s/^__version__ = \"[^\"]+\"/__version__ = \"${NEW}\"/" __init__.py
|
|
sed -i -E "s/^version: .*/version: ${NEW}/" plugin.yaml
|
|
|
|
# Verify both files changed and that the new version is present.
|
|
grep -q "^__version__ = \"${NEW}\"" __init__.py
|
|
grep -q "^version: ${NEW}$" plugin.yaml
|
|
|
|
echo "--- diff ---"
|
|
git --no-pager diff -- __init__.py plugin.yaml
|
|
|
|
- name: Configure Git identity
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
|
|
- name: Commit and tag
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${{ steps.bump.outputs.tag }}"
|
|
git add __init__.py plugin.yaml
|
|
if git diff --cached --quiet; then
|
|
# PR already bumped the files to the target version. Tag the
|
|
# existing HEAD rather than creating an empty release commit.
|
|
echo "Version files already at ${TAG}; tagging current HEAD."
|
|
else
|
|
git commit -m "chore(release): ${TAG}"
|
|
fi
|
|
git tag -a "${TAG}" -m "${TAG}"
|
|
|
|
- name: Push commit and tag
|
|
run: |
|
|
set -euo pipefail
|
|
# HEAD push is a no-op when nothing was committed in this run.
|
|
git push origin HEAD:main
|
|
git push origin "${{ steps.bump.outputs.tag }}"
|
|
|
|
- name: Extract CHANGELOG section for this version
|
|
id: changelog
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${{ steps.bump.outputs.version }}"
|
|
# Pull lines between `## [VERSION]` and the next `## [` heading.
|
|
SECTION=$(awk -v ver="$VERSION" '
|
|
$0 ~ "^## \\[" ver "\\]" { found=1; next }
|
|
found && /^## \[/ { exit }
|
|
found { print }
|
|
' CHANGELOG.md)
|
|
if [ -z "$SECTION" ]; then
|
|
echo "::warning::No CHANGELOG.md section found for v${VERSION} — falling back to auto-generated release notes."
|
|
echo "has_section=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "has_section=true" >> "$GITHUB_OUTPUT"
|
|
{
|
|
echo 'body<<EOF_CHANGELOG'
|
|
echo "$SECTION"
|
|
echo 'EOF_CHANGELOG'
|
|
} >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Create GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ steps.bump.outputs.tag }}
|
|
name: ${{ steps.bump.outputs.tag }}
|
|
body: ${{ steps.changelog.outputs.body }}
|
|
generate_release_notes: ${{ steps.changelog.outputs.has_section == 'false' }}
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|