From e670963b69dd64aa93fc7d3619af51a084e5fece Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 14:55:48 -0600 Subject: [PATCH 1/9] chore: add shared renovate config with version bumping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extends shared Unstructured renovate config for security-only dependency updates - Includes version bumping script for automatic version and CHANGELOG updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- renovate.json5 | 22 ++++ scripts/renovate-security-bump.sh | 202 ++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 renovate.json5 create mode 100755 scripts/renovate-security-bump.sh diff --git a/renovate.json5 b/renovate.json5 new file mode 100644 index 000000000..c54fe3e62 --- /dev/null +++ b/renovate.json5 @@ -0,0 +1,22 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>unstructured-io/renovate-config" + ], + "packageRules": [ + { + "matchDatasources": ["pypi"], + "matchIsVulnerabilityAlert": true, + "postUpgradeTasks": { + "commands": [ + "VERSION_FILE=prepline_general/api/__version__.py bash scripts/renovate-security-bump.sh" + ], + "fileFilters": [ + "prepline_general/api/__version__.py", + "CHANGELOG.md" + ], + "executionMode": "branch" + } + } + ] +} diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh new file mode 100755 index 000000000..fe9fe0dac --- /dev/null +++ b/scripts/renovate-security-bump.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Shared script for Renovate to bump version and update CHANGELOG +# Can be downloaded and executed in any repo with version and changelog files +# Auto-detects changed dependencies from git diff + +# Use current working directory as repo root (where Renovate executes the script) +# Override these via environment variables if your repo has different paths +REPO_ROOT="${REPO_ROOT:-$(pwd)}" +VERSION_FILE="${VERSION_FILE:-$REPO_ROOT/unstructured/__version__.py}" +CHANGELOG_FILE="${CHANGELOG_FILE:-$REPO_ROOT/CHANGELOG.md}" + +echo "=== Renovate Security Version Bump ===" + +# Read current version from __version__.py +CURRENT_VERSION=$(grep -o -E "(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-dev[0-9]+)?" "$VERSION_FILE") +echo "Current version: $CURRENT_VERSION" + +# Determine release version based on current version format +if [[ "$CURRENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev.*)?$ ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]}" + DEV_SUFFIX="${BASH_REMATCH[4]}" + + if [[ -n "$DEV_SUFFIX" ]]; then + # Strip -dev suffix to release current version + RELEASE_VERSION="$MAJOR.$MINOR.$PATCH" + echo "Stripping dev suffix: $CURRENT_VERSION → $RELEASE_VERSION" + else + # Already a release version, bump to next patch + NEW_PATCH=$((PATCH + 1)) + RELEASE_VERSION="$MAJOR.$MINOR.$NEW_PATCH" + echo "Bumping patch version: $CURRENT_VERSION → $RELEASE_VERSION" + fi +else + echo "Error: Could not parse version: $CURRENT_VERSION" + exit 1 +fi + +# Update __version__.py +echo "Updating $VERSION_FILE to version $RELEASE_VERSION" + +# Detect quote style used in the file +if grep -q "__version__ = ['\"]" "$VERSION_FILE"; then + if grep -q "__version__ = \"" "$VERSION_FILE"; then + # Double quotes + sed -i.bak -E "s/__version__ = \"[^\"]+\"/__version__ = \"$RELEASE_VERSION\"/" "$VERSION_FILE" + else + # Single quotes + sed -i.bak -E "s/__version__ = '[^']+'/__version__ = '$RELEASE_VERSION'/" "$VERSION_FILE" + fi +else + echo "Error: Could not detect quote style in $VERSION_FILE" + exit 1 +fi + +# Verify the update succeeded +if ! grep -q "__version__ = ['\"]$RELEASE_VERSION['\"]" "$VERSION_FILE"; then + echo "Error: Failed to update version in $VERSION_FILE" + exit 1 +fi + +rm -f "$VERSION_FILE.bak" + +# Detect changed packages from git diff (best effort, not critical) +echo "Detecting changed dependencies..." +CHANGED_PACKAGES=$(git diff --cached requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) + +if [ -z "$CHANGED_PACKAGES" ]; then + # Try without --cached + CHANGED_PACKAGES=$(git diff requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) +fi + +# Build changelog entry (generic for now, can be manually edited) +if [ -n "$CHANGED_PACKAGES" ]; then + PACKAGE_COUNT=$(echo "$CHANGED_PACKAGES" | wc -l | tr -d ' ') + echo "Found $PACKAGE_COUNT changed package(s)" + CHANGELOG_ENTRY="- **Security update**: Bumped dependencies to address security vulnerabilities" +else + echo "Could not auto-detect packages, using generic entry" + CHANGELOG_ENTRY="- **Security update**: Bumped dependencies to address security vulnerabilities" +fi +echo "Changelog entry: $CHANGELOG_ENTRY" + +# Update CHANGELOG.md +echo "Updating CHANGELOG..." + +# Only look for -dev version to rename if CURRENT_VERSION had -dev suffix +if [[ -n "$DEV_SUFFIX" ]]; then + # Look for -dev version header in CHANGELOG that matches our version + DEV_VERSION_HEADER=$(grep -m 1 -F "## $CURRENT_VERSION" "$CHANGELOG_FILE" || true) + + if [[ -n "$DEV_VERSION_HEADER" ]]; then + echo "Found dev version in CHANGELOG: $DEV_VERSION_HEADER" + + # Extract the -dev version number from header + DEV_VERSION=$(echo "$DEV_VERSION_HEADER" | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+-dev[0-9]*") + + echo "Renaming CHANGELOG header: $DEV_VERSION → $RELEASE_VERSION" + + # Create awk script to: + # 1. Rename the -dev version header + # 2. Find or create Fixes section + # 3. Append security entry + awk -v dev_version="$DEV_VERSION" \ + -v release_version="$RELEASE_VERSION" \ + -v security_entry="$CHANGELOG_ENTRY" ' + BEGIN { + in_target_version = 0 + found_fixes = 0 + added_entry = 0 + } + + # Match the dev version header and rename it + /^## / { + if ($0 ~ "^## " dev_version) { + print "## " release_version + in_target_version = 1 + next + } else { + # Hit a different version header, stop being in target version + if (in_target_version && !found_fixes && !added_entry) { + # We never found Fixes section, add it before this new version + print "" + print "### Fixes" + print security_entry + print "" + added_entry = 1 + } + in_target_version = 0 + found_fixes = 0 + } + } + + # Found Fixes section in target version + /^### Fixes/ && in_target_version { + print + print security_entry + found_fixes = 1 + added_entry = 1 + next + } + + { print } + + END { + # Handle case where target dev version is last entry and has no Fixes section + if (in_target_version && !found_fixes && !added_entry) { + print "" + print "### Fixes" + print security_entry + } + } + ' "$CHANGELOG_FILE" > "$CHANGELOG_FILE.tmp" + + mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" + else + # Dev version in __version__.py but no dev header found in CHANGELOG + # This shouldn't happen, but create new entry as fallback + echo "Warning: Current version has -dev suffix but no matching dev header in CHANGELOG" + echo "Creating new entry for $RELEASE_VERSION" + + cat > /tmp/new_changelog_section.tmp < "$CHANGELOG_FILE.tmp" + mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" + rm -f /tmp/new_changelog_section.tmp + fi +else + # Current version was already a release, so we bumped to next patch + # Create new release entry at top + echo "Current version was already released, creating new entry for $RELEASE_VERSION" + + cat > /tmp/new_changelog_section.tmp < "$CHANGELOG_FILE.tmp" + mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" + rm -f /tmp/new_changelog_section.tmp +fi + +echo "" +echo "✓ Successfully updated version to $RELEASE_VERSION" +echo "✓ Updated CHANGELOG with security fix entry" +echo "" +echo "Modified files:" +echo " - $VERSION_FILE" +echo " - $CHANGELOG_FILE" From 84d7602de4c5e8b6c2874d291505e166bd866e15 Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 15:14:37 -0600 Subject: [PATCH 2/9] chore: update renovate-security-bump.sh with shfmt formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Syncs script with latest version from renovate-config that includes: - shfmt formatting (2-space indents) - pyproject.toml versioning support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 358 ++++++++++++++++++------------ 1 file changed, 220 insertions(+), 138 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index fe9fe0dac..299aecaf0 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -3,184 +3,250 @@ set -euo pipefail # Shared script for Renovate to bump version and update CHANGELOG -# Can be downloaded and executed in any repo with version and changelog files +# Supports both __version__.py and pyproject.toml versioning styles # Auto-detects changed dependencies from git diff # Use current working directory as repo root (where Renovate executes the script) -# Override these via environment variables if your repo has different paths REPO_ROOT="${REPO_ROOT:-$(pwd)}" -VERSION_FILE="${VERSION_FILE:-$REPO_ROOT/unstructured/__version__.py}" CHANGELOG_FILE="${CHANGELOG_FILE:-$REPO_ROOT/CHANGELOG.md}" +# VERSION_FILE can be: +# - Path to __version__.py (traditional Python) +# - Path to pyproject.toml (modern Python with uv/poetry) +# - "auto" or unset to auto-detect +VERSION_FILE="${VERSION_FILE:-auto}" + echo "=== Renovate Security Version Bump ===" -# Read current version from __version__.py -CURRENT_VERSION=$(grep -o -E "(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-dev[0-9]+)?" "$VERSION_FILE") -echo "Current version: $CURRENT_VERSION" - -# Determine release version based on current version format -if [[ "$CURRENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev.*)?$ ]]; then - MAJOR="${BASH_REMATCH[1]}" - MINOR="${BASH_REMATCH[2]}" - PATCH="${BASH_REMATCH[3]}" - DEV_SUFFIX="${BASH_REMATCH[4]}" - - if [[ -n "$DEV_SUFFIX" ]]; then - # Strip -dev suffix to release current version - RELEASE_VERSION="$MAJOR.$MINOR.$PATCH" - echo "Stripping dev suffix: $CURRENT_VERSION → $RELEASE_VERSION" +# Auto-detect versioning style +detect_version_style() { + if [[ "$VERSION_FILE" == "auto" ]]; then + # Check for pyproject.toml with version field first (modern style) + if [[ -f "$REPO_ROOT/pyproject.toml" ]] && grep -q "^version\s*=" "$REPO_ROOT/pyproject.toml"; then + VERSION_FILE="$REPO_ROOT/pyproject.toml" + VERSION_STYLE="pyproject" + echo "Auto-detected: pyproject.toml versioning" + # Check for common __version__.py locations + elif [[ -f "$REPO_ROOT/unstructured/__version__.py" ]]; then + VERSION_FILE="$REPO_ROOT/unstructured/__version__.py" + VERSION_STYLE="python" + echo "Auto-detected: __version__.py versioning" + else + echo "Error: Could not auto-detect version file. Set VERSION_FILE explicitly." + exit 1 + fi + elif [[ "$VERSION_FILE" == *.py ]]; then + VERSION_STYLE="python" + echo "Using Python __version__.py style: $VERSION_FILE" + elif [[ "$VERSION_FILE" == *pyproject.toml ]]; then + VERSION_STYLE="pyproject" + echo "Using pyproject.toml style: $VERSION_FILE" else - # Already a release version, bump to next patch - NEW_PATCH=$((PATCH + 1)) - RELEASE_VERSION="$MAJOR.$MINOR.$NEW_PATCH" - echo "Bumping patch version: $CURRENT_VERSION → $RELEASE_VERSION" + echo "Error: Unknown version file type: $VERSION_FILE" + exit 1 + fi +} + +# Read current version based on style +read_current_version() { + if [[ "$VERSION_STYLE" == "python" ]]; then + CURRENT_VERSION=$(grep -o -E "(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-dev[0-9]+)?" "$VERSION_FILE") + elif [[ "$VERSION_STYLE" == "pyproject" ]]; then + # Extract version from pyproject.toml (handles both quoted styles) + CURRENT_VERSION=$(grep -E "^version\s*=" "$VERSION_FILE" | head -1 | sed -E 's/version\s*=\s*["\x27]?([^"\x27]+)["\x27]?/\1/' | tr -d ' ') + fi + echo "Current version: $CURRENT_VERSION" +} + +# Calculate new release version +calculate_release_version() { + if [[ "$CURRENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev.*)?$ ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]}" + DEV_SUFFIX="${BASH_REMATCH[4]}" + + if [[ -n "$DEV_SUFFIX" ]]; then + # Strip -dev suffix to release current version + RELEASE_VERSION="$MAJOR.$MINOR.$PATCH" + echo "Stripping dev suffix: $CURRENT_VERSION → $RELEASE_VERSION" + else + # Already a release version, bump to next patch + NEW_PATCH=$((PATCH + 1)) + RELEASE_VERSION="$MAJOR.$MINOR.$NEW_PATCH" + echo "Bumping patch version: $CURRENT_VERSION → $RELEASE_VERSION" + fi + else + echo "Error: Could not parse version: $CURRENT_VERSION" + exit 1 + fi +} + +# Update version in __version__.py +update_python_version() { + echo "Updating $VERSION_FILE to version $RELEASE_VERSION" + + # Detect quote style used in the file + if grep -q "__version__ = ['\"]" "$VERSION_FILE"; then + if grep -q "__version__ = \"" "$VERSION_FILE"; then + # Double quotes + sed -i.bak -E "s/__version__ = \"[^\"]+\"/__version__ = \"$RELEASE_VERSION\"/" "$VERSION_FILE" + else + # Single quotes + sed -i.bak -E "s/__version__ = '[^']+'/__version__ = '$RELEASE_VERSION'/" "$VERSION_FILE" + fi + else + echo "Error: Could not detect quote style in $VERSION_FILE" + exit 1 fi -else - echo "Error: Could not parse version: $CURRENT_VERSION" - exit 1 -fi -# Update __version__.py -echo "Updating $VERSION_FILE to version $RELEASE_VERSION" + # Verify the update succeeded + if ! grep -q "__version__ = ['\"]${RELEASE_VERSION}['\"]" "$VERSION_FILE"; then + echo "Error: Failed to update version in $VERSION_FILE" + exit 1 + fi + + rm -f "$VERSION_FILE.bak" +} -# Detect quote style used in the file -if grep -q "__version__ = ['\"]" "$VERSION_FILE"; then - if grep -q "__version__ = \"" "$VERSION_FILE"; then +# Update version in pyproject.toml +update_pyproject_version() { + echo "Updating $VERSION_FILE to version $RELEASE_VERSION" + + # Detect quote style (single or double quotes) + if grep -qE "^version\s*=\s*\"" "$VERSION_FILE"; then # Double quotes - sed -i.bak -E "s/__version__ = \"[^\"]+\"/__version__ = \"$RELEASE_VERSION\"/" "$VERSION_FILE" + sed -i.bak -E "s/^(version\s*=\s*)\"[^\"]+\"/\1\"$RELEASE_VERSION\"/" "$VERSION_FILE" else # Single quotes - sed -i.bak -E "s/__version__ = '[^']+'/__version__ = '$RELEASE_VERSION'/" "$VERSION_FILE" + sed -i.bak -E "s/^(version\s*=\s*)'[^']+'$/\1'$RELEASE_VERSION'/" "$VERSION_FILE" fi -else - echo "Error: Could not detect quote style in $VERSION_FILE" - exit 1 -fi -# Verify the update succeeded -if ! grep -q "__version__ = ['\"]$RELEASE_VERSION['\"]" "$VERSION_FILE"; then - echo "Error: Failed to update version in $VERSION_FILE" - exit 1 -fi + # Verify the update succeeded + if ! grep -qE "^version\s*=\s*['\"]${RELEASE_VERSION}['\"]" "$VERSION_FILE"; then + echo "Error: Failed to update version in $VERSION_FILE" + exit 1 + fi -rm -f "$VERSION_FILE.bak" + rm -f "$VERSION_FILE.bak" +} -# Detect changed packages from git diff (best effort, not critical) -echo "Detecting changed dependencies..." -CHANGED_PACKAGES=$(git diff --cached requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) +# Detect changed packages from git diff +detect_changed_packages() { + echo "Detecting changed dependencies..." -if [ -z "$CHANGED_PACKAGES" ]; then - # Try without --cached - CHANGED_PACKAGES=$(git diff requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) -fi + # Try requirements files first + CHANGED_PACKAGES=$(git diff --cached requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) + + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) + fi + + # Try uv.lock if no requirements changes found + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff --cached uv.lock 2>/dev/null | grep -E "^[-+]name\s*=" | sed -E 's/^[-+]name\s*=\s*"([^"]+)"/\1/' | sort -u | head -20 || true) + fi + + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff uv.lock 2>/dev/null | grep -E "^[-+]name\s*=" | sed -E 's/^[-+]name\s*=\s*"([^"]+)"/\1/' | sort -u | head -20 || true) + fi + + # Build changelog entry + if [ -n "$CHANGED_PACKAGES" ]; then + PACKAGE_COUNT=$(echo "$CHANGED_PACKAGES" | wc -l | tr -d ' ') + echo "Found $PACKAGE_COUNT changed package(s)" + else + echo "Could not auto-detect packages, using generic entry" + fi -# Build changelog entry (generic for now, can be manually edited) -if [ -n "$CHANGED_PACKAGES" ]; then - PACKAGE_COUNT=$(echo "$CHANGED_PACKAGES" | wc -l | tr -d ' ') - echo "Found $PACKAGE_COUNT changed package(s)" - CHANGELOG_ENTRY="- **Security update**: Bumped dependencies to address security vulnerabilities" -else - echo "Could not auto-detect packages, using generic entry" CHANGELOG_ENTRY="- **Security update**: Bumped dependencies to address security vulnerabilities" -fi -echo "Changelog entry: $CHANGELOG_ENTRY" + echo "Changelog entry: $CHANGELOG_ENTRY" +} # Update CHANGELOG.md -echo "Updating CHANGELOG..." +update_changelog() { + echo "Updating CHANGELOG..." -# Only look for -dev version to rename if CURRENT_VERSION had -dev suffix -if [[ -n "$DEV_SUFFIX" ]]; then - # Look for -dev version header in CHANGELOG that matches our version - DEV_VERSION_HEADER=$(grep -m 1 -F "## $CURRENT_VERSION" "$CHANGELOG_FILE" || true) + # Check if CHANGELOG exists + if [[ ! -f "$CHANGELOG_FILE" ]]; then + echo "Warning: CHANGELOG.md not found at $CHANGELOG_FILE, skipping changelog update" + return 0 + fi + + # Only look for -dev version to rename if CURRENT_VERSION had -dev suffix + if [[ -n "${DEV_SUFFIX:-}" ]]; then + # Look for -dev version header in CHANGELOG that matches our version + DEV_VERSION_HEADER=$(grep -m 1 -F "## $CURRENT_VERSION" "$CHANGELOG_FILE" || true) - if [[ -n "$DEV_VERSION_HEADER" ]]; then - echo "Found dev version in CHANGELOG: $DEV_VERSION_HEADER" + if [[ -n "$DEV_VERSION_HEADER" ]]; then + echo "Found dev version in CHANGELOG: $DEV_VERSION_HEADER" - # Extract the -dev version number from header - DEV_VERSION=$(echo "$DEV_VERSION_HEADER" | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+-dev[0-9]*") + # Extract the -dev version number from header + DEV_VERSION=$(echo "$DEV_VERSION_HEADER" | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+-dev[0-9]*") - echo "Renaming CHANGELOG header: $DEV_VERSION → $RELEASE_VERSION" + echo "Renaming CHANGELOG header: $DEV_VERSION → $RELEASE_VERSION" - # Create awk script to: - # 1. Rename the -dev version header - # 2. Find or create Fixes section - # 3. Append security entry - awk -v dev_version="$DEV_VERSION" \ + awk -v dev_version="$DEV_VERSION" \ -v release_version="$RELEASE_VERSION" \ -v security_entry="$CHANGELOG_ENTRY" ' - BEGIN { - in_target_version = 0 - found_fixes = 0 - added_entry = 0 - } - - # Match the dev version header and rename it - /^## / { - if ($0 ~ "^## " dev_version) { - print "## " release_version - in_target_version = 1 + BEGIN { + in_target_version = 0 + found_fixes = 0 + added_entry = 0 + } + + /^## / { + if ($0 ~ "^## " dev_version) { + print "## " release_version + in_target_version = 1 + next + } else { + if (in_target_version && !found_fixes && !added_entry) { + print "" + print "### Fixes" + print security_entry + print "" + added_entry = 1 + } + in_target_version = 0 + found_fixes = 0 + } + } + + /^### Fixes/ && in_target_version { + print + print security_entry + found_fixes = 1 + added_entry = 1 next - } else { - # Hit a different version header, stop being in target version + } + + { print } + + END { if (in_target_version && !found_fixes && !added_entry) { - # We never found Fixes section, add it before this new version print "" print "### Fixes" print security_entry - print "" - added_entry = 1 } - in_target_version = 0 - found_fixes = 0 - } - } - - # Found Fixes section in target version - /^### Fixes/ && in_target_version { - print - print security_entry - found_fixes = 1 - added_entry = 1 - next - } - - { print } - - END { - # Handle case where target dev version is last entry and has no Fixes section - if (in_target_version && !found_fixes && !added_entry) { - print "" - print "### Fixes" - print security_entry } - } - ' "$CHANGELOG_FILE" > "$CHANGELOG_FILE.tmp" + ' "$CHANGELOG_FILE" >"$CHANGELOG_FILE.tmp" - mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" + mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" + else + echo "Warning: Current version has -dev suffix but no matching dev header in CHANGELOG" + create_new_changelog_entry + fi else - # Dev version in __version__.py but no dev header found in CHANGELOG - # This shouldn't happen, but create new entry as fallback - echo "Warning: Current version has -dev suffix but no matching dev header in CHANGELOG" - echo "Creating new entry for $RELEASE_VERSION" - - cat > /tmp/new_changelog_section.tmp < "$CHANGELOG_FILE.tmp" - mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" - rm -f /tmp/new_changelog_section.tmp + # Current version was already a release, so we bumped to next patch + create_new_changelog_entry fi -else - # Current version was already a release, so we bumped to next patch - # Create new release entry at top - echo "Current version was already released, creating new entry for $RELEASE_VERSION" +} - cat > /tmp/new_changelog_section.tmp </tmp/new_changelog_section.tmp < "$CHANGELOG_FILE.tmp" + cat /tmp/new_changelog_section.tmp "$CHANGELOG_FILE" >"$CHANGELOG_FILE.tmp" mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" rm -f /tmp/new_changelog_section.tmp +} + +# Main execution +detect_version_style +read_current_version +calculate_release_version + +if [[ "$VERSION_STYLE" == "python" ]]; then + update_python_version +elif [[ "$VERSION_STYLE" == "pyproject" ]]; then + update_pyproject_version fi +detect_changed_packages +update_changelog + echo "" echo "✓ Successfully updated version to $RELEASE_VERSION" echo "✓ Updated CHANGELOG with security fix entry" echo "" echo "Modified files:" echo " - $VERSION_FILE" -echo " - $CHANGELOG_FILE" +if [[ -f "$CHANGELOG_FILE" ]]; then + echo " - $CHANGELOG_FILE" +fi From 135e78707ea31fae855d538807133f0130544112 Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 15:35:07 -0600 Subject: [PATCH 3/9] chore: update script with improved pip-compile requirements detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PEP 508 compliant package name regex (supports dots) - Detection for requirements/*.in files - Detection for pyproject.toml dependencies - Better logging of detected packages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 37 ++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index 299aecaf0..cba1caa97 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -135,26 +135,51 @@ update_pyproject_version() { detect_changed_packages() { echo "Detecting changed dependencies..." - # Try requirements files first - CHANGED_PACKAGES=$(git diff --cached requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) + # Package name regex per PEP 508: starts with letter/digit, can contain letters, digits, dots, underscores, hyphens + local pkg_pattern='^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*==' + + # Try requirements/*.txt files first (pip-compile output format) + CHANGED_PACKAGES=$(git diff --cached requirements/*.txt 2>/dev/null | grep -E "$pkg_pattern" | sed 's/^[+-]//' | sort -u | head -20 || true) + + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff requirements/*.txt 2>/dev/null | grep -E "$pkg_pattern" | sed 's/^[+-]//' | sort -u | head -20 || true) + fi + + # Try requirements/*.in files (unpinned requirements) + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff --cached requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed 's/[<>=].*//' | sort -u | head -20 || true) + fi if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff requirements/*.txt 2>/dev/null | grep -E "^[-+][a-zA-Z0-9_-]+==" | sed 's/^[+-]//' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed 's/[<>=].*//' | sort -u | head -20 || true) fi # Try uv.lock if no requirements changes found if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff --cached uv.lock 2>/dev/null | grep -E "^[-+]name\s*=" | sed -E 's/^[-+]name\s*=\s*"([^"]+)"/\1/' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff --cached uv.lock 2>/dev/null | grep -E '^[-+]name\s*=' | sed -E 's/^[-+]name\s*=\s*"([^"]+)"/\1/' | sort -u | head -20 || true) fi if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff uv.lock 2>/dev/null | grep -E "^[-+]name\s*=" | sed -E 's/^[-+]name\s*=\s*"([^"]+)"/\1/' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff uv.lock 2>/dev/null | grep -E '^[-+]name\s*=' | sed -E 's/^[-+]name\s*=\s*"([^"]+)"/\1/' | sort -u | head -20 || true) + fi + + # Try pyproject.toml dependencies section + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff --cached pyproject.toml 2>/dev/null | grep -E '^[-+]\s*"?[a-zA-Z0-9][a-zA-Z0-9._-]*[<>=]' | sed -E 's/^[-+]\s*"?([a-zA-Z0-9][a-zA-Z0-9._-]*).*/\1/' | sort -u | head -20 || true) + fi + + if [ -z "$CHANGED_PACKAGES" ]; then + CHANGED_PACKAGES=$(git diff pyproject.toml 2>/dev/null | grep -E '^[-+]\s*"?[a-zA-Z0-9][a-zA-Z0-9._-]*[<>=]' | sed -E 's/^[-+]\s*"?([a-zA-Z0-9][a-zA-Z0-9._-]*).*/\1/' | sort -u | head -20 || true) fi # Build changelog entry if [ -n "$CHANGED_PACKAGES" ]; then PACKAGE_COUNT=$(echo "$CHANGED_PACKAGES" | wc -l | tr -d ' ') - echo "Found $PACKAGE_COUNT changed package(s)" + echo "Found $PACKAGE_COUNT changed package(s):" + echo "$CHANGED_PACKAGES" | head -5 | sed 's/^/ - /' + if [ "$PACKAGE_COUNT" -gt 5 ]; then + echo " ... and $((PACKAGE_COUNT - 5)) more" + fi else echo "Could not auto-detect packages, using generic entry" fi From e4167bf3654c8689ba8378027d917586699a076a Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 15:44:33 -0600 Subject: [PATCH 4/9] fix: handle extras in .in files and ~= in pyproject.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index cba1caa97..462a2b469 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -146,12 +146,13 @@ detect_changed_packages() { fi # Try requirements/*.in files (unpinned requirements) + # Strip extras [.*] and version specifiers, handle packages like requests[security]>=2.0 if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff --cached requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed 's/[<>=].*//' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff --cached requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed -E 's/\[.*//; s/[<>=~].*//' | sort -u | head -20 || true) fi if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed 's/[<>=].*//' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed -E 's/\[.*//; s/[<>=~].*//' | sort -u | head -20 || true) fi # Try uv.lock if no requirements changes found @@ -164,12 +165,13 @@ detect_changed_packages() { fi # Try pyproject.toml dependencies section + # Match version specifiers (<>=~^!), extras [, quotes ", commas, or end of line for unversioned if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff --cached pyproject.toml 2>/dev/null | grep -E '^[-+]\s*"?[a-zA-Z0-9][a-zA-Z0-9._-]*[<>=]' | sed -E 's/^[-+]\s*"?([a-zA-Z0-9][a-zA-Z0-9._-]*).*/\1/' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff --cached pyproject.toml 2>/dev/null | grep -E '^[-+]\s*"?[a-zA-Z0-9][a-zA-Z0-9._-]*([<>=~^!\[",]|$)' | sed -E 's/^[-+]\s*"?([a-zA-Z0-9][a-zA-Z0-9._-]*).*/\1/' | sort -u | head -20 || true) fi if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff pyproject.toml 2>/dev/null | grep -E '^[-+]\s*"?[a-zA-Z0-9][a-zA-Z0-9._-]*[<>=]' | sed -E 's/^[-+]\s*"?([a-zA-Z0-9][a-zA-Z0-9._-]*).*/\1/' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff pyproject.toml 2>/dev/null | grep -E '^[-+]\s*"?[a-zA-Z0-9][a-zA-Z0-9._-]*([<>=~^!\[",]|$)' | sed -E 's/^[-+]\s*"?([a-zA-Z0-9][a-zA-Z0-9._-]*).*/\1/' | sort -u | head -20 || true) fi # Build changelog entry From 10587be220b474a04d594e0325e0dc9b83b9f369 Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 15:50:28 -0600 Subject: [PATCH 5/9] fix: strip inline comments and exclude URL deps in .in files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index 462a2b469..97d31d6a9 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -146,13 +146,13 @@ detect_changed_packages() { fi # Try requirements/*.in files (unpinned requirements) - # Strip extras [.*] and version specifiers, handle packages like requests[security]>=2.0 + # Strip comments, extras [.*], version specifiers; exclude URLs and flags if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff --cached requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed -E 's/\[.*//; s/[<>=~].*//' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff --cached requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | grep -v '://' | sed 's/^[+-]//' | sed -E 's/#.*//; s/\[.*//; s/[<>=~].*//' | sed 's/[[:space:]]*$//' | sort -u | head -20 || true) fi if [ -z "$CHANGED_PACKAGES" ]; then - CHANGED_PACKAGES=$(git diff requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | sed 's/^[+-]//' | sed -E 's/\[.*//; s/[<>=~].*//' | sort -u | head -20 || true) + CHANGED_PACKAGES=$(git diff requirements/*.in 2>/dev/null | grep -E '^[-+][a-zA-Z0-9][a-zA-Z0-9._-]*' | grep -v '^[-+]#' | grep -v '^[-+]-' | grep -v '://' | sed 's/^[+-]//' | sed -E 's/#.*//; s/\[.*//; s/[<>=~].*//' | sed 's/[[:space:]]*$//' | sort -u | head -20 || true) fi # Try uv.lock if no requirements changes found From 4ef562796be72d69efde371ee3043b97ea2c78fb Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 16:17:44 -0600 Subject: [PATCH 6/9] fix: address cursor bot review comments on bump script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index 97d31d6a9..09e66a37c 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -22,7 +22,7 @@ echo "=== Renovate Security Version Bump ===" detect_version_style() { if [[ "$VERSION_FILE" == "auto" ]]; then # Check for pyproject.toml with version field first (modern style) - if [[ -f "$REPO_ROOT/pyproject.toml" ]] && grep -q "^version\s*=" "$REPO_ROOT/pyproject.toml"; then + if [[ -f "$REPO_ROOT/pyproject.toml" ]] && grep -qE "^version\s*=" "$REPO_ROOT/pyproject.toml"; then VERSION_FILE="$REPO_ROOT/pyproject.toml" VERSION_STYLE="pyproject" echo "Auto-detected: pyproject.toml versioning" @@ -50,7 +50,7 @@ detect_version_style() { # Read current version based on style read_current_version() { if [[ "$VERSION_STYLE" == "python" ]]; then - CURRENT_VERSION=$(grep -o -E "(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-dev[0-9]+)?" "$VERSION_FILE") + CURRENT_VERSION=$(grep -o -E "(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-dev[0-9]*)?" "$VERSION_FILE" | head -1) elif [[ "$VERSION_STYLE" == "pyproject" ]]; then # Extract version from pyproject.toml (handles both quoted styles) CURRENT_VERSION=$(grep -E "^version\s*=" "$VERSION_FILE" | head -1 | sed -E 's/version\s*=\s*["\x27]?([^"\x27]+)["\x27]?/\1/' | tr -d ' ') @@ -60,7 +60,7 @@ read_current_version() { # Calculate new release version calculate_release_version() { - if [[ "$CURRENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev.*)?$ ]]; then + if [[ "$CURRENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev[0-9]*)?$ ]]; then MAJOR="${BASH_REMATCH[1]}" MINOR="${BASH_REMATCH[2]}" PATCH="${BASH_REMATCH[3]}" @@ -119,7 +119,7 @@ update_pyproject_version() { sed -i.bak -E "s/^(version\s*=\s*)\"[^\"]+\"/\1\"$RELEASE_VERSION\"/" "$VERSION_FILE" else # Single quotes - sed -i.bak -E "s/^(version\s*=\s*)'[^']+'$/\1'$RELEASE_VERSION'/" "$VERSION_FILE" + sed -i.bak -E "s/^(version\s*=\s*)'[^']+'/\1'$RELEASE_VERSION'/" "$VERSION_FILE" fi # Verify the update succeeded @@ -223,7 +223,7 @@ update_changelog() { } /^## / { - if ($0 ~ "^## " dev_version) { + if (index($0, "## " dev_version) == 1) { print "## " release_version in_target_version = 1 next @@ -273,7 +273,10 @@ update_changelog() { create_new_changelog_entry() { echo "Creating new CHANGELOG entry for $RELEASE_VERSION" - cat >/tmp/new_changelog_section.tmp <"$tmp_file" <"$CHANGELOG_FILE.tmp" + cat "$tmp_file" "$CHANGELOG_FILE" >"$CHANGELOG_FILE.tmp" mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" - rm -f /tmp/new_changelog_section.tmp + rm -f "$tmp_file" } # Main execution From 07e12cc65acbd05d70675857d445c4f4074f7203 Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 16:39:43 -0600 Subject: [PATCH 7/9] fix: portable sed, trailing content, package detection, exact match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use portable sed for pyproject version extraction (avoid \x27) - Discard trailing content in pyproject version extraction - Include detected packages in changelog entries - Prevent substring match of CHANGELOG dev version header 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index 09e66a37c..5d4c52716 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -52,8 +52,14 @@ read_current_version() { if [[ "$VERSION_STYLE" == "python" ]]; then CURRENT_VERSION=$(grep -o -E "(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-dev[0-9]*)?" "$VERSION_FILE" | head -1) elif [[ "$VERSION_STYLE" == "pyproject" ]]; then - # Extract version from pyproject.toml (handles both quoted styles) - CURRENT_VERSION=$(grep -E "^version\s*=" "$VERSION_FILE" | head -1 | sed -E 's/version\s*=\s*["\x27]?([^"\x27]+)["\x27]?/\1/' | tr -d ' ') + # Extract version from pyproject.toml (detect quote style for portability) + if grep -qE "^version\s*=\s*\"" "$VERSION_FILE"; then + # Double quotes - match and discard trailing content (comments, etc) + CURRENT_VERSION=$(grep -E "^version\s*=" "$VERSION_FILE" | head -1 | sed -E 's/version\s*=\s*"([^"]+)".*/\1/' | tr -d ' ') + else + # Single quotes (portable - avoid \x27 which breaks on BSD sed) + CURRENT_VERSION=$(grep -E "^version\s*=" "$VERSION_FILE" | head -1 | sed -E "s/version\s*=\s*'([^']+)'.*/\1/" | tr -d ' ') + fi fi echo "Current version: $CURRENT_VERSION" } @@ -182,11 +188,22 @@ detect_changed_packages() { if [ "$PACKAGE_COUNT" -gt 5 ]; then echo " ... and $((PACKAGE_COUNT - 5)) more" fi + + # Build specific changelog entry with package names + if [ "$PACKAGE_COUNT" -eq 1 ]; then + PACKAGE_NAME=$(echo "$CHANGED_PACKAGES" | head -1 | cut -d'=' -f1) + CHANGELOG_ENTRY="- **Security update**: Updated \`${PACKAGE_NAME}\` to address security vulnerability" + elif [ "$PACKAGE_COUNT" -le 3 ]; then + PACKAGE_NAMES=$(echo "$CHANGED_PACKAGES" | cut -d'=' -f1 | paste -sd, - | sed 's/,/, /g' | sed 's/\([^,]*\)/`\1`/g') + CHANGELOG_ENTRY="- **Security update**: Updated ${PACKAGE_NAMES} to address security vulnerabilities" + else + CHANGELOG_ENTRY="- **Security update**: Updated ${PACKAGE_COUNT} dependencies to address security vulnerabilities" + fi else echo "Could not auto-detect packages, using generic entry" + CHANGELOG_ENTRY="- **Security update**: Bumped dependencies to address security vulnerabilities" fi - CHANGELOG_ENTRY="- **Security update**: Bumped dependencies to address security vulnerabilities" echo "Changelog entry: $CHANGELOG_ENTRY" } @@ -202,8 +219,10 @@ update_changelog() { # Only look for -dev version to rename if CURRENT_VERSION had -dev suffix if [[ -n "${DEV_SUFFIX:-}" ]]; then - # Look for -dev version header in CHANGELOG that matches our version - DEV_VERSION_HEADER=$(grep -m 1 -F "## $CURRENT_VERSION" "$CHANGELOG_FILE" || true) + # Look for -dev version header in CHANGELOG that matches our version exactly (not substring) + # Escape dots for regex, then match with end-of-line or whitespace anchor + ESCAPED_VERSION="${CURRENT_VERSION//./\\.}" + DEV_VERSION_HEADER=$(grep -m 1 -E "^## ${ESCAPED_VERSION}(\s*$)" "$CHANGELOG_FILE" || true) if [[ -n "$DEV_VERSION_HEADER" ]]; then echo "Found dev version in CHANGELOG: $DEV_VERSION_HEADER" From 38c50a20444cf49a320e3359058470616ca15a0e Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 16:44:48 -0600 Subject: [PATCH 8/9] feat: auto-detect and adapt to CHANGELOG format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Script now detects CHANGELOG format (brackets, subsections) and adapts output accordingly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 84 ++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index 5d4c52716..0dc3a0cb0 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -207,6 +207,27 @@ detect_changed_packages() { echo "Changelog entry: $CHANGELOG_ENTRY" } +# Detect CHANGELOG format from existing entries +detect_changelog_format() { + # Check if headers use brackets: ## [1.2.3] vs ## 1.2.3 + if grep -q -m 1 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+' "$CHANGELOG_FILE" 2>/dev/null; then + CHANGELOG_USE_BRACKETS=true + echo "Detected CHANGELOG format: bracketed headers (## [version])" + else + CHANGELOG_USE_BRACKETS=false + echo "Detected CHANGELOG format: plain headers (## version)" + fi + + # Check if CHANGELOG uses subsections (### Fixes) or direct bullets + if grep -q -m 1 '^### ' "$CHANGELOG_FILE" 2>/dev/null; then + CHANGELOG_USE_SUBSECTIONS=true + echo "Detected CHANGELOG format: uses subsections (### Fixes)" + else + CHANGELOG_USE_SUBSECTIONS=false + echo "Detected CHANGELOG format: direct bullet points" + fi +} + # Update CHANGELOG.md update_changelog() { echo "Updating CHANGELOG..." @@ -217,12 +238,19 @@ update_changelog() { return 0 fi + # Detect the format + detect_changelog_format + # Only look for -dev version to rename if CURRENT_VERSION had -dev suffix if [[ -n "${DEV_SUFFIX:-}" ]]; then # Look for -dev version header in CHANGELOG that matches our version exactly (not substring) - # Escape dots for regex, then match with end-of-line or whitespace anchor + # Escape dots for regex, handle brackets if used ESCAPED_VERSION="${CURRENT_VERSION//./\\.}" - DEV_VERSION_HEADER=$(grep -m 1 -E "^## ${ESCAPED_VERSION}(\s*$)" "$CHANGELOG_FILE" || true) + if [ "$CHANGELOG_USE_BRACKETS" = true ]; then + DEV_VERSION_HEADER=$(grep -m 1 -E "^## \[${ESCAPED_VERSION}\]" "$CHANGELOG_FILE" || true) + else + DEV_VERSION_HEADER=$(grep -m 1 -E "^## ${ESCAPED_VERSION}(\s*$)" "$CHANGELOG_FILE" || true) + fi if [[ -n "$DEV_VERSION_HEADER" ]]; then echo "Found dev version in CHANGELOG: $DEV_VERSION_HEADER" @@ -234,7 +262,9 @@ update_changelog() { awk -v dev_version="$DEV_VERSION" \ -v release_version="$RELEASE_VERSION" \ - -v security_entry="$CHANGELOG_ENTRY" ' + -v security_entry="$CHANGELOG_ENTRY" \ + -v use_brackets="$CHANGELOG_USE_BRACKETS" \ + -v use_subsections="$CHANGELOG_USE_SUBSECTIONS" ' BEGIN { in_target_version = 0 found_fixes = 0 @@ -242,16 +272,24 @@ update_changelog() { } /^## / { - if (index($0, "## " dev_version) == 1) { - print "## " release_version + # Match the dev version header (with or without brackets) + dev_header = use_brackets == "true" ? "## [" dev_version "]" : "## " dev_version + release_header = use_brackets == "true" ? "## [" release_version "]" : "## " release_version + + if (index($0, dev_header) == 1) { + print release_header in_target_version = 1 next } else { if (in_target_version && !found_fixes && !added_entry) { - print "" - print "### Fixes" + if (use_subsections == "true") { + print "" + print "### Fixes" + } print security_entry - print "" + if (use_subsections == "true") { + print "" + } added_entry = 1 } in_target_version = 0 @@ -259,7 +297,7 @@ update_changelog() { } } - /^### Fixes/ && in_target_version { + /^### Fixes/ && in_target_version && use_subsections == "true" { print print security_entry found_fixes = 1 @@ -271,8 +309,10 @@ update_changelog() { END { if (in_target_version && !found_fixes && !added_entry) { - print "" - print "### Fixes" + if (use_subsections == "true") { + print "" + print "### Fixes" + } print security_entry } } @@ -295,13 +335,31 @@ create_new_changelog_entry() { local tmp_file tmp_file=$(mktemp) - cat >"$tmp_file" <"$tmp_file" <"$tmp_file" <"$CHANGELOG_FILE.tmp" mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" From 64ce5909389052c7cadf543aa42042f039d7f978 Mon Sep 17 00:00:00 2001 From: Lawrence Elitzer Date: Wed, 24 Dec 2025 17:14:22 -0600 Subject: [PATCH 9/9] fix: shellcheck SC2016 - pass PACKAGE_COUNT as awk variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/renovate-security-bump.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/renovate-security-bump.sh b/scripts/renovate-security-bump.sh index 0dc3a0cb0..40197e3a7 100755 --- a/scripts/renovate-security-bump.sh +++ b/scripts/renovate-security-bump.sh @@ -194,7 +194,8 @@ detect_changed_packages() { PACKAGE_NAME=$(echo "$CHANGED_PACKAGES" | head -1 | cut -d'=' -f1) CHANGELOG_ENTRY="- **Security update**: Updated \`${PACKAGE_NAME}\` to address security vulnerability" elif [ "$PACKAGE_COUNT" -le 3 ]; then - PACKAGE_NAMES=$(echo "$CHANGED_PACKAGES" | cut -d'=' -f1 | paste -sd, - | sed 's/,/, /g' | sed 's/\([^,]*\)/`\1`/g') + # Wrap each package in backticks, then join with commas + PACKAGE_NAMES=$(echo "$CHANGED_PACKAGES" | cut -d'=' -f1 | awk -v count="$PACKAGE_COUNT" '{printf "`%s`", $0; if (NR < count) printf ", "}') CHANGELOG_ENTRY="- **Security update**: Updated ${PACKAGE_NAMES} to address security vulnerabilities" else CHANGELOG_ENTRY="- **Security update**: Updated ${PACKAGE_COUNT} dependencies to address security vulnerabilities" @@ -276,7 +277,11 @@ update_changelog() { dev_header = use_brackets == "true" ? "## [" dev_version "]" : "## " dev_version release_header = use_brackets == "true" ? "## [" release_version "]" : "## " release_version - if (index($0, dev_header) == 1) { + # Exact match: strip trailing whitespace and compare + line_trimmed = $0 + gsub(/[[:space:]]+$/, "", line_trimmed) + + if (line_trimmed == dev_header) { print release_header in_target_version = 1 next