# Release commands for component-specific versioning # Usage: just release::bump-workbench 1.0.0, just release::tag-workbench 1.0.0, etc. # # Release Process (two steps): # 1. Bump version: just release::bump- # - Updates all version strings in source files # - Commit the changes: git add -A && git commit -m "chore: bump to " # 2. Create tag: just release::tag- # - Verifies versions match the tag # - Creates and pushes the git tag # - Tag triggers GitHub Actions workflow # # This project uses component-specific git tags for releases: # - robust-mcp-server-vX.Y.Z (triggers PyPI, Docker, GitHub release) # - robust-mcp-workbench-vX.Y.Z (triggers workbench archive release) # # Version Format (SemVer 2.0): # - X.Y.Z - Stable release # - X.Y.Z-alpha - Alpha (TestPyPI only) # - X.Y.Z-alpha.N - Alpha with number (TestPyPI only) # - X.Y.Z-beta - Beta (TestPyPI only) # - X.Y.Z-beta.N - Beta with number (TestPyPI only) # - X.Y.Z-rc.N - Release candidate (TestPyPI only) # Project root directory project_root := justfile_directory() # ============================================================================= # Version Bump Commands # ============================================================================= # Bump the Robust MCP Bridge workbench version in all source files bump-workbench version: #!/usr/bin/env bash set -euo pipefail VERSION="{{version}}" TODAY=$(date +%Y-%m-%d) # Validate version format (SemVer 2.0) if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "ERROR: Version '$VERSION' is not valid semver (X.Y.Z or X.Y.Z-prerelease)" echo "Examples: 1.0.0, 1.0.0-alpha, 1.0.0-beta.1, 1.0.0-rc.1" exit 1 fi echo "Bumping Robust MCP Bridge Workbench to version: $VERSION (date: $TODAY)" echo "" # Update __version__ in the bridge module's __init__.py INIT_FILE="{{project_root}}/addon/FreecadRobustMCPBridge/freecad_mcp_bridge/__init__.py" if [ -f "$INIT_FILE" ]; then sed 's/^__version__ = "[^"]*"/__version__ = "'"$VERSION"'"/' "$INIT_FILE" > "$INIT_FILE.tmp" && mv "$INIT_FILE.tmp" "$INIT_FILE" echo "Updated $INIT_FILE:" grep "__version__" "$INIT_FILE" else echo "ERROR: File not found: $INIT_FILE" exit 1 fi # Update wiki-source.txt version and date WIKI_FILE="{{project_root}}/addon/FreecadRobustMCPBridge/wiki-source.txt" if [ -f "$WIKI_FILE" ]; then sed "s/|Version=.*/|Version=${VERSION}/" "$WIKI_FILE" > "$WIKI_FILE.tmp" && mv "$WIKI_FILE.tmp" "$WIKI_FILE" sed "s/|Date=.*/|Date=${TODAY}/" "$WIKI_FILE" > "$WIKI_FILE.tmp" && mv "$WIKI_FILE.tmp" "$WIKI_FILE" echo "" echo "Updated $WIKI_FILE:" grep -E "^\|Version=|\|Date=" "$WIKI_FILE" else echo "WARNING: File not found: $WIKI_FILE" fi # Update the workbench version in package.xml PACKAGE_XML="{{project_root}}/package.xml" if [ -f "$PACKAGE_XML" ]; then # Use awk for precise XML editing within the workbench section awk -v version="$VERSION" -v date="$TODAY" ' // { in_workbench=1 } /<\/workbench>/ { in_workbench=0 } in_workbench && // { gsub(/[^<]*<\/version>/, "" version "") } in_workbench && // { gsub(/[^<]*<\/date>/, "" date "") } { print } ' "$PACKAGE_XML" > "$PACKAGE_XML.tmp" && mv "$PACKAGE_XML.tmp" "$PACKAGE_XML" echo "" echo "Updated $PACKAGE_XML (workbench section):" grep -A3 '' "$PACKAGE_XML" | head -5 else echo "ERROR: File not found: $PACKAGE_XML" exit 1 fi echo "" echo "Version bump complete!" echo "" echo "Next steps:" echo " 1. Review changes: git diff" echo " 2. Commit: git add -A && git commit -m 'chore: bump workbench to $VERSION'" echo " 3. Tag and release: just release::tag-workbench $VERSION" # ============================================================================= # Tag Creation Commands # ============================================================================= # Create and push a release tag for the Robust MCP Server (triggers PyPI + Docker release) # Note: Robust MCP Server uses setuptools-scm, so version is derived from git tag at build time tag-mcp-server version: #!/usr/bin/env bash set -euo pipefail # Source shared release helper functions . "{{project_root}}/scripts/release-helpers.sh" TAG="robust-mcp-server-v{{version}}" # Run all pre-release checks (version format, main branch, up-to-date, clean tree) pre_release_checks "{{version}}" || exit 1 echo "Creating tag: $TAG" echo "" echo "This will trigger:" echo " - PyPI release (stable) or TestPyPI (alpha/beta/rc)" echo " - Docker Hub release" echo " - GitHub release with wheel and tar.gz" echo "" read -p "Continue? [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Aborted." exit 1 fi git tag -a "$TAG" -m "Release Robust MCP Server v{{version}}" git push origin "$TAG" echo "" echo "Tag $TAG created and pushed!" echo "Watch the release at: https://github.com/spkane/freecad-addon-robust-mcp-server/actions" # Create and push a release tag for the Robust MCP Bridge workbench tag-workbench version: #!/usr/bin/env bash set -euo pipefail # Source shared release helper functions . "{{project_root}}/scripts/release-helpers.sh" TAG="robust-mcp-workbench-v{{version}}" VERSION="{{version}}" # Run all pre-release checks (version format, main branch, up-to-date, clean tree) pre_release_checks "$VERSION" || exit 1 # Verify version in source files matches tag version echo "Verifying version in source files..." # Check __init__.py INIT_FILE="{{project_root}}/addon/FreecadRobustMCPBridge/freecad_mcp_bridge/__init__.py" INIT_VERSION=$(grep -o '__version__ = "[^"]*"' "$INIT_FILE" | cut -d'"' -f2) if [ "$INIT_VERSION" != "$VERSION" ]; then echo "ERROR: Version mismatch in $INIT_FILE" echo " Expected: $VERSION" echo " Found: $INIT_VERSION" echo "" echo "Run 'just release::bump-workbench $VERSION' first, then commit the changes." exit 1 fi echo "✓ $INIT_FILE: $INIT_VERSION" # Check wiki-source.txt WIKI_FILE="{{project_root}}/addon/FreecadRobustMCPBridge/wiki-source.txt" if [ -f "$WIKI_FILE" ]; then WIKI_VERSION=$(grep -o '|Version=[^|]*' "$WIKI_FILE" | cut -d= -f2 | tr -d '\n') if [ "$WIKI_VERSION" != "$VERSION" ]; then echo "ERROR: Version mismatch in $WIKI_FILE" echo " Expected: $VERSION" echo " Found: $WIKI_VERSION" echo "" echo "Run 'just release::bump-workbench $VERSION' first, then commit the changes." exit 1 fi echo "✓ $WIKI_FILE: $WIKI_VERSION" fi # Check package.xml PACKAGE_XML="{{project_root}}/package.xml" PKG_VERSION=$(awk '//,/<\/workbench>/' "$PACKAGE_XML" | grep -o '[^<]*' | head -1 | sed 's/<[^>]*>//g') if [ "$PKG_VERSION" != "$VERSION" ]; then echo "ERROR: Version mismatch in $PACKAGE_XML (workbench section)" echo " Expected: $VERSION" echo " Found: $PKG_VERSION" echo "" echo "Run 'just release::bump-workbench $VERSION' first, then commit the changes." exit 1 fi echo "✓ $PACKAGE_XML (workbench): $PKG_VERSION" echo "" echo "Version verification passed!" echo "" echo "Creating tag: $TAG" echo "" echo "This will trigger:" echo " - GitHub release with workbench archive" echo "" read -p "Continue? [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Aborted." exit 1 fi git tag -a "$TAG" -m "Release Robust MCP Bridge Workbench v{{version}}" git push origin "$TAG" echo "" echo "Tag $TAG created and pushed!" echo "Watch the release at: https://github.com/spkane/freecad-addon-robust-mcp-server/actions" # ============================================================================= # Tag Information Commands # ============================================================================= # List all release tags grouped by component list-tags: #!/usr/bin/env bash echo "=== Robust MCP Server Releases ===" git tag -l 'robust-mcp-server-v*' --sort=-v:refname | head -10 echo "" echo "=== Robust MCP Bridge Workbench Releases ===" git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -10 # Show the latest version of each component latest-versions: #!/usr/bin/env bash echo "Latest versions:" echo "" SERVER_TAG=$(git tag -l 'robust-mcp-server-v*' --sort=-v:refname | head -n1) WORKBENCH_TAG=$(git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -n1) echo " Robust MCP Server: ${SERVER_TAG:-none}" echo " MCP Bridge Workbench: ${WORKBENCH_TAG:-none}" # Show commits since the last release of a component changes-since component: #!/usr/bin/env bash set -euo pipefail case "{{component}}" in mcp-server|server) PREFIX="robust-mcp-server-v" PATHS="src/freecad_mcp pyproject.toml Dockerfile" ;; workbench) PREFIX="robust-mcp-workbench-v" PATHS="addon/FreecadRobustMCPBridge" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, server, workbench" exit 1 ;; esac LATEST_TAG=$(git tag -l "${PREFIX}*" --sort=-v:refname | head -1) if [ -z "$LATEST_TAG" ]; then echo "No previous releases found for {{component}}" echo "Showing all commits for relevant paths:" git log --oneline -- $PATHS | head -20 else echo "Changes since $LATEST_TAG:" echo "" git log --oneline "$LATEST_TAG"..HEAD -- $PATHS fi # ============================================================================= # Tag Management # ============================================================================= # Delete a release tag (local and remote) delete-tag tag: #!/usr/bin/env bash set -euo pipefail echo "This will delete the tag '{{tag}}' both locally and from the remote." echo "" read -p "Are you sure? [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Aborted." exit 1 fi # Delete local tag if git tag -l '{{tag}}' | grep -F -q '{{tag}}'; then git tag -d '{{tag}}' echo "Deleted local tag: {{tag}}" else echo "Local tag '{{tag}}' not found (may already be deleted)" fi # Delete remote tag if git ls-remote --tags origin | grep -F -q 'refs/tags/{{tag}}'; then git push origin --delete '{{tag}}' echo "Deleted remote tag: {{tag}}" else echo "Remote tag '{{tag}}' not found (may already be deleted)" fi echo "" echo "Tag '{{tag}}' deleted." # Show information about a release (useful before rollback) show-release-info tag: #!/usr/bin/env bash set -euo pipefail TAG="{{tag}}" echo "==========================================" echo "Release Information: $TAG" echo "==========================================" echo "" # Check if tag exists locally if git tag -l "$TAG" | grep -qF "$TAG"; then echo "Local tag: EXISTS" COMMIT=$(git rev-parse "$TAG" 2>/dev/null) echo " Commit: $COMMIT" echo " Date: $(git log -1 --format=%ai "$TAG")" echo " Author: $(git log -1 --format='%an <%ae>' "$TAG")" else echo "Local tag: NOT FOUND" fi # Check if tag exists on remote if git ls-remote --tags origin | grep -qF "refs/tags/$TAG"; then echo "Remote tag: EXISTS" else echo "Remote tag: NOT FOUND" fi echo "" # Check which branches contain the tag echo "Branches containing this tag:" BRANCHES=$(git branch -a --contains "$TAG" 2>/dev/null || true) if [ -n "$BRANCHES" ]; then echo "$BRANCHES" | sed 's/^/ /' else echo " (none or tag not found)" fi echo "" # Check if GitHub Release exists echo "GitHub Release:" if command -v gh &> /dev/null; then if gh release view "$TAG" --json tagName,name,isDraft,isPrerelease,createdAt &>/dev/null; then gh release view "$TAG" --json tagName,name,isDraft,isPrerelease,createdAt | \ jq -r '" Name: \(.name)\n Draft: \(.isDraft)\n Prerelease: \(.isPrerelease)\n Created: \(.createdAt)"' echo "" echo " Assets:" gh release view "$TAG" --json assets | jq -r '.assets[].name' | sed 's/^/ - /' else echo " NOT FOUND (no GitHub Release for this tag)" fi else echo " (gh CLI not installed - cannot check)" fi echo "" # Determine component type and show additional info case "$TAG" in robust-mcp-server-v*) VERSION="${TAG#robust-mcp-server-v}" echo "Component: Robust MCP Server" echo "Version: $VERSION" echo "" echo "Published to:" echo " - PyPI: https://pypi.org/project/freecad-robust-mcp/$VERSION/" echo " - Docker Hub: spkane/freecad-robust-mcp:$VERSION" echo "" echo "NOTE: PyPI packages cannot be fully deleted, only yanked." echo " Docker tags can be deleted from Docker Hub." ;; robust-mcp-workbench-v*) VERSION="${TAG#robust-mcp-workbench-v}" echo "Component: Robust MCP Bridge Workbench" echo "Version: $VERSION" ;; *) echo "Component: Unknown (not a recognized release tag format)" ;; esac # Rollback a release (delete tag and GitHub Release) rollback-release tag: #!/usr/bin/env bash set -euo pipefail TAG="{{tag}}" echo "==========================================" echo "ROLLBACK RELEASE: $TAG" echo "==========================================" echo "" # Determine component type and version COMPONENT="" VERSION="" case "$TAG" in robust-mcp-server-v*) COMPONENT="mcp-server" VERSION="${TAG#robust-mcp-server-v}" ;; robust-mcp-workbench-v*) COMPONENT="workbench" VERSION="${TAG#robust-mcp-workbench-v}" ;; *) COMPONENT="unknown" ;; esac # Show what will be affected echo "This will:" echo " 1. Delete the GitHub Release (if exists)" echo " 2. Delete the git tag (local and remote)" if [ "$COMPONENT" = "mcp-server" ]; then echo " 3. Optionally yank from PyPI" echo " 4. Provide Docker Hub cleanup instructions" fi echo "" # Check what exists LOCAL_TAG_EXISTS=false REMOTE_TAG_EXISTS=false GH_RELEASE_EXISTS=false if git tag -l "$TAG" | grep -qF "$TAG"; then LOCAL_TAG_EXISTS=true echo " Local tag: WILL BE DELETED" else echo " Local tag: not found (already deleted?)" fi if git ls-remote --tags origin | grep -qF "refs/tags/$TAG"; then REMOTE_TAG_EXISTS=true echo " Remote tag: WILL BE DELETED" else echo " Remote tag: not found (already deleted?)" fi if command -v gh &> /dev/null; then if gh release view "$TAG" &>/dev/null; then GH_RELEASE_EXISTS=true echo " GitHub Release: WILL BE DELETED" else echo " GitHub Release: not found (already deleted?)" fi else echo " GitHub Release: (gh CLI not installed - cannot check/delete)" fi echo "" # Component-specific info case "$COMPONENT" in mcp-server) echo "Component: Robust MCP Server v$VERSION" echo "" echo "IMPORTANT: MCP Server releases publish to multiple registries:" echo " - PyPI: Package cannot be deleted, only yanked (hidden from pip install)" echo " - Docker Hub: Tags can be deleted via web UI or API" echo "" ;; workbench) echo "Component: Robust MCP Bridge Workbench v$VERSION" echo "" echo "This release only creates a GitHub Release with archive files." echo "Rollback will fully clean up this release." echo "" ;; *) echo "WARNING: Unrecognized tag format. Proceeding with generic rollback." echo "" ;; esac # Nothing to do? if [ "$LOCAL_TAG_EXISTS" = false ] && [ "$REMOTE_TAG_EXISTS" = false ] && [ "$GH_RELEASE_EXISTS" = false ]; then echo "Nothing to rollback - tag and release not found." exit 0 fi read -p "Proceed with rollback? [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Aborted." exit 1 fi echo "" # Delete GitHub Release first (before tag, since release references tag) if [ "$GH_RELEASE_EXISTS" = true ]; then echo "Deleting GitHub Release..." if gh release delete "$TAG" --yes; then echo " ✓ GitHub Release deleted." else echo " WARNING: Failed to delete GitHub Release." fi fi # Delete remote tag if [ "$REMOTE_TAG_EXISTS" = true ]; then echo "Deleting remote tag..." if git push origin --delete "$TAG"; then echo " ✓ Remote tag deleted." else echo " WARNING: Failed to delete remote tag." fi fi # Delete local tag if [ "$LOCAL_TAG_EXISTS" = true ]; then echo "Deleting local tag..." if git tag -d "$TAG"; then echo " ✓ Local tag deleted." else echo " WARNING: Failed to delete local tag." fi fi echo "" echo "==========================================" echo "Git/GitHub Rollback complete!" echo "==========================================" # MCP Server: Provide PyPI and Docker cleanup instructions if [ "$COMPONENT" = "mcp-server" ]; then echo "" echo "--- PyPI Cleanup ---" echo "" echo "To yank version $VERSION from PyPI (hides from 'pip install'):" echo "" echo " 1. Go to: https://pypi.org/manage/project/freecad-robust-mcp/releases/" echo " 2. Click 'Options' → 'Yank'" echo " 3. Enter a reason (e.g., 'Released from wrong branch')" echo " 4. Confirm" echo "" echo "Note: Yanking hides the version from default pip install, but users" echo " who pin to this exact version can still install it." echo " Yanking can be undone from the same page." echo "" read -p "Open PyPI release page in browser? [y/N] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then PYPI_URL="https://pypi.org/manage/project/freecad-robust-mcp/releases/" if command -v open &> /dev/null; then open "$PYPI_URL" elif command -v xdg-open &> /dev/null; then xdg-open "$PYPI_URL" else echo "Could not open browser. Visit: $PYPI_URL" fi fi echo "" echo "--- Docker Hub Cleanup ---" echo "" echo "To delete Docker Hub tags:" echo "" echo " 1. Go to: https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" echo " 2. Find tag: $VERSION" echo " 3. Click the checkbox and 'Delete'" echo " 4. Also delete 'latest' tag if this was the latest release" echo "" read -p "Open Docker Hub tags page in browser? [y/N] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then DOCKER_URL="https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" if command -v open &> /dev/null; then open "$DOCKER_URL" elif command -v xdg-open &> /dev/null; then xdg-open "$DOCKER_URL" else echo "Could not open browser. Visit: $DOCKER_URL" fi fi fi echo "" echo "==========================================" echo "ROLLBACK SUMMARY" echo "==========================================" echo "" # Show what was done and what remains case "$COMPONENT" in mcp-server) echo "COMPLETED (automated):" echo " ✓ GitHub Release deleted" echo " ✓ Git tag deleted (local and remote)" echo "" echo "MANUAL STEPS REQUIRED:" echo " □ PyPI: Yank version $VERSION" echo " URL: https://pypi.org/manage/project/freecad-robust-mcp/releases/" echo "" echo " □ Docker Hub: Delete tag $VERSION" echo " URL: https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" echo "" echo " □ Docker Hub: Delete 'latest' tag (if this was the latest release)" echo " URL: https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" ;; workbench) echo "COMPLETED (automated):" echo " ✓ GitHub Release deleted" echo " ✓ Git tag deleted (local and remote)" echo "" echo "MANUAL STEPS REQUIRED:" echo " (none - rollback is complete!)" ;; *) echo "COMPLETED (automated):" echo " ✓ GitHub Release deleted (if existed)" echo " ✓ Git tag deleted (local and remote)" echo "" echo "MANUAL STEPS REQUIRED:" echo " (unknown component - verify no additional cleanup needed)" ;; esac echo "" # Delete only the GitHub Release (keep tag) delete-github-release tag: #!/usr/bin/env bash set -euo pipefail TAG="{{tag}}" if ! command -v gh &> /dev/null; then echo "ERROR: gh CLI is required but not installed." echo "Install: https://cli.github.com/" exit 1 fi if ! gh release view "$TAG" &>/dev/null; then echo "No GitHub Release found for tag: $TAG" exit 0 fi echo "This will delete the GitHub Release for: $TAG" echo "(The git tag will be preserved)" echo "" gh release view "$TAG" --json name,createdAt,assets | \ jq -r '"Release: \(.name)\nCreated: \(.createdAt)\nAssets: \(.assets | length)"' echo "" read -p "Delete this release? [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Aborted." exit 1 fi gh release delete "$TAG" --yes echo "GitHub Release deleted. Tag '$TAG' preserved." # ============================================================================= # Release Status # ============================================================================= # Show unreleased changes across all components status: #!/usr/bin/env bash set -euo pipefail echo "==========================================" echo "Release Status - Unreleased Changes" echo "==========================================" echo "" # Helper function to count changes count_changes() { local prefix="$1" local paths="$2" local latest_tag=$(git tag -l "${prefix}*" --sort=-v:refname | head -1) if [ -z "$latest_tag" ]; then # No releases yet, count all commits git log --oneline -- $paths 2>/dev/null | wc -l | tr -d ' ' else git log --oneline "$latest_tag"..HEAD -- $paths 2>/dev/null | wc -l | tr -d ' ' fi } # Robust MCP Server SERVER_CHANGES=$(count_changes "robust-mcp-server-v" "src/freecad_mcp pyproject.toml Dockerfile") SERVER_TAG=$(git tag -l 'robust-mcp-server-v*' --sort=-v:refname | head -1) if [ "$SERVER_CHANGES" -gt 0 ]; then echo "Robust MCP Server: $SERVER_CHANGES unreleased commit(s)" echo " Latest: ${SERVER_TAG:-none}" echo " View: just release::changes-since mcp-server" else echo "Robust MCP Server: up to date (${SERVER_TAG:-no releases})" fi echo "" # Robust MCP Bridge Workbench WORKBENCH_CHANGES=$(count_changes "robust-mcp-workbench-v" "addon/FreecadRobustMCPBridge") WORKBENCH_TAG=$(git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -1) if [ "$WORKBENCH_CHANGES" -gt 0 ]; then echo "Robust MCP Bridge Workbench: $WORKBENCH_CHANGES unreleased commit(s)" echo " Latest: ${WORKBENCH_TAG:-none}" echo " View: just release::changes-since workbench" else echo "Robust MCP Bridge Workbench: up to date (${WORKBENCH_TAG:-no releases})" fi echo "" echo "==========================================" # ============================================================================= # Changelog / Release Notes Helpers # ============================================================================= # Draft release notes for a component by extracting conventional commits since last release draft-notes component: #!/usr/bin/env bash set -euo pipefail case "{{component}}" in mcp-server|server) PREFIX="robust-mcp-server-v" PATHS="src/freecad_mcp pyproject.toml Dockerfile" COMPONENT_NAME="Robust MCP Server" ;; workbench) PREFIX="robust-mcp-workbench-v" PATHS="addon/FreecadRobustMCPBridge" COMPONENT_NAME="Robust MCP Bridge Workbench" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, server, workbench" exit 1 ;; esac LATEST_TAG=$(git tag -l "${PREFIX}*" --sort=-v:refname | head -1) echo "# Draft Release Notes for $COMPONENT_NAME" echo "" if [ -z "$LATEST_TAG" ]; then echo "No previous releases found. Showing all commits for component paths." echo "" REV_RANGE="HEAD" else echo "Changes since: $LATEST_TAG" echo "" REV_RANGE="${LATEST_TAG}..HEAD" fi # Get commits and categorize by conventional commit type echo "## Added" echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ feat(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true echo "" echo "## Changed" echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ (refactor|perf|style)(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true echo "" echo "## Fixed" echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ fix(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true echo "" echo "## Documentation" echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ docs(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true echo "" echo "## Other Changes" echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -ivE "^[a-f0-9]+ (feat|fix|refactor|perf|style|docs|test|ci|build|chore)(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true echo "" echo "---" echo "" echo "## All Commits (chronological)" echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | sed -E 's/^[a-f0-9]+ /- /' || echo "(no commits)" # Extract release notes section for a specific component version (for GitHub Release body) # Reads from component-specific RELEASE_NOTES.md files extract-changelog component version: #!/usr/bin/env bash set -euo pipefail # Map component to RELEASE_NOTES.md path case "{{component}}" in mcp-server|server) RELEASE_NOTES="{{project_root}}/src/freecad_mcp/RELEASE_NOTES.md" ;; workbench) RELEASE_NOTES="{{project_root}}/addon/FreecadRobustMCPBridge/RELEASE_NOTES.md" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, server, workbench" exit 1 ;; esac if [ ! -f "$RELEASE_NOTES" ]; then echo "No RELEASE_NOTES.md found at: $RELEASE_NOTES" exit 0 fi # Extract section for this version # Format: ## Version X.Y.Z (date) awk -v version="{{version}}" ' BEGIN { found=0 } /^## Version / { if (found) exit if (index($0, version) > 0) { found=1; next } } found { print } ' "$RELEASE_NOTES" # ============================================================================= # Dry Run Commands (preview without pushing) # ============================================================================= # Preview what a release tag would look like (no actual tag created) dry-run-tag component version: #!/usr/bin/env bash # Validate version format (SemVer 2.0) if [[ ! "{{version}}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "Error: Invalid version format '{{version}}'" echo "Expected: X.Y.Z or X.Y.Z-prerelease (e.g., 1.0.0, 1.0.0-alpha, 1.0.0-beta.1)" exit 1 fi case "{{component}}" in mcp-server|server) TAG="robust-mcp-server-v{{version}}" echo "Would create tag: $TAG" echo "Triggers: PyPI, Docker Hub, GitHub Release" ;; workbench) TAG="robust-mcp-workbench-v{{version}}" echo "Would create tag: $TAG" echo "Triggers: GitHub Release with workbench archive" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, server, workbench" exit 1 ;; esac # ============================================================================= # FreeCAD Wiki Update Helpers # ============================================================================= # Helper to update FreeCAD wiki for a component (copies content to clipboard and opens edit page) wiki-update component: #!/usr/bin/env bash set -euo pipefail case "{{component}}" in workbench|bridge) WIKI_SOURCE="{{project_root}}/addon/FreecadRobustMCPBridge/wiki-source.txt" WIKI_PAGE="Robust_MCP_Bridge_Workbench" COMPONENT_NAME="Robust MCP Bridge Workbench" ;; *) echo "Unknown component: {{component}}" echo "Valid options: workbench (or bridge)" exit 1 ;; esac WIKI_URL="https://wiki.freecad.org/index.php?title=${WIKI_PAGE}&action=edit" echo "==========================================" echo "FreeCAD Wiki Update Helper" echo "==========================================" echo "" echo "Component: $COMPONENT_NAME" echo "Wiki Page: https://wiki.freecad.org/${WIKI_PAGE}" echo "" # Check if wiki-source.txt exists if [ ! -f "$WIKI_SOURCE" ]; then echo "ERROR: Wiki source file not found: $WIKI_SOURCE" exit 1 fi # Extract current version from wiki-source.txt CURRENT_VERSION=$(grep -o '|Version=[^|]*' "$WIKI_SOURCE" | cut -d= -f2 | tr -d '\n') CURRENT_DATE=$(grep -o '|Date=[^|]*' "$WIKI_SOURCE" | cut -d= -f2 | tr -d '\n') echo "Current version in wiki-source.txt:" echo " Version: $CURRENT_VERSION" echo " Date: $CURRENT_DATE" echo "" # Try to copy to clipboard (platform-specific) COPIED=false if command -v pbcopy &> /dev/null; then # macOS cat "$WIKI_SOURCE" | pbcopy COPIED=true echo "Content copied to clipboard (macOS pbcopy)" elif command -v xclip &> /dev/null; then # Linux with xclip cat "$WIKI_SOURCE" | xclip -selection clipboard COPIED=true echo "Content copied to clipboard (xclip)" elif command -v xsel &> /dev/null; then # Linux with xsel cat "$WIKI_SOURCE" | xsel --clipboard --input COPIED=true echo "Content copied to clipboard (xsel)" elif command -v wl-copy &> /dev/null; then # Wayland cat "$WIKI_SOURCE" | wl-copy COPIED=true echo "Content copied to clipboard (wl-copy)" else echo "NOTE: No clipboard utility found (pbcopy, xclip, xsel, wl-copy)" echo " You'll need to manually copy the content." fi echo "" echo "==========================================" echo "INSTRUCTIONS" echo "==========================================" echo "" echo "1. The wiki edit page will open in your browser" echo "2. Log in to your FreeCAD wiki account if prompted" echo "3. Select ALL content in the edit box (Ctrl+A / Cmd+A)" echo "4. Paste the new content (Ctrl+V / Cmd+V)" echo "5. Add an edit summary like: 'Update to version $CURRENT_VERSION'" echo "6. Click 'Show preview' to verify changes" echo "7. Click 'Save changes' when satisfied" echo "" # Ask for confirmation before opening browser read -p "Open wiki edit page in browser? [Y/n] " -n 1 -r echo if [[ $REPLY =~ ^[Nn]$ ]]; then echo "" echo "Aborted. You can manually visit:" echo " $WIKI_URL" echo "" echo "Wiki source file location:" echo " $WIKI_SOURCE" exit 0 fi # Open the wiki edit page in browser (platform-specific) if command -v open &> /dev/null; then # macOS open "$WIKI_URL" elif command -v xdg-open &> /dev/null; then # Linux xdg-open "$WIKI_URL" elif command -v wslview &> /dev/null; then # WSL wslview "$WIKI_URL" else echo "Could not open browser automatically." echo "Please manually visit: $WIKI_URL" fi echo "" echo "Browser opened to: $WIKI_URL" if [ "$COPIED" = true ]; then echo "" echo "The wiki content is in your clipboard - ready to paste!" fi # Show the wiki source content for a component (for review) wiki-show component: #!/usr/bin/env bash set -euo pipefail case "{{component}}" in workbench|bridge) WIKI_SOURCE="{{project_root}}/addon/FreecadRobustMCPBridge/wiki-source.txt" COMPONENT_NAME="Robust MCP Bridge Workbench" ;; *) echo "Unknown component: {{component}}" echo "Valid options: workbench (or bridge)" exit 1 ;; esac echo "==========================================" echo "Wiki Source: $COMPONENT_NAME" echo "==========================================" echo "File: $WIKI_SOURCE" echo "" # Show version info CURRENT_VERSION=$(grep -o '|Version=[^|]*' "$WIKI_SOURCE" | cut -d= -f2 | tr -d '\n') CURRENT_DATE=$(grep -o '|Date=[^|]*' "$WIKI_SOURCE" | cut -d= -f2 | tr -d '\n') echo "Version: $CURRENT_VERSION" echo "Date: $CURRENT_DATE" echo "" echo "==========================================" echo "" cat "$WIKI_SOURCE" # Diff the local wiki source against the current wiki page (requires curl) wiki-diff component: #!/usr/bin/env bash set -euo pipefail case "{{component}}" in workbench|bridge) WIKI_SOURCE="{{project_root}}/addon/FreecadRobustMCPBridge/wiki-source.txt" WIKI_PAGE="Robust_MCP_Bridge_Workbench" COMPONENT_NAME="Robust MCP Bridge Workbench" ;; *) echo "Unknown component: {{component}}" echo "Valid options: workbench (or bridge)" exit 1 ;; esac WIKI_RAW_URL="https://wiki.freecad.org/index.php?title=${WIKI_PAGE}&action=raw" echo "Fetching current wiki content for $COMPONENT_NAME..." echo "" # Create temp file for wiki content TEMP_WIKI=$(mktemp) trap "rm -f $TEMP_WIKI" EXIT # Fetch current wiki content if ! curl -sS "$WIKI_RAW_URL" > "$TEMP_WIKI" 2>/dev/null; then echo "ERROR: Could not fetch wiki page. The page may not exist yet." echo "URL: $WIKI_RAW_URL" exit 1 fi # Check if page exists (MediaWiki returns specific content for missing pages) if grep -q "There is currently no text in this page" "$TEMP_WIKI"; then echo "NOTE: Wiki page does not exist yet." echo "This will be a new page creation." echo "" echo "Local content to be uploaded:" echo "==========================================" head -20 "$WIKI_SOURCE" echo "..." echo "(truncated - run 'just release::wiki-show {{component}}' to see full content)" exit 0 fi echo "Comparing local wiki-source.txt with live wiki page..." echo "" # Show diff if diff -u "$TEMP_WIKI" "$WIKI_SOURCE"; then echo "No differences found - wiki is up to date!" else echo "" echo "==========================================" echo "Differences found (above)" echo "Run 'just release::wiki-update {{component}}' to update the wiki" fi