From 45938b4c4b77b731382df6e3092768ac4c20f7f2 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 20 Feb 2026 18:10:01 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20add=20context7=20chat=20widget?= =?UTF-8?q?=20to=20app=20footer=20in=20docfx=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docfx/docfx.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docfx/docfx.json b/.docfx/docfx.json index 3098c98..39d08ad 100644 --- a/.docfx/docfx.json +++ b/.docfx/docfx.json @@ -45,7 +45,7 @@ ], "globalMetadata": { "_appTitle": "Extensions for Asp.Versioning by Codebelt", - "_appFooter": "Generated by DocFX. Copyright 2024-2026 Geekle. All rights reserved.", + "_appFooter": "Generated by DocFX. Copyright 2024-2026 Geekle. All rights reserved.", "_appLogoPath": "images/50x50.png", "_appFaviconPath": "images/favicon.ico", "_googleAnalyticsTagId": "G-G15R2J7GBR", From df1556a3facebae23806f577467a581e98d9a365 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 20 Feb 2026 18:10:13 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7=20fix=20formatting=20of=20vers?= =?UTF-8?q?ion=20entries=20in=20package=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PackageReleaseNotes.txt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt index 20e2c41..cca8f81 100644 --- a/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt @@ -1,16 +1,16 @@ -Version 10.0.2 +Version: 10.0.2 Availability: .NET 10 and .NET 9   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 10.0.1 +Version: 10.0.1 Availability: .NET 10 and .NET 9   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 10.0.0 +Version: 10.0.0 Availability: .NET 10 and .NET 9   # ALM @@ -18,80 +18,80 @@ Availability: .NET 10 and .NET 9 - REMOVED Support for TFM .NET 8 (LTS) - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.8 +Version: 9.0.8 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.7 +Version: 9.0.7 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.6 +Version: 9.0.6 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.5 +Version: 9.0.5 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.4 +Version: 9.0.4 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.3 +Version: 9.0.3 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)   -Version 9.0.2 +Version: 9.0.2 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies to latest and greatest with respect to TFMs   -Version 9.0.1 +Version: 9.0.1 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies to latest and greatest with respect to TFMs   -Version 9.0.0 +Version: 9.0.0 Availability: .NET 9 and .NET 8   # ALM - CHANGED Dependencies to latest and greatest with respect to TFMs - REMOVED Support for TFM .NET 6 (LTS)   -Version 8.4.0 +Version: 8.4.0 Availability: .NET 8 and .NET 6   # ALM - CHANGED Dependencies to latest and greatest with respect to TFMs   -Version 8.3.2 +Version: 8.3.2 Availability: .NET 8 and .NET 6   # ALM - REMOVED Support for TFM .NET 7 (STS)   -Version 8.3.0 +Version: 8.3.0 Availability: .NET 8, .NET 7 and .NET 6   # ALM - CHANGED Dependencies to latest and greatest with respect to TFMs   -Version 8.2.0 +Version: 8.2.0 Availability: .NET 8, .NET 7 and .NET 6   # ALM From dd2c662a6ac8153827a9b6216e5fe54ba59faa2c Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 20 Feb 2026 18:10:17 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20add=20service=20update=20workfl?= =?UTF-8?q?ow=20and=20bump=20nuget=20script=20for=20automation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/dispatch-targets.json | 1 + .github/scripts/bump-nuget.py | 133 ++++++++++++++++++++++ .github/workflows/service-update.yml | 139 +++++++++++++++++++++++ .github/workflows/trigger-downstream.yml | 78 +++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 .github/dispatch-targets.json create mode 100644 .github/scripts/bump-nuget.py create mode 100644 .github/workflows/service-update.yml create mode 100644 .github/workflows/trigger-downstream.yml diff --git a/.github/dispatch-targets.json b/.github/dispatch-targets.json new file mode 100644 index 0000000..6fadab5 --- /dev/null +++ b/.github/dispatch-targets.json @@ -0,0 +1 @@ +[ "swashbuckle-aspnetcore" ] diff --git a/.github/scripts/bump-nuget.py b/.github/scripts/bump-nuget.py new file mode 100644 index 0000000..65a8b21 --- /dev/null +++ b/.github/scripts/bump-nuget.py @@ -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}"' + ) + + return m.group(0) + + # Match PackageVersion elements (handles multiline) + pattern = re.compile( + r"]*\bInclude="([^"]+)")' + r'(?=[^>]*\bVersion="([^"]+)")' + r"[^>]*>", + re.DOTALL, + ) + new_content = pattern.sub(replace_version, content) + + # Show results + if changes: + print(f"Updated {len(changes)} package(s) from {TRIGGER_SOURCE}:") + print("\n".join(changes)) + else: + print(f"No packages from {TRIGGER_SOURCE} needed updating.") + + if skipped_third_party: + print() + print(f"Skipped {len(skipped_third_party)} third-party package(s):") + print("\n".join(skipped_third_party[:5])) # Show first 5 + if len(skipped_third_party) > 5: + print(f" ... and {len(skipped_third_party) - 5} more") + + with open("Directory.Packages.props", "w") as f: + f.write(new_content) + + return 0 if changes else 0 # Return 0 even if no changes (not an error) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/service-update.yml b/.github/workflows/service-update.yml new file mode 100644 index 0000000..ea92ea5 --- /dev/null +++ b/.github/workflows/service-update.yml @@ -0,0 +1,139 @@ +name: Service Update + +on: + repository_dispatch: + types: [codebelt-service-update] + workflow_dispatch: + inputs: + source_repo: + description: 'Triggering source repo name (e.g. cuemon)' + required: false + default: '' + source_version: + description: 'Version released by source (e.g. 10.3.0)' + required: false + default: '' + dry_run: + type: boolean + description: 'Dry run — show changes but do not commit or open PR' + default: false + +permissions: + contents: write + pull-requests: write + +jobs: + service-update: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Resolve trigger inputs + id: trigger + run: | + SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}" + VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}" + echo "source=$SOURCE" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Determine new version for this repo + id: newver + run: | + CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1) + NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}') + BRANCH="v${NEW}/service-update" + echo "current=$CURRENT" >> $GITHUB_OUTPUT + echo "new=$NEW" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + + - name: Generate codebelt-aicia token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.CODEBELT_AICIA_APP_ID }} + private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }} + owner: codebeltnet + + - name: Bump NuGet packages + run: python3 .github/scripts/bump-nuget.py + env: + TRIGGER_SOURCE: ${{ steps.trigger.outputs.source }} + TRIGGER_VERSION: ${{ steps.trigger.outputs.version }} + + - name: Update PackageReleaseNotes.txt + run: | + NEW="${{ steps.newver.outputs.new }}" + for f in .nuget/*/PackageReleaseNotes.txt; do + [ -f "$f" ] || continue + TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0") + ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n" + { printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f" + done + + - name: Update CHANGELOG.md + run: | + python3 - <<'EOF' + import os, re + from datetime import date + new_ver = os.environ['NEW_VERSION'] + today = date.today().isoformat() + entry = f"## [{new_ver}] - {today}\n\nThis is a service update that focuses on package dependencies.\n\n" + with open("CHANGELOG.md") as f: + content = f.read() + idx = content.find("## [") + content = (content[:idx] + entry + content[idx:]) if idx != -1 else (content + entry) + with open("CHANGELOG.md", "w") as f: + f.write(content) + print(f"CHANGELOG updated for v{new_ver}") + EOF + env: + NEW_VERSION: ${{ steps.newver.outputs.new }} + + # Note: Docker image bumps removed in favor of manual updates + # The automated selection was picking wrong variants (e.g., mono-* instead of standard) + # TODO: Move to hosted service for smarter image selection + + - name: Show diff (dry run) + if: ${{ github.event.inputs.dry_run == 'true' }} + run: git diff + + - name: Create branch and open PR + if: ${{ github.event.inputs.dry_run != 'true' }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + NEW="${{ steps.newver.outputs.new }}" + BRANCH="${{ steps.newver.outputs.branch }}" + SOURCE="${{ steps.trigger.outputs.source }}" + SRC_VER="${{ steps.trigger.outputs.version }}" + + git config user.name "codebelt-aicia[bot]" + git config user.email "codebelt-aicia[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add -A + git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0 + git commit -m "V${NEW}/service update" + git push origin "$BRANCH" + + echo "This is a service update that focuses on package dependencies." > pr_body.txt + echo "" >> pr_body.txt + echo "Automated changes:" >> pr_body.txt + echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt + echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt + echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt + echo "" >> pr_body.txt + echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt + echo "Use Dependabot or manual updates for those." >> pr_body.txt + echo "" >> pr_body.txt + echo "Generated by codebelt-aicia" >> pr_body.txt + if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then + echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt + else + echo "Triggered by: manual workflow dispatch" >> pr_body.txt + fi + + gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael diff --git a/.github/workflows/trigger-downstream.yml b/.github/workflows/trigger-downstream.yml new file mode 100644 index 0000000..29eb29c --- /dev/null +++ b/.github/workflows/trigger-downstream.yml @@ -0,0 +1,78 @@ +name: Trigger Downstream Service Updates + +on: + release: + types: [published] + +jobs: + dispatch: + if: github.event.release.prerelease == false + runs-on: ubuntu-24.04 + permissions: + contents: read + + steps: + - name: Checkout (to read dispatch-targets.json) + uses: actions/checkout@v4 + + - name: Check for dispatch targets + id: check + run: | + if [ ! -f .github/dispatch-targets.json ]; then + echo "No dispatch-targets.json found, skipping." + echo "has_targets=false" >> $GITHUB_OUTPUT + exit 0 + fi + COUNT=$(python3 -c "import json; print(len(json.load(open('.github/dispatch-targets.json'))))") + echo "has_targets=$([ $COUNT -gt 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT + + - name: Extract version from release tag + if: steps.check.outputs.has_targets == 'true' + id: version + run: | + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Generate codebelt-aicia token + if: steps.check.outputs.has_targets == 'true' + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.CODEBELT_AICIA_APP_ID }} + private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }} + owner: codebeltnet + + - name: Dispatch to downstream repos + if: steps.check.outputs.has_targets == 'true' + run: | + python3 - <<'EOF' + import json, urllib.request, os, sys + + targets = json.load(open('.github/dispatch-targets.json')) + token = os.environ['GH_TOKEN'] + version = os.environ['VERSION'] + source = os.environ['SOURCE_REPO'] + + for repo in targets: + url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches' + payload = json.dumps({ + 'event_type': 'codebelt-service-update', + 'client_payload': { + 'source_repo': source, + 'source_version': version + } + }).encode() + req = urllib.request.Request(url, data=payload, method='POST', headers={ + 'Authorization': f'Bearer {token}', + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28' + }) + with urllib.request.urlopen(req) as r: + print(f'✓ Dispatched to {repo}: HTTP {r.status}') + EOF + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ steps.version.outputs.version }} + SOURCE_REPO: ${{ github.event.repository.name }}