-
Notifications
You must be signed in to change notification settings - Fork 0
✨ add bump-nuget script for simplified package version updates #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,133 @@ | ||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||
| """ | ||||||||||||||||||
| Simplified package bumping for Codebelt service updates (Option B). | ||||||||||||||||||
|
|
||||||||||||||||||
| Only updates packages published by the triggering source repo. | ||||||||||||||||||
| Does NOT update Microsoft.Extensions.*, BenchmarkDotNet, or other third-party packages. | ||||||||||||||||||
| Does NOT parse TFM conditions - only bumps Codebelt/Cuemon/Savvyio packages to the triggering version. | ||||||||||||||||||
|
|
||||||||||||||||||
| Usage: | ||||||||||||||||||
| TRIGGER_SOURCE=cuemon TRIGGER_VERSION=10.3.0 python3 bump-nuget.py | ||||||||||||||||||
|
|
||||||||||||||||||
| Behavior: | ||||||||||||||||||
| - If TRIGGER_SOURCE is "cuemon" and TRIGGER_VERSION is "10.3.0": | ||||||||||||||||||
| - Cuemon.Core: 10.2.1 → 10.3.0 | ||||||||||||||||||
| - Cuemon.Extensions.IO: 10.2.1 → 10.3.0 | ||||||||||||||||||
| - Microsoft.Extensions.Hosting: 9.0.13 → UNCHANGED (not a Codebelt package) | ||||||||||||||||||
| - BenchmarkDotNet: 0.15.8 → UNCHANGED (not a Codebelt package) | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| import re | ||||||||||||||||||
| import os | ||||||||||||||||||
| import sys | ||||||||||||||||||
| from typing import Dict, List | ||||||||||||||||||
|
|
||||||||||||||||||
| TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "") | ||||||||||||||||||
| TRIGGER_VERSION = os.environ.get("TRIGGER_VERSION", "") | ||||||||||||||||||
|
|
||||||||||||||||||
| # Map of source repos to their package ID prefixes | ||||||||||||||||||
| SOURCE_PACKAGE_MAP: Dict[str, List[str]] = { | ||||||||||||||||||
| "cuemon": ["Cuemon."], | ||||||||||||||||||
| "xunit": ["Codebelt.Extensions.Xunit"], | ||||||||||||||||||
| "benchmarkdotnet": ["Codebelt.Extensions.BenchmarkDotNet"], | ||||||||||||||||||
| "bootstrapper": ["Codebelt.Bootstrapper"], | ||||||||||||||||||
| "newtonsoft-json": [ | ||||||||||||||||||
| "Codebelt.Extensions.Newtonsoft.Json", | ||||||||||||||||||
| "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft", | ||||||||||||||||||
| ], | ||||||||||||||||||
| "aws-signature-v4": ["Codebelt.Extensions.AspNetCore.Authentication.AwsSignature"], | ||||||||||||||||||
| "unitify": ["Codebelt.Unitify"], | ||||||||||||||||||
| "yamldotnet": [ | ||||||||||||||||||
| "Codebelt.Extensions.YamlDotNet", | ||||||||||||||||||
| "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Text.Yaml", | ||||||||||||||||||
| ], | ||||||||||||||||||
| "globalization": ["Codebelt.Extensions.Globalization"], | ||||||||||||||||||
| "asp-versioning": ["Codebelt.Extensions.Asp.Versioning"], | ||||||||||||||||||
| "swashbuckle-aspnetcore": ["Codebelt.Extensions.Swashbuckle"], | ||||||||||||||||||
| "savvyio": ["Savvyio."], | ||||||||||||||||||
| "shared-kernel": [], | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def is_triggered_package(package_name: str) -> bool: | ||||||||||||||||||
| """Check if package is published by the triggering source repo.""" | ||||||||||||||||||
| if not TRIGGER_SOURCE: | ||||||||||||||||||
| return False | ||||||||||||||||||
| prefixes = SOURCE_PACKAGE_MAP.get(TRIGGER_SOURCE, []) | ||||||||||||||||||
| return any(package_name.startswith(prefix) for prefix in prefixes) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def main(): | ||||||||||||||||||
| if not TRIGGER_SOURCE or not TRIGGER_VERSION: | ||||||||||||||||||
| print( | ||||||||||||||||||
| "Error: TRIGGER_SOURCE and TRIGGER_VERSION environment variables required" | ||||||||||||||||||
| ) | ||||||||||||||||||
| print(f" TRIGGER_SOURCE={TRIGGER_SOURCE}") | ||||||||||||||||||
| print(f" TRIGGER_VERSION={TRIGGER_VERSION}") | ||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||
|
|
||||||||||||||||||
| # Strip 'v' prefix if present in version | ||||||||||||||||||
| target_version = TRIGGER_VERSION.lstrip("v") | ||||||||||||||||||
|
|
||||||||||||||||||
| print(f"Trigger: {TRIGGER_SOURCE} @ {target_version}") | ||||||||||||||||||
| print(f"Only updating packages from: {TRIGGER_SOURCE}") | ||||||||||||||||||
| print() | ||||||||||||||||||
|
|
||||||||||||||||||
| try: | ||||||||||||||||||
| with open("Directory.Packages.props", "r") as f: | ||||||||||||||||||
| content = f.read() | ||||||||||||||||||
| except FileNotFoundError: | ||||||||||||||||||
| print("Error: Directory.Packages.props not found") | ||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||
|
|
||||||||||||||||||
| changes = [] | ||||||||||||||||||
| skipped_third_party = [] | ||||||||||||||||||
|
|
||||||||||||||||||
| def replace_version(m: re.Match) -> str: | ||||||||||||||||||
| pkg = m.group(1) | ||||||||||||||||||
| current = m.group(2) | ||||||||||||||||||
|
|
||||||||||||||||||
| if not is_triggered_package(pkg): | ||||||||||||||||||
| skipped_third_party.append(f" {pkg} (skipped - not from {TRIGGER_SOURCE})") | ||||||||||||||||||
| return m.group(0) | ||||||||||||||||||
|
|
||||||||||||||||||
| if target_version != current: | ||||||||||||||||||
| changes.append(f" {pkg}: {current} → {target_version}") | ||||||||||||||||||
| return m.group(0).replace( | ||||||||||||||||||
| f'Version="{current}"', f'Version="{target_version}"' | ||||||||||||||||||
|
Comment on lines
+96
to
+97
|
||||||||||||||||||
| return m.group(0).replace( | |
| f'Version="{current}"', f'Version="{target_version}"' | |
| return re.sub( | |
| r'\bVersion="[^"]+"', | |
| f'Version="{target_version}"', | |
| m.group(0), | |
| count=1, |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern uses positive lookahead assertions to capture the Include and Version attributes. However, the pattern doesn't enforce the closing /> or </PackageVersion>. While the pattern captures up to >, it should be more specific to handle self-closing tags properly. Consider using r"<PackageVersion\b(?=[^>]*\bInclude=\"([^\"]+)\")(?=[^>]*\bVersion=\"([^\"]+)\")[^>]*/>" to specifically match self-closing tags, which is the standard format in Directory.Packages.props files.
| r"[^>]*>", | |
| r"[^>]*/>", |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script writes to Directory.Packages.props without creating a backup or using atomic write operations. If the write operation fails partway through (e.g., disk full, interrupted process), the file could be corrupted. Consider using a safer pattern: write to a temporary file first, then rename it to replace the original (atomic operation on most filesystems), or at least create a backup before writing.
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling for FileNotFoundError is good, but other exceptions during file operations (like PermissionError, IOError when writing) are not caught. Consider adding exception handling around the file write operation on lines 126-127 to provide a more helpful error message if the write fails.
| with open("Directory.Packages.props", "w") as f: | |
| f.write(new_content) | |
| try: | |
| with open("Directory.Packages.props", "w") as f: | |
| f.write(new_content) | |
| except (PermissionError, OSError) as exc: | |
| print(f"Error: Failed to write Directory.Packages.props: {exc}") | |
| sys.exit(1) |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return statement on line 129 contains redundant logic. The expression 0 if changes else 0 always evaluates to 0 regardless of whether there are changes or not. If the intention is to return 0 in all cases (as the comment suggests), simplify this to return 0.
| return 0 if changes else 0 # Return 0 even if no changes (not an error) | |
| return 0 # Return 0 even if no changes (not an error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The imports should follow PEP 8 conventions by grouping standard library imports separately from third-party imports (though all imports here are standard library). However, the
from typing importstatement should typically come after the regular imports. Consider reordering to:import os,import re,import sys, thenfrom typing import Dict, Listfor consistency with Python conventions.