# 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) # - macro-cut-object-for-magnets-vX.Y.Z # - macro-multi-export-vX.Y.Z # # 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/FreecadRobustMCP/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 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" # Private helper recipe for bumping macro versions # Parameters: macro_dir, macro_name, macro_file_basename, readme_basename, tag_command, version [private] _bump-macro macro_dir macro_name macro_file_basename readme_basename tag_command version: #!/usr/bin/env bash set -euo pipefail VERSION="{{version}}" TODAY=$(date +%Y-%m-%d) MACRO_DIR="{{project_root}}/macros/{{macro_dir}}" MACRO_NAME="{{macro_name}}" # 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 $MACRO_NAME Macro to version: $VERSION (date: $TODAY)" echo "" # Update __Version__ and __Date__ in .FCMacro file MACRO_FILE="$MACRO_DIR/{{macro_file_basename}}" if [ -f "$MACRO_FILE" ]; then sed "s/^__Version__ = [\"'].*[\"']/__Version__ = \"${VERSION}\"/" "$MACRO_FILE" > "$MACRO_FILE.tmp" && mv "$MACRO_FILE.tmp" "$MACRO_FILE" sed "s/^__Date__ = [\"'].*[\"']/__Date__ = \"${TODAY}\"/" "$MACRO_FILE" > "$MACRO_FILE.tmp" && mv "$MACRO_FILE.tmp" "$MACRO_FILE" echo "Updated $MACRO_FILE:" grep -E "^__(Version|Date)__" "$MACRO_FILE" else echo "ERROR: File not found: $MACRO_FILE" exit 1 fi # Update README version line README_FILE="$MACRO_DIR/{{readme_basename}}" if [ -f "$README_FILE" ]; then sed "s/^\*\*Version:\*\* .*/\*\*Version:\*\* ${VERSION}/" "$README_FILE" > "$README_FILE.tmp" && mv "$README_FILE.tmp" "$README_FILE" echo "" echo "Updated $README_FILE:" grep "Version:" "$README_FILE" | head -1 else echo "WARNING: File not found: $README_FILE" fi # Update wiki-source.txt version and date WIKI_FILE="$MACRO_DIR/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 package.xml macro version PACKAGE_XML="{{project_root}}/package.xml" if [ -f "$PACKAGE_XML" ]; then awk -v name="$MACRO_NAME" -v version="$VERSION" -v date="$TODAY" ' // { in_macro=1 } /<\/macro>/ { in_macro=0; found_name=0 } in_macro && /.*<\/name>/ { if (index($0, name) > 0) found_name=1 } in_macro && found_name && // { gsub(/[^<]*<\/version>/, "" version "") } in_macro && found_name && // { gsub(/[^<]*<\/date>/, "" date "") } { print } ' "$PACKAGE_XML" > "$PACKAGE_XML.tmp" && mv "$PACKAGE_XML.tmp" "$PACKAGE_XML" echo "" echo "Updated $PACKAGE_XML ($MACRO_NAME section):" grep -A4 ">$MACRO_NAME<" "$PACKAGE_XML" 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 $MACRO_NAME macro to $VERSION'" echo " 3. Tag and release: just release::{{tag_command}} $VERSION" # Bump the Cut Object for Magnets macro version in all source files bump-macro-magnets version: (_bump-macro "Cut_Object_for_Magnets" "Cut Object for Magnets" "CutObjectForMagnets.FCMacro" "README-CutObjectForMagnets.md" "tag-macro-magnets" version) # Bump the Multi Export macro version in all source files bump-macro-export version: (_bump-macro "Multi_Export" "Multi Export" "MultiExport.FCMacro" "README-MultiExport.md" "tag-macro-export" 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 TAG="robust-mcp-server-v{{version}}" # Validate version format 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)" exit 1 fi # Check for uncommitted changes if ! git diff --quiet HEAD; then echo "ERROR: You have uncommitted changes. Please commit or stash them first." exit 1 fi 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-robust-mcp-and-more/actions" # Create and push a release tag for the Robust MCP Bridge workbench tag-workbench version: #!/usr/bin/env bash set -euo pipefail TAG="robust-mcp-workbench-v{{version}}" VERSION="{{version}}" if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "ERROR: Version '$VERSION' is not valid semver" exit 1 fi # Check for uncommitted changes if ! git diff --quiet HEAD; then echo "ERROR: You have uncommitted changes. Please commit or stash them first." exit 1 fi # Verify version in source files matches tag version echo "Verifying version in source files..." # Check __init__.py INIT_FILE="{{project_root}}/addon/FreecadRobustMCP/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 # 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 "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-robust-mcp-and-more/actions" # Private helper recipe for tagging macro releases # Parameters: macro_dir, macro_name, macro_file_basename, tag_prefix, bump_command, tag_message, version [private] _tag-macro macro_dir macro_name macro_file_basename tag_prefix bump_command tag_message version: #!/usr/bin/env bash set -euo pipefail VERSION="{{version}}" TAG="{{tag_prefix}}v{{version}}" MACRO_DIR="{{project_root}}/macros/{{macro_dir}}" MACRO_NAME="{{macro_name}}" # 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" exit 1 fi # Check for uncommitted changes if ! git diff --quiet HEAD; then echo "ERROR: You have uncommitted changes. Please commit or stash them first." exit 1 fi # Verify version in source files matches tag version echo "Verifying version in source files..." # Check .FCMacro file MACRO_FILE="$MACRO_DIR/{{macro_file_basename}}" MACRO_VERSION=$(grep -o '__Version__ = "[^"]*"' "$MACRO_FILE" | cut -d'"' -f2) if [ "$MACRO_VERSION" != "$VERSION" ]; then echo "ERROR: Version mismatch in $MACRO_FILE" echo " Expected: $VERSION" echo " Found: $MACRO_VERSION" echo "" echo "Run 'just release::{{bump_command}} $VERSION' first, then commit the changes." exit 1 fi # Check package.xml PACKAGE_XML="{{project_root}}/package.xml" PKG_VERSION=$(awk -v name="$MACRO_NAME" ' // { in_macro=1 } /<\/macro>/ { in_macro=0; found_name=0 } in_macro && index($0, name) > 0 { found_name=1 } in_macro && found_name && // { gsub(/.*/, ""); gsub(/<\/version>.*/, ""); print; exit } ' "$PACKAGE_XML") if [ "$PKG_VERSION" != "$VERSION" ]; then echo "ERROR: Version mismatch in $PACKAGE_XML ($MACRO_NAME section)" echo " Expected: $VERSION" echo " Found: $PKG_VERSION" echo "" echo "Run 'just release::{{bump_command}} $VERSION' first, then commit the changes." exit 1 fi echo "Version verification passed!" echo "" echo "Creating tag: $TAG" echo "" echo "This will trigger:" echo " - GitHub release with macro 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 "{{tag_message}} v{{version}}" git push origin "$TAG" echo "" echo "Tag $TAG created and pushed!" echo "Watch the release at: https://github.com/spkane/freecad-robust-mcp-and-more/actions" # Create and push a release tag for the Cut Object for Magnets macro tag-macro-magnets version: (_tag-macro "Cut_Object_for_Magnets" "Cut Object for Magnets" "CutObjectForMagnets.FCMacro" "macro-cut-object-for-magnets-" "bump-macro-magnets" "Release Cut Object for Magnets Macro" version) # Create and push a release tag for the Multi Export macro tag-macro-export version: (_tag-macro "Multi_Export" "Multi Export" "MultiExport.FCMacro" "macro-multi-export-" "bump-macro-export" "Release Multi Export Macro" version) # ============================================================================= # 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 "=== MCP Workbench Releases ===" git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -10 echo "" echo "=== Cut Object for Magnets Macro Releases ===" git tag -l 'macro-cut-object-for-magnets-v*' --sort=-v:refname | head -10 echo "" echo "=== Multi Export Macro Releases ===" git tag -l 'macro-multi-export-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) MAGNETS_TAG=$(git tag -l 'macro-cut-object-for-magnets-v*' --sort=-v:refname | head -n1) EXPORT_TAG=$(git tag -l 'macro-multi-export-v*' --sort=-v:refname | head -n1) echo " Robust MCP Server: ${SERVER_TAG:-none}" echo " Robust MCP Workbench: ${WORKBENCH_TAG:-none}" echo " Macro Magnets: ${MAGNETS_TAG:-none}" echo " Macro Export: ${EXPORT_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/FreecadRobustMCP" ;; macro-magnets|magnets) PREFIX="macro-cut-object-for-magnets-v" PATHS="macros/Cut_Object_for_Magnets" ;; macro-export|export) PREFIX="macro-multi-export-v" PATHS="macros/Multi_Export" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, workbench, macro-magnets, macro-export" 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." # ============================================================================= # 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/FreecadRobustMCP") 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 "" # Macro Magnets MAGNETS_CHANGES=$(count_changes "macro-cut-object-for-magnets-v" "macros/Cut_Object_for_Magnets") MAGNETS_TAG=$(git tag -l 'macro-cut-object-for-magnets-v*' --sort=-v:refname | head -1) if [ "$MAGNETS_CHANGES" -gt 0 ]; then echo "Macro (Cut Object for Magnets): $MAGNETS_CHANGES unreleased commit(s)" echo " Latest: ${MAGNETS_TAG:-none}" echo " View: just release::changes-since macro-magnets" else echo "Macro (Cut Object for Magnets): up to date (${MAGNETS_TAG:-no releases})" fi echo "" # Macro Export EXPORT_CHANGES=$(count_changes "macro-multi-export-v" "macros/Multi_Export") EXPORT_TAG=$(git tag -l 'macro-multi-export-v*' --sort=-v:refname | head -1) if [ "$EXPORT_CHANGES" -gt 0 ]; then echo "Macro (Multi Export): $EXPORT_CHANGES unreleased commit(s)" echo " Latest: ${EXPORT_TAG:-none}" echo " View: just release::changes-since macro-export" else echo "Macro (Multi Export): up to date (${EXPORT_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/FreecadRobustMCP" COMPONENT_NAME="Robust MCP Bridge Workbench" ;; macro-magnets|magnets) PREFIX="macro-cut-object-for-magnets-v" PATHS="macros/Cut_Object_for_Magnets" COMPONENT_NAME="Cut Object for Magnets Macro" ;; macro-export|export) PREFIX="macro-multi-export-v" PATHS="macros/Multi_Export" COMPONENT_NAME="Multi Export Macro" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, workbench, macro-magnets, macro-export" 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 changelog section for a specific component version (for GitHub Release body) extract-changelog component version: #!/usr/bin/env bash set -euo pipefail # Match header exactly as it appears in CHANGELOG.md case "{{component}}" in mcp-server|server) HEADER="### Robust MCP Server v{{version}}" ;; workbench) HEADER="### Robust MCP Bridge Workbench v{{version}}" ;; macro-magnets|magnets) HEADER="### Cut Object for Magnets Macro v{{version}}" ;; macro-export|export) HEADER="### Multi Export Macro v{{version}}" ;; *) echo "Unknown component: {{component}}" exit 1 ;; esac CHANGELOG="{{project_root}}/CHANGELOG.md" # Extract section between this version header and the next component header or separator # Only exit on: "---" separator OR "### " followed by component name (capital letter) # This allows #### Added, #### Changed, etc. to be included awk -v header="$HEADER" ' BEGIN { found=0 } $0 == header || $0 == header " " { found=1; next } found && /^---$/ { exit } found && /^### [A-Z]/ { exit } found { print } ' "$CHANGELOG" # ============================================================================= # 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" ;; macro-magnets|magnets) TAG="macro-cut-object-for-magnets-v{{version}}" echo "Would create tag: $TAG" echo "Triggers: GitHub Release with macro archive" ;; macro-export|export) TAG="macro-multi-export-v{{version}}" echo "Would create tag: $TAG" echo "Triggers: GitHub Release with macro archive" ;; *) echo "Unknown component: {{component}}" echo "Valid: mcp-server, workbench, macro-magnets, macro-export" exit 1 ;; esac # ============================================================================= # FreeCAD Wiki Update Helpers # ============================================================================= # Helper to update FreeCAD wiki for a macro (copies content to clipboard and opens edit page) wiki-update macro: #!/usr/bin/env bash set -euo pipefail case "{{macro}}" in macro-magnets|magnets|cut) WIKI_SOURCE="{{project_root}}/macros/Cut_Object_for_Magnets/wiki-source.txt" WIKI_PAGE="Macro_Cut_Object_for_Magnets" MACRO_NAME="Cut Object for Magnets" ;; macro-export|export|multi) WIKI_SOURCE="{{project_root}}/macros/Multi_Export/wiki-source.txt" WIKI_PAGE="Macro_Multi_Export" MACRO_NAME="Multi Export" ;; *) echo "Unknown macro: {{macro}}" echo "Valid options: macro-magnets (or magnets, cut), macro-export (or export, multi)" 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 "Macro: $MACRO_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 macro (for review) wiki-show macro: #!/usr/bin/env bash set -euo pipefail case "{{macro}}" in macro-magnets|magnets|cut) WIKI_SOURCE="{{project_root}}/macros/Cut_Object_for_Magnets/wiki-source.txt" MACRO_NAME="Cut Object for Magnets" ;; macro-export|export|multi) WIKI_SOURCE="{{project_root}}/macros/Multi_Export/wiki-source.txt" MACRO_NAME="Multi Export" ;; *) echo "Unknown macro: {{macro}}" echo "Valid options: macro-magnets (or magnets, cut), macro-export (or export, multi)" exit 1 ;; esac echo "==========================================" echo "Wiki Source: $MACRO_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 macro: #!/usr/bin/env bash set -euo pipefail case "{{macro}}" in macro-magnets|magnets|cut) WIKI_SOURCE="{{project_root}}/macros/Cut_Object_for_Magnets/wiki-source.txt" WIKI_PAGE="Macro_Cut_Object_for_Magnets" MACRO_NAME="Cut Object for Magnets" ;; macro-export|export|multi) WIKI_SOURCE="{{project_root}}/macros/Multi_Export/wiki-source.txt" WIKI_PAGE="Macro_Multi_Export" MACRO_NAME="Multi Export" ;; *) echo "Unknown macro: {{macro}}" echo "Valid options: macro-magnets (or magnets, cut), macro-export (or export, multi)" exit 1 ;; esac WIKI_RAW_URL="https://wiki.freecad.org/index.php?title=${WIKI_PAGE}&action=raw" echo "Fetching current wiki content for $MACRO_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 {{macro}}' 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 {{macro}}' to update the wiki" fi