Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 126 additions & 35 deletions release/create_release_pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import argparse
import os
import subprocess
import sys
from inspect import getsourcefile
from os.path import abspath

import semver
import tomlkit

from utils.validate_version import assert_version_is_not_published

Expand All @@ -15,38 +17,109 @@
SCRIPTS_DIR = os.path.join(REPO_ROOT_DIR, "scripts")
SCRIPT_FILENAME = os.path.basename(getsourcefile(lambda: 0))

def overwrite_version(version):
def get_current_version():
"""Read the current version from Cargo.toml using tomlkit"""
toml_path = os.path.join(REPO_ROOT_DIR, "Cargo.toml")
with open(toml_path, "r") as file:
lines = file.readlines()

# This will preserve line order.
current_version = None
for i, line in enumerate(lines):
if line.startswith("version ="):
current_version = line.split("=")[1].strip().strip('"')
if current_version == version:
print(f"Already at version {version}.")
exit(1)
lines[i] = f"version = \"{version}\"\n"
break

if current_version is None:
print("The `version` field is not present in Cargo.toml.")
exit(1)

try:
with open(toml_path, "r") as file:
doc = tomlkit.parse(file.read())

if "package" in doc and "version" in doc["package"]:
return doc["package"]["version"]
else:
print("Error: The `version` field is not present in Cargo.toml [package] section.")
sys.exit(1)
except FileNotFoundError:
print(f"Error: Cargo.toml not found at {toml_path}")
sys.exit(1)
except Exception as e:
print(f"Error: Failed to parse Cargo.toml: {e}")
sys.exit(1)

def overwrite_version(version, dry_run=False):
"""
Update version in Cargo.toml using tomlkit.
Preserves formatting, comments, and structure.
"""
toml_path = os.path.join(REPO_ROOT_DIR, "Cargo.toml")

try:
with open(toml_path, "r") as file:
content = file.read()
doc = tomlkit.parse(content)
except FileNotFoundError:
print(f"Error: Cargo.toml not found at {toml_path}")
sys.exit(1)
except Exception as e:
print(f"Error: Failed to parse Cargo.toml: {e}")
sys.exit(1)

# Check if [package] section exists
if "package" not in doc:
print("Error: [package] section not found in Cargo.toml")
sys.exit(1)

# Check if version field exists
if "version" not in doc["package"]:
print("Error: version field not found in [package] section")
sys.exit(1)

current_version = doc["package"]["version"]

if current_version == version:
print(f"Already at version {version}.")
sys.exit(1)

commit_message = f"chore(deps): change version from {current_version} with {version}"
print(commit_message)
print(f"Updating version in Cargo.toml: {current_version} -> {version}")

if dry_run:
print("Dry-run mode: Skipping version file write and commit.")
return

# Update version in the document
doc["package"]["version"] = version

# Write back to file (preserves formatting and comments)
with open(toml_path, "w") as file:
file.writelines(lines)
file.write(tomlkit.dumps(doc))

# Update VRL version in Cargo.lock
subprocess.run(["cargo", "update", "-p", "vrl"], check=True, cwd=REPO_ROOT_DIR)

subprocess.run(["git", "commit", "-a", "-m", commit_message], check=True, cwd=REPO_ROOT_DIR)


def resolve_version(version_arg):
"""
Resolve version argument to actual version string.
Supports:
- Exact version (e.g., "1.2.3")
- "major" - bump major version
- "minor" - bump minor version
- "patch" - bump patch version
"""
bump_types = ["major", "minor", "patch"]

if version_arg.lower() in bump_types:
current_version_str = get_current_version()
current_version = semver.VersionInfo.parse(current_version_str)

if version_arg.lower() == "major":
new_version = current_version.bump_major()
elif version_arg.lower() == "minor":
new_version = current_version.bump_minor()
elif version_arg.lower() == "patch":
new_version = current_version.bump_patch()

new_version_str = str(new_version)
print(f"Bumping {version_arg} version: {current_version_str} -> {new_version_str}")
return new_version_str
else:
# Assume it's an exact version
return version_arg

def validate_version(version):
try:
semver.VersionInfo.parse(version)
Expand All @@ -56,24 +129,33 @@ def validate_version(version):

assert_version_is_not_published(version)

def generate_changelog():
def generate_changelog(dry_run=False):
print("Generating changelog...")
if dry_run:
print("Dry-run mode: Skipping changelog generation and commit.")
return
subprocess.run(["./generate_release_changelog.sh", "--no-prompt"], check=True, cwd=SCRIPTS_DIR)
subprocess.run(["git", "commit", "-a", "-m", "chore(releasing): generate changelog"],
check=True,
cwd=REPO_ROOT_DIR)

def create_branch(branch_name, dry_run=False):
print(f"Creating branch: {branch_name}")
if dry_run:
print("Dry-run mode: Skipping branch creation.")
return
subprocess.run(["git", "checkout", "-b", branch_name], check=True, cwd=REPO_ROOT_DIR)
if not dry_run:
subprocess.run(["git", "push", "-u", "origin", branch_name],
check=True,
cwd=REPO_ROOT_DIR)
subprocess.run(["git", "push", "-u", "origin", branch_name],
check=True,
cwd=REPO_ROOT_DIR)

def create_pull_request(branch_name, new_version, dry_run=False):
def create_pull_request(branch_name, new_version, issue_link=None, dry_run=False):
title = f"chore(releasing): Prepare {new_version} release"
body = f"Generated with {SCRIPT_FILENAME}"

if issue_link:
body += f"\n\nRelated issue: {issue_link}"

print(f"Creating pull request with title: {title}")
if dry_run:
print("Dry-run mode: Skipping PR creation.")
Expand All @@ -87,24 +169,33 @@ def create_pull_request(branch_name, new_version, dry_run=False):

def main():
parser = argparse.ArgumentParser(description="Prepare a new release")
parser.add_argument("version", help="The new version to release")
parser.add_argument("version", help="The new version to release (e.g., '1.2.3', 'major', 'minor', or 'patch')")
parser.add_argument("--issue", "-i", dest="issue_link",
help="GitHub issue link to include in the PR body (e.g., 'https://github.com/owner/repo/issues/123')")
parser.add_argument("--dry-run", action="store_true",
help="Run the script without making remote changes")
help="Run the script without making any changes (read-only)")
args = parser.parse_args()

new_version = args.version
# Resolve version (could be exact version or bump type)
new_version = resolve_version(args.version)
dry_run = args.dry_run
issue_link = args.issue_link

if not dry_run:
validate_version(new_version)

validate_version(new_version)
branch_name = f"prepare-{new_version}-release"
create_branch(branch_name, dry_run)
overwrite_version(new_version)
generate_changelog()
subprocess.run(["git", "push"], check=True, cwd=REPO_ROOT_DIR)
create_pull_request(branch_name, new_version, dry_run)
overwrite_version(new_version, dry_run)
generate_changelog(dry_run)

if not dry_run:
subprocess.run(["git", "push"], check=True, cwd=REPO_ROOT_DIR)

create_pull_request(branch_name, new_version, issue_link, dry_run)

if dry_run:
print("Dry-run completed. No actual remote changes were made.")
print("\nDry-run completed. No changes were made.")

if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions release/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
requests
semver
tomlkit
Loading