Skip to content

Conversation

@ImJustHenry
Copy link

@ImJustHenry ImJustHenry commented Nov 30, 2025

Changes

  • Added a Python script (tools/bump_version.py) to automate version bumps based on PR labels (patch, minor, major, release).
  • Added a GitHub Actions workflow (.github/workflows/version-bump.yml) that triggers on PR labeling.
  • For release labels, the workflow removes the -draft suffix from the version.
  • Non-release bumps retain any existing suffix (e.g., -draft) while incrementing the appropriate version component.
  • Commits and PRs for version bumps are automatically generated by the workflow.

Note: This PR only implements part 1 of issue #218 (Schema Version Updates). Part 2 (automated backports and patch updates via GH bot) has not been addressed.

Related Issue

Partially fixes #218

Submitter Checklist

As the author of this PR, please check off the items in this checklist:

@ImJustHenry ImJustHenry requested a review from a team as a code owner November 30, 2025 01:36
@afrittoli
Copy link
Member

Thanks @ImJustHenry for your contribution! A couple of comments:

  • We should not bump versions on each PR, only at release time. We could use manual dispatch when we want to create a release: the workflow creates a new PR. Once the PR is approved and merged, we could have another workflow doing the tagging and release notes,

  • I would follow the rule that if we are making a release on main, the version bump will be either major or minor (although in practice only minor until we release 1.0), depending on the labels of the PRs since the last minor release.

    • If there is at least on PR labelled as major we use major
    • If there is at least one PR labelled as minor we use minor
    • If we try to release only patch PRs on main, we should instead make a patch release on the latest release branch
  • There is already a shell script that can be used to update versions. The script updates schemas and examples and docs as well. Having a Python script is definitely an improvement, but should avoid having two scripts that do the same thing.

  • @xibz proposal in the issue was to switch from -draft versions to using latest - any thoughts on that? @xibz do we still need latest if everything is automated for contributors?

Copy link
Contributor

@xibz xibz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Overall it looks good, but just had a few minor comments.

return major, minor, patch, suffix


def bump_version(major, minor, patch):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe instead we could have just 3 new variables, and we should probably throw an exception for unknown LABELs

new_patch = 0
new_minor = 0
new_major = 0

if LABEL == "patch":
    new_patch = patch + 1
elif LABEL == "minor":
    new_minor = minor + 1
elif LABEL == "major":
    new_major = major + 1
else
    raise ExceptionType(f"invalid LABEL: {LABEL}")

return new_major, new_minor, new_patch

Additionally, we may need a suffix or prefix potentially, e.g. "v1.0.0-beta". But I think that can come when we need it. It looks like you started some functionality for suffix, but I dont think it's done.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks you for the feedback!

I understand the suggestion to use three new variables, it does make the bump logic more explicit and clear. I also see your point about throwing an exception for unknown labels. In the current setup, the GitHub workflow only passes predefined labels (patch, minor, major, release), so in practice an exception isn’t strictly necessary.

That said, adding an exception could serve as a useful safeguard if someone were to run the script manually with an invalid label. I’ll update the script to incorporate both of these suggestions.

old_version = read_version()

# Parse current version
major, minor, patch, suffix = parse_version(old_version)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens to suffix?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the question! The suffix is parsed so that I can correctly separate the numeric patch value from any trailing suffix such as -draft. I designed the logic under the assumption that versions should always include -draft unless the label is release, in which case the suffix is intentionally removed.

So the suffix is extracted purely so the script can reliably parse versions like 0.5.0-draft without errors, and so a release label can strip the suffix. Outside of that case, the script intentionally replaces the suffix with -draft on all non-release bumps.

else:
# Otherwise, bump version according to label
new_major, new_minor, new_patch = bump_version(major, minor, patch)
new_version = f"{new_major}.{new_minor}.{new_patch}-draft"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use suffix here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, the original assumption was that only finalized releases should omit the -draft suffix. That’s why I intentionally append -draft during version bumps, so all non-release versions are clearly marked.

If the goal is to preserve existing suffixes (potentially for more complex cases in the future like -beta) the logic can certainly be updated to consistently use the parsed suffix instead of forcing -draft. The current implementation mainly ensures that non-release versions are easily distinguishable from official releases.

@xibz
Copy link
Contributor

xibz commented Dec 1, 2025

@afrittoli - The shell script seems a little larger in scope. This doesn't do version file replacement in JSON, for example, but to manage the version specifically. I think we can use the shell script and this in conjunction. So I think we will need to update the shell script as well or move the rest of it to the python script.

@ImJustHenry - Can you take a look at that shell script? I think we may be able to utilize both of the files to do a release, or we can completely replace the shell script with full python, but that requires doing the file version replacement to also be added.

@afrittoli
Copy link
Member

The main problem I see with this approach is that it will attempt to create a new PR to update versions every time a PR is labelled. I think we should execute this at release time, not on every PR.
The only thing to be checked on every PR should be that a label is set.

Even if we wanted to update versions every PR, we shouldn't do it with an extra PR, there would be no easy way to coordinate merging the PRs in the right order then.
There are a couple of alternatives:

  • use pre-commit and have the script executed before commit - commit fails if the script was not executed
  • use a workflow to add a commit on top within the same PR

But again, I don't think we should update versions for every single PR. @xibz @davidB thoughts?

@ImJustHenry
Copy link
Author

@xibz - I’ve reviewed the shell script. Please correct me if I miss anything but the shell script basically manages releases by updating all version references across the repository, including JSON schemas, conformance files, and documentation, based on the version specified in version.txt. The approach could be that we can utilize both scripts, the Python script calculates the next version based on the label and updates version.txt, while the shell script reads version.txt and updates all references across the repository. Or alternatively, if you prefer having everything in a single language, I can convert the shell script’s functionality into the Python script. I’m happy to proceed with whichever approach is preferred.

@afrittoli - Thank you for the feedback! I actually modeled the workflow after what I observed with Dependabot, which creates a new PR every time it bumps a dependency version. My assumption was that each merged PR into main would correspond to either a minor, major, or patch change, and thought that the repository should have a PR for every version commit for consistency. I understand your concern about creating a PR on every labeled PR and the potential coordination issues. To address this, the approach you mentioned of using manual dispatch to create releases makes sense. This way it does ensure that the version bumps only happen intentionally, instead of triggering on every PR.

I can start updating the code and incorporate all the feedback if there are no other issues that haven’t been addressed.

@xibz
Copy link
Contributor

xibz commented Dec 3, 2025

@ImJustHenry

Please correct me if I miss anything but the shell script basically manages releases by updating all version references across the repository, including JSON schemas, conformance files, and documentation, based on the version specified in version.txt.

Exactly

I can convert the shell script’s functionality into the Python script. I’m happy to proceed with whichever approach is preferred.

This is probably the right move.

Also, if you could add some tests now that we are using a proper language, that would be great.
In addition, I am curious what your capstone project is, because python has a semver library that we can use, and returns a structure. Something to consider as long as it doesn't stomp over the goal of the capstone project.

@ImJustHenry
Copy link
Author

@xibz Thanks for the feedback, and sorry for the delayed reply (its finals week for me😅).

My capstone is focused on CI/CD automation within SLU’s Open Source program. A major part of the assignment involves contributing automation, release workflows, and documentation improvements across multiple OSS SLU repositories. As part of the capstone, I’m also doing excellence activities, which involve contributing externally like submitting and merging pull requests to projects outside of OSS SLU.

So your proposal doesn’t conflict with my capstone at all. I’ll go ahead and implement and fix this PR as suggested if there is no other concerns!

@xibz
Copy link
Contributor

xibz commented Dec 8, 2025

@ImJustHenry - No worries! And great to hear that your capstone is around CI/CD automation. That's a great area to dive into for a contribution given most open source projects have some form of CI/CD.

When you have the changes ready, feel free to ping me on Slack or just drop a comment here.

As a small piece of advice for next time: before opening a PR, it’s generally best to first create an issue. The reason is that it gives the project maintainers a chance to provide context or guidance. For example, if there are existing scripts or plans you might not be aware of.

Imagine if the team was planning to move away from GitHub Actions (we’re not, but just as an example), then updating the workflow wouldn’t have been the right direction, and that PR likely wouldn’t be accepted. Creating an issue first helps surface that kind of context early on.

It also helps prevent overlapping work and lets everyone know you’re taking it on. Open-source maintainers love contributors who jump in :), and starting with an issue makes the whole experience smoother for both sides.

Luckily, this PR is super straightforward, so no worries here. Just something to keep in mind for future contributions. You’ll get a lot more out of the experience that way 👍

Copilot AI review requested due to automatic review settings December 13, 2025 13:43
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements automated schema version bumping through a Python script and GitHub Actions workflow. When a PR is labeled with patch, minor, or major, the workflow automatically creates a new PR with the updated version. However, the implementation has critical issues that need to be addressed before merging.

Key Changes

  • Python script to parse and bump semantic versions based on PR labels
  • GitHub Actions workflow triggered by PR labeling events
  • Automated PR creation for version updates

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
tools/bump_version.py Core version bumping logic that parses version.txt and increments version numbers based on label type
.github/workflows/version-bump.yml Workflow automation that triggers version bump script when patch/minor/major labels are applied to PRs

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import sys

# The GitHub label passed as an argument: patch, minor, major
LABEL = sys.argv[1]
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for command-line arguments. If the script is invoked without arguments, it will crash with an IndexError. The script should validate that sys.argv has the required number of arguments and provide a helpful error message if not.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +12
with open(VERSION_FILE, "r") as f:
return f.read().strip()
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for file operations. If version.txt does not exist or is not readable, the script will crash with a FileNotFoundError. Consider adding try-except blocks to handle these cases gracefully and provide helpful error messages.

Suggested change
with open(VERSION_FILE, "r") as f:
return f.read().strip()
try:
with open(VERSION_FILE, "r") as f:
return f.read().strip()
except FileNotFoundError:
print(f"Error: {VERSION_FILE} not found. Please ensure the version file exists.", file=sys.stderr)
sys.exit(1)
except OSError as e:
print(f"Error reading {VERSION_FILE}: {e}", file=sys.stderr)
sys.exit(1)

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +31
parts = v.split(".") # Split into ['0', '5', '0']

major = int(parts[0])
minor = int(parts[1])
patch = int(parts[2])
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for malformed version strings. If version.txt contains a version string that doesn't have exactly 3 parts separated by dots (e.g., "0.5" or "0.5.0.1"), the script will crash with an IndexError when trying to access parts[2]. Add validation to ensure the version string is well-formed before parsing.

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +68
new_major, new_minor, new_patch = bump_version(major, minor, patch)
new_version = f"{new_major}.{new_minor}.{new_patch}"
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version suffix is being lost during the bump process. The PR description states "Non-release bumps retain any existing suffix (e.g., -draft) while incrementing the appropriate version component," but the current implementation removes the suffix in parse_version (line 25) and never restores it when constructing the new version (line 68). The suffix should be preserved and appended back to the new version string.

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +58
if LABEL == "patch":
new_patch = patch + 1
elif LABEL == "minor":
new_minor = minor + 1
new_patch = 0
elif LABEL == "major":
new_major = major + 1
new_minor = 0
new_patch = 0
else:
raise ValueError(f"Invalid Label: {LABEL}")
return new_major, new_minor, new_patch
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing implementation for 'release' label handling. The PR description states "For release labels, the workflow removes the -draft suffix from the version," but there is no code to handle the 'release' label. The script only processes 'patch', 'minor', and 'major' labels, and will raise a ValueError if 'release' is passed.

Copilot uses AI. Check for mistakes.
new_major, new_minor, new_patch = bump_version(major, minor, patch)
new_version = f"{new_major}.{new_minor}.{new_patch}"

# Prevent race conditions by only writing if version changed
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading comment about race condition prevention. The comment claims this check "prevents race conditions," but comparing version strings doesn't prevent race conditions in concurrent workflows. The check only avoids unnecessary writes when the version hasn't changed (which shouldn't happen in normal operation). Consider updating the comment to accurately describe what this check does.

Suggested change
# Prevent race conditions by only writing if version changed
# Only write if the version has changed to avoid unnecessary writes

Copilot uses AI. Check for mistakes.

steps:
- name: Checkout repository
uses: actions/checkout@v4
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent checkout action version. This workflow uses actions/checkout@v4, but the existing workflow in .github/workflows/main.yml uses actions/checkout@v5. For consistency and to ensure the latest features and security fixes, consider using v5 here as well.

Suggested change
uses: actions/checkout@v4
uses: actions/checkout@v5

Copilot uses AI. Check for mistakes.
"""
new_major = major
new_minor = minor
new_patch = patch
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'new_patch' is unnecessary as it is redefined before this value is used.
This assignment to 'new_patch' is unnecessary as it is redefined before this value is used.
This assignment to 'new_patch' is unnecessary as it is redefined before this value is used.

Suggested change
new_patch = patch

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improvements on automated release

3 participants