From 01f9f155172ba204a1a1e9aec1ea4e6290b3fc30 Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Thu, 19 Mar 2026 16:58:47 -0400 Subject: [PATCH 1/4] Add python script for extracting package version --- findVersion.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 findVersion.py diff --git a/findVersion.py b/findVersion.py new file mode 100644 index 0000000..9f8cced --- /dev/null +++ b/findVersion.py @@ -0,0 +1,38 @@ +import ast +import sys +import os + +def findVersion(repo_name): + # Python package directories use underscores instead of hyphens + package_dir = repo_name.replace('-', '_') + file_path = os.path.join(package_dir, "__init__.py") + + if not os.path.exists(file_path): + print(f"Error: Expected file at {file_path} does not exist.", file=sys.stderr) + return None + + with open(file_path, "r") as f: + tree = ast.parse(f.read()) + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == "__version__": + # Extract and return the string value + return node.value.s if hasattr(node.value, "s") else node.value.value + + return None + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Error: Repository name argument is missing.", file=sys.stderr) + sys.exit(1) + + repo_name = sys.argv[1] + version = findVersion(repo_name) + + if version: + print(version) + sys.exit(0) + else: + print(f"Could not find __version__ string inside {repo_name}/__init__.py", file=sys.stderr) + sys.exit(1) From b4f0a04b521a1ca133db198a6a4192b3e0ba1451 Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Thu, 19 Mar 2026 17:35:42 -0400 Subject: [PATCH 2/4] Add action to automate release when version updated --- .github/workflows/autoRelease.yaml | 99 ++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/autoRelease.yaml diff --git a/.github/workflows/autoRelease.yaml b/.github/workflows/autoRelease.yaml new file mode 100644 index 0000000..ced2b88 --- /dev/null +++ b/.github/workflows/autoRelease.yaml @@ -0,0 +1,99 @@ +name: Auto-Release on Version Bump + +on: + push: + branches: + - main + paths: + - '**/__init__.py' # Only run this check if an __init__.py file is modified, which is where the version to be defined + +# We need write permissions to create tags and releases +permissions: + contents: write + +jobs: + check-and-release: + name: Check Version and Create Release + runs-on: ubuntu-latest + + steps: + - name: Ensure GitHub CLI is Installed + run: | + if ! command -v gh &> /dev/null; then + echo "GitHub CLI not found. Installing via apt..." + # 1. Create keyring directory + sudo mkdir -p -m 755 /etc/apt/keyrings + # 2. Download GitHub's GPG key + wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null + # 3. Add the GitHub repo to sources + sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + # 4. Update and install + sudo apt update + sudo apt install gh -y + else + echo "GitHub CLI is already installed." + fi + + # The checkout action is needed to access the repository files, including __init__.py for version extraction + - name: Checkout Package Repository + uses: actions/checkout@v4 + + - name: Extract Version from __init__.py + id: get_version + run: | + # Strip the 'org-name/' prefix to get just the package name + REPO_NAME="${GITHUB_REPOSITORY#*/}" + + # Execute the central script, passing the clean repo name + wget https://raw.githubusercontent.com/mdolab/.github/refs/heads/AutoReleaseAction/findVersion.py + VERSION=$(python findVersion.py "$REPO_NAME") + + echo "Found version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Get Latest Release Tag + id: get_latest_release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Fetch the latest release, defaulting to v0.0.0 if none exists + # -R ensures gh cli context is strictly bound to this repository + LATEST_TAG=$(gh release view -R $GITHUB_REPOSITORY --json tagName -q .tagName 2>/dev/null || echo "v0.0.0") + + # Strip the 'v' prefix for a clean comparison + LATEST_VERSION="${LATEST_TAG#v}" + + echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT + + - name: Compare Versions and Create Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CURRENT_VERSION: ${{ steps.get_version.outputs.version }} + LATEST_VERSION: ${{ steps.get_latest_release.outputs.latest_version }} + run: | + echo "Latest published release: $LATEST_VERSION" + echo "Current file version: $CURRENT_VERSION" + + if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then + echo "Versions are identical. No release needed." + else + # Use 'sort -V' to determine which version string is the highest + HIGHEST_VERSION=$(printf '%s\n' "$LATEST_VERSION" "$CURRENT_VERSION" | sort -V | tail -n1) + + if [ "$HIGHEST_VERSION" != "$CURRENT_VERSION" ]; then + # This catches typos or regressions where the file version is LOWER than the release + echo "::warning::Current version ($CURRENT_VERSION) is lower than the latest release ($LATEST_VERSION). Skipping release." + + else + echo "Valid version bump detected ($LATEST_VERSION -> $CURRENT_VERSION)! Creating release..." + + gh release create "v$CURRENT_VERSION" \ + -R $GITHUB_REPOSITORY \ + --title "v$CURRENT_VERSION" \ + --generate-notes \ + --target main + + echo "Release created successfully." + fi + fi From 0917ab41ffe42110ac4137aa2cc2bae44bde1bed Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Thu, 19 Mar 2026 17:46:39 -0400 Subject: [PATCH 3/4] Fix `on` --- .github/workflows/autoRelease.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/autoRelease.yaml b/.github/workflows/autoRelease.yaml index ced2b88..0dc4930 100644 --- a/.github/workflows/autoRelease.yaml +++ b/.github/workflows/autoRelease.yaml @@ -1,11 +1,10 @@ name: Auto-Release on Version Bump on: - push: - branches: - - main - paths: - - '**/__init__.py' # Only run this check if an __init__.py file is modified, which is where the version to be defined + workflow_call: + secrets: + GITHUB_TOKEN: + required: true # We need write permissions to create tags and releases permissions: From 178110cab701655c12423ff938cc6df77785a942 Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Thu, 19 Mar 2026 17:54:05 -0400 Subject: [PATCH 4/4] Clean up secrets --- .github/workflows/autoRelease.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/autoRelease.yaml b/.github/workflows/autoRelease.yaml index 0dc4930..d175b7d 100644 --- a/.github/workflows/autoRelease.yaml +++ b/.github/workflows/autoRelease.yaml @@ -1,10 +1,6 @@ name: Auto-Release on Version Bump -on: - workflow_call: - secrets: - GITHUB_TOKEN: - required: true +on: workflow_call # We need write permissions to create tags and releases permissions: