Improve build workflow with version bump selection and changelog in releases (#104)

## Summary

- Add `version_type` choice input with options: BUMP_PATCH (default), BUMP_MINOR, BUMP_MAJOR, SPECIFIC_VERSION
- Add optional `specific_version` input for explicit version selection
- Include changelog content in Forgejo release body under "What's Changed" section
- Move CHANGELOG.md to repository root (still copied into docs during Quartz build)
- Add CHANGELOG link to docs index page
- Update doc-links script to recognize build-time docs from repo root

## Changes

**Workflow inputs:**
- Previously: single optional `version` string input
- Now: `version_type` choice dropdown (defaults to BUMP_PATCH) + optional `specific_version` for explicit versions

**Release body:**
- Previously: just asset download instructions
- Now: includes "What's Changed" section with changelog entries for this release

**CHANGELOG.md location:**
- Previously: `docs/CHANGELOG.md`
- Now: `CHANGELOG.md` (repo root), copied into docs content during build

## Deployment and Testing

- [ ] Run build workflow with BUMP_PATCH (default)
- [ ] Run build workflow with BUMP_MINOR
- [ ] Verify changelog appears in release body
- [ ] Verify docs site includes CHANGELOG page

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/104
This commit is contained in:
Erich Blume 2026-02-04 08:13:16 -08:00
commit efdd569285
9 changed files with 129 additions and 39 deletions

View file

@ -7,7 +7,7 @@
#
# Usage:
# 1. Go to Actions > Build BlumeOps > Run workflow
# 2. Enter a version tag (e.g., v1.2.0) or leave empty to auto-increment patch
# 2. Select version bump type (patch/minor/major) or choose specific version
# 3. The workflow creates a release with attached artifacts
#
# Documentation asset URL:
@ -18,8 +18,18 @@ name: Build BlumeOps
on:
workflow_dispatch:
inputs:
version:
description: 'Version (e.g., v1.2.0) or empty to auto-increment patch (v_._.+1)'
version_type:
description: 'Version bump type'
required: true
default: 'BUMP_PATCH'
type: choice
options:
- BUMP_PATCH
- BUMP_MINOR
- BUMP_MAJOR
- SPECIFIC_VERSION
specific_version:
description: 'Specific version (only used when version_type is SPECIFIC_VERSION, e.g., v1.2.0)'
required: false
default: ''
type: string
@ -31,30 +41,57 @@ jobs:
- name: Resolve version
id: version
run: |
INPUT_VERSION="${{ inputs.version }}"
VERSION_TYPE="${{ inputs.version_type }}"
SPECIFIC_VERSION="${{ inputs.specific_version }}"
if [ -n "$INPUT_VERSION" ]; then
# User provided a version - validate it
if [[ ! "$INPUT_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Version must be in format vX.Y.Z (e.g., v1.0.0)"
exit 1
fi
VERSION="$INPUT_VERSION"
echo "Using provided version: $VERSION"
else
# Auto-increment patch version from latest release
# Fetch latest release
echo "Fetching latest release..."
LATEST=$(curl -s "https://forge.ops.eblu.me/api/v1/repos/eblume/blumeops/releases/latest" | jq -r '.tag_name // empty' || true)
if [ -z "$LATEST" ]; then
VERSION="v1.0.0"
echo "No previous releases found, starting at: $VERSION"
LATEST="v0.0.0"
echo "No previous releases found, using base version: $LATEST"
else
# Parse vX.Y.Z and increment patch
VERSION=$(echo "$LATEST" | awk -F. '{print $1"."$2"."$3+1}')
echo "Auto-incremented from $LATEST to: $VERSION"
echo "Latest release: $LATEST"
fi
# Parse current version components (strip 'v' prefix)
CURRENT="${LATEST#v}"
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)
case "$VERSION_TYPE" in
BUMP_MAJOR)
VERSION="v$((MAJOR + 1)).0.0"
echo "Bumping major: $LATEST -> $VERSION"
;;
BUMP_MINOR)
VERSION="v${MAJOR}.$((MINOR + 1)).0"
echo "Bumping minor: $LATEST -> $VERSION"
;;
BUMP_PATCH)
VERSION="v${MAJOR}.${MINOR}.$((PATCH + 1))"
echo "Bumping patch: $LATEST -> $VERSION"
;;
SPECIFIC_VERSION)
if [ -z "$SPECIFIC_VERSION" ]; then
echo "Error: specific_version is required when version_type is SPECIFIC_VERSION"
exit 1
fi
# Validate format
if [[ ! "$SPECIFIC_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Version must be in format vX.Y.Z (e.g., v1.0.0)"
exit 1
fi
VERSION="$SPECIFIC_VERSION"
echo "Using specific version: $VERSION"
;;
*)
echo "Error: Unknown version_type: $VERSION_TYPE"
exit 1
;;
esac
# Check if this version already exists
if curl -sf "https://forge.ops.eblu.me/api/v1/repos/eblume/blumeops/releases/tags/$VERSION" > /dev/null 2>&1; then
@ -73,6 +110,7 @@ jobs:
fetch-depth: 0
- name: Build changelog
id: changelog
run: |
VERSION="${{ steps.version.outputs.version }}"
@ -83,11 +121,25 @@ jobs:
echo "Found $FRAGMENTS changelog fragments, building changelog..."
uvx towncrier build --version "$VERSION" --yes
echo "changelog_updated=true" >> "$GITHUB_OUTPUT"
# Extract the changelog section for this release to include in release body
# The section starts with "## [$VERSION]" and ends before the next "## [" or EOF
RELEASE_NOTES=$(awk -v ver="$VERSION" '
/^## \[/ {
if (found) exit
if (index($0, "[" ver "]")) found=1
}
found {print}
' CHANGELOG.md | tail -n +2)
# Save release notes to a file for later use (handles multiline content)
echo "$RELEASE_NOTES" > /tmp/release_notes.md
echo "Release notes extracted for $VERSION"
else
echo "No changelog fragments found, skipping towncrier"
echo "changelog_updated=false" >> "$GITHUB_OUTPUT"
echo "" > /tmp/release_notes.md
fi
id: changelog
- name: Build docs
run: |
@ -110,6 +162,9 @@ jobs:
rm -rf content
cp -r "$GITHUB_WORKSPACE/docs" content
# Copy CHANGELOG.md from repo root into content so it's accessible in docs
cp "$GITHUB_WORKSPACE/CHANGELOG.md" content/
# Build
echo "Building static site..."
npx quartz build
@ -128,20 +183,37 @@ jobs:
run: |
VERSION="${{ steps.version.outputs.version }}"
TARBALL="docs-${VERSION}.tar.gz"
CHANGELOG_UPDATED="${{ steps.changelog.outputs.changelog_updated }}"
echo "Creating release $VERSION..."
# Use Forgejo API to create release (requires authentication)
RELEASE_DATA=$(cat <<EOF
# Build release body with changelog if available
{
"tag_name": "$VERSION",
"name": "BlumeOps $VERSION",
"body": "BlumeOps release $VERSION\n\n## Documentation\n\nDownload \`$TARBALL\` and configure the quartz container with:\n\n\`\`\`\nDOCS_RELEASE_URL=https://forge.ops.eblu.me/eblume/blumeops/releases/download/$VERSION/$TARBALL\n\`\`\`",
"draft": false,
"prerelease": false
}
EOF
)
echo "BlumeOps release $VERSION"
echo ""
if [ "$CHANGELOG_UPDATED" = "true" ] && [ -s /tmp/release_notes.md ]; then
echo "## What's Changed"
echo ""
cat /tmp/release_notes.md
echo ""
fi
echo "## Documentation"
echo ""
echo "Download \`$TARBALL\` and configure the quartz container with:"
echo ""
echo "\`\`\`"
echo "DOCS_RELEASE_URL=https://forge.ops.eblu.me/eblume/blumeops/releases/download/$VERSION/$TARBALL"
echo "\`\`\`"
} > /tmp/release_body.txt
# Use jq to properly escape the body for JSON
RELEASE_DATA=$(jq -n \
--arg tag "$VERSION" \
--arg name "BlumeOps $VERSION" \
--rawfile body /tmp/release_body.txt \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
RELEASE_RESPONSE=$(curl -s \
-X POST \
@ -203,7 +275,7 @@ jobs:
# Stage changelog changes if updated
if [ "$CHANGELOG_UPDATED" = "true" ]; then
git add docs/CHANGELOG.md docs/changelog.d/
git add CHANGELOG.md docs/changelog.d/
fi
# Check if there are changes to commit

View file

@ -1,3 +1,9 @@
---
title: changelog
tags:
- meta
---
# Changelog
All notable changes to BlumeOps are documented in this file.

View file

@ -0,0 +1 @@
Build workflow now supports version bump selection (major/minor/patch) and includes changelog in release body

View file

@ -0,0 +1 @@
Move CHANGELOG.md to repository root (still included in docs build)

View file

@ -0,0 +1 @@
doc-links script now recognizes build-time docs from repo root (e.g., CHANGELOG.md)

View file

@ -15,7 +15,7 @@ How to publish documentation changes to https://docs.ops.eblu.me.
After merging documentation changes to main:
1. Go to **Actions** > **Build BlumeOps** > **Run workflow**
2. Enter a version (e.g., `v1.2.0`) or leave empty to auto-increment
2. Select version bump type (patch/minor/major) or enter a specific version
3. The workflow builds, releases, and deploys automatically
Direct link: https://forge.ops.eblu.me/eblume/blumeops/actions?workflow=build-blumeops.yaml
@ -47,7 +47,7 @@ echo "Add new feature X" > docs/changelog.d/my-feature.feature.md
echo "Fix bug Y" > docs/changelog.d/+fix-bug.bugfix.md
```
Fragments are automatically collected into `docs/CHANGELOG.md` during release.
Fragments are automatically collected into `CHANGELOG.md` (at repo root) during release.
**Fragment types:**
| Type | Directory | Description |

View file

@ -12,3 +12,4 @@ Welcome to the BlumeOps documentation.
- [[reference/index | Reference]] - Technical specifications and service details
- [[how-to/index | How-to]] - Task-oriented instructions for common operations
- [[explanation/index | Explanation]] - Understanding the "why" behind BlumeOps
- [[CHANGELOG]] - Release history and changes

View file

@ -82,6 +82,14 @@ def main() -> int:
else:
ambiguous_filenames.add(filename)
# Special case: files at repo root that are copied into docs during build
# These are valid link targets even though they don't exist in docs/
REPO_ROOT = DOCS_DIR.parent
BUILD_TIME_DOCS = ["CHANGELOG.md"]
for filename in BUILD_TIME_DOCS:
if (REPO_ROOT / filename).exists():
valid_targets.add(Path(filename).stem)
# Collect all broken and ambiguous links
broken_links: list[tuple[str, int, str]] = []
ambiguous_links: list[tuple[str, int, str, list[str]]] = []

View file

@ -3,7 +3,7 @@
[tool.towncrier]
directory = "docs/changelog.d"
filename = "docs/CHANGELOG.md"
filename = "CHANGELOG.md"
package = ""
title_format = "## [{version}] - {project_date}"
issue_format = ""