From 13f9e272f83cea5c9e3adb5ca7a1998d5d832e7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:33:47 +0000 Subject: [PATCH 1/4] Initial plan From a6706d72f05c87a0e5b9b5992c5d891b46e372cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:40:22 +0000 Subject: [PATCH 2/4] fix(icons): reduce max_url_length to 3550 to stay under camo limit Co-authored-by: ChipWolf <3164166+ChipWolf@users.noreply.github.com> --- badgesort/icons.py | 12 ++++++------ tests/test_camo_url_limits.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/badgesort/icons.py b/badgesort/icons.py index b9d47c2..175decf 100644 --- a/badgesort/icons.py +++ b/badgesort/icons.py @@ -26,16 +26,16 @@ logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) -def svg_to_base64_data_uri(svg_content, fill_color='white', max_url_length=3700): +def svg_to_base64_data_uri(svg_content, fill_color='white', max_url_length=3550): """Convert an SVG to a compressed base64-encoded data URI with specified fill color optimized for 14x14px badges. Args: svg_content: SVG content as string fill_color: Fill color for the SVG paths ('white', 'black', or None) max_url_length: Maximum data URI length before falling back to PNG rasterization. - Default 3700 chars ensures badge URLs stay under GitHub's camo proxy + Default 3550 chars ensures badge URLs stay under GitHub's camo proxy 8192 char limit (which hex-encodes URLs: 76 + url_len*2 <= 8192). - Calculation: safe_url = (8192 - 76) / 2 - overhead ≈ 3850, with 150 char margin = 3700 + Calculation: safe_url = (8192 - 76) / 2 - overhead ≈ 3949, with 10% margin = 3550 Uses scour-based SVG compression for optimal file size. For very large SVGs that would exceed URL length limits, falls back to PNG rasterization at 14x14px. @@ -424,8 +424,8 @@ def run(args): if should_embed_svg: logger.debug(f'Embedding SVG data URI for {icon.slug}') # Convert SVG to base64 data URI for embedding - # Use 3700 char limit to stay under GitHub camo's 8192 char limit - icon_data_uri = svg_to_base64_data_uri(icon.svg, icon_hex_comp, max_url_length=3700) + # Use 3550 char limit to stay under GitHub camo's 8192 char limit + icon_data_uri = svg_to_base64_data_uri(icon.svg, icon_hex_comp, max_url_length=3550) icon_data_uri_encoded = quote(icon_data_uri, safe='') icon_url = f'{icon_base}/{icon_title_safe}-{badge_color}.svg' if icon_title_safe else f'{icon_base}/-{badge_color}.svg' icon_url += f'?style={args.badge_style}&logo={icon_data_uri_encoded}' @@ -449,7 +449,7 @@ def run(args): background_color = badge_color # Always use white icons for good contrast against any background - icon_data_uri = svg_to_base64_data_uri(icon.svg, 'white', max_url_length=3700) + icon_data_uri = svg_to_base64_data_uri(icon.svg, 'white', max_url_length=3550) icon_data_uri_encoded = quote(icon_data_uri, safe='') icon_url = f'{icon_base}/icon/{icon_title_safe}?icon={icon_data_uri_encoded}&label&color={background_color}&labelColor={background_color}' if icon_title_safe else f'{icon_base}/icon/?icon={icon_data_uri_encoded}&label&color={background_color}&labelColor={background_color}' else: diff --git a/tests/test_camo_url_limits.py b/tests/test_camo_url_limits.py index 8436832..857db58 100644 --- a/tests/test_camo_url_limits.py +++ b/tests/test_camo_url_limits.py @@ -33,14 +33,14 @@ def calculate_camo_url_length(badge_url): def test_svg_data_uri_max_length_default(): """Test that default max_url_length respects camo limits.""" - # The default max_url_length should be 3700 to stay under camo's 8192 limit + # The default max_url_length should be 3550 to stay under camo's 8192 limit # This test verifies the function signature has the correct default import inspect sig = inspect.signature(svg_to_base64_data_uri) max_url_length_default = sig.parameters['max_url_length'].default - assert max_url_length_default == 3700, \ - f"Default max_url_length should be 3700 (found {max_url_length_default})" + assert max_url_length_default == 3550, \ + f"Default max_url_length should be 3550 (found {max_url_length_default})" def test_shields_embedded_svg_urls_under_camo_limit(): From fb0670653699880c7272a3d9f536502351191891 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:51:47 +0000 Subject: [PATCH 3/4] chore(ci): install librsvg2-bin for PNG fallback in tests Co-authored-by: ChipWolf <3164166+ChipWolf@users.noreply.github.com> --- .github/workflows/build-and-test.yaml | 4 ++++ badgesort/icons.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 50012aa..8618ea1 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -33,6 +33,10 @@ jobs: uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: '3.14' + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y librsvg2-bin - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/badgesort/icons.py b/badgesort/icons.py index 175decf..b552782 100644 --- a/badgesort/icons.py +++ b/badgesort/icons.py @@ -35,7 +35,8 @@ def svg_to_base64_data_uri(svg_content, fill_color='white', max_url_length=3550) max_url_length: Maximum data URI length before falling back to PNG rasterization. Default 3550 chars ensures badge URLs stay under GitHub's camo proxy 8192 char limit (which hex-encodes URLs: 76 + url_len*2 <= 8192). - Calculation: safe_url = (8192 - 76) / 2 - overhead ≈ 3949, with 10% margin = 3550 + Calculation: (8192 - 76 camo_overhead) / 2 hex_encoding - ~109 badge_url_overhead = 3949, with 10% margin = 3550 + (~109 char badge_url_overhead is the badge URL structure: domain, path, parameters, excluding the data URI payload) Uses scour-based SVG compression for optimal file size. For very large SVGs that would exceed URL length limits, falls back to PNG rasterization at 14x14px. From 0ba8bc15c0464106956b6c3d7624008af94b737e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:59:59 +0000 Subject: [PATCH 4/4] feat(ci,icons): use Docker container for tests and add camo URL warnings Co-authored-by: ChipWolf <3164166+ChipWolf@users.noreply.github.com> --- .github/workflows/build-and-test.yaml | 25 +++++++++++-------------- badgesort/icons.py | 21 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 8618ea1..a9a93e4 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -29,22 +29,19 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 1 - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 + - name: Build BadgeSort container + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 with: - python-version: '3.14' - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y librsvg2-bin - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e . - pip install pytest pytest-cov - - name: Run tests + context: . + load: true + tags: badgesort:test + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Run tests in container run: | - python -m pytest tests/ -v --cov=badgesort --cov-report=term-missing + docker run --rm badgesort:test sh -c ". \$VENV_PATH/bin/activate && pip install pytest pytest-cov && pytest tests/ -v --cov=badgesort --cov-report=term-missing" image: name: Build Image needs: [test] diff --git a/badgesort/icons.py b/badgesort/icons.py index b552782..22f7069 100644 --- a/badgesort/icons.py +++ b/badgesort/icons.py @@ -23,9 +23,20 @@ # Cache for logo availability checks to avoid repeated requests _logo_availability_cache = {} +# GitHub camo proxy constants +CAMO_URL_LIMIT = 8192 +CAMO_OVERHEAD = 76 # base URL (35) + digest (40) + slash (1) + logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) +def _calculate_camo_url_length(badge_url): + """Calculate the approximate camo URL length for a badge URL. + + GitHub's camo proxy format: https://camo.githubusercontent.com// + """ + return CAMO_OVERHEAD + (len(badge_url.encode('utf-8')) * 2) + def svg_to_base64_data_uri(svg_content, fill_color='white', max_url_length=3550): """Convert an SVG to a compressed base64-encoded data URI with specified fill color optimized for 14x14px badges. @@ -64,7 +75,8 @@ def svg_to_base64_data_uri(svg_content, fill_color='white', max_url_length=3550) if png_data_uri: return png_data_uri else: - logger.debug('PNG fallback failed, using original SVG despite size') + logger.warning(f'PNG fallback failed for oversized SVG ({len(svg_data_uri)} chars > {max_url_length} limit). ' + f'Using original SVG despite size. Badge may exceed GitHub camo URL limit.') # For now, return the original SVG data URI even if it's too long # This is better than skipping the icon entirely @@ -459,6 +471,13 @@ def run(args): # Store custom URL if provided custom_url = custom_params.get('url', None) + + # Check if badge URL would exceed GitHub's camo proxy limit and warn + camo_length = _calculate_camo_url_length(icon_url) + if camo_length > CAMO_URL_LIMIT: + logger.warning(f'Badge URL for {icon.slug} exceeds GitHub camo limit: {camo_length} > {CAMO_URL_LIMIT} chars. ' + f'Badge may not render correctly on GitHub. Consider using a simpler icon or badge style.') + icon_list.append({ 'rgb': icon_rgb, 'slug': icon.slug,