From 326397204f976b8d7eb1f78978986666770a65db Mon Sep 17 00:00:00 2001 From: Dimo Terziev Date: Fri, 13 Feb 2026 23:29:55 +0000 Subject: [PATCH] Add .NET 10 and remove legacy .NET versions --- .claude/hooks/install-dotnet.sh | 93 +++++++++++ .claude/hooks/nuget-proxy-forwarder.py | 153 ++++++++++++++++++ .claude/settings.json | 15 ++ .gitattributes | 1 + .github/workflows/dotnet.yml | 2 +- .gitignore | 2 + Directory.Packages.props | 10 +- ...merableDataReaderAdapter.Benchmarks.csproj | 2 +- .../Program.cs | 3 +- .../EnumerableDataReaderAdapter.csproj | 2 +- .../EnumerableDataReaderAdapter.Tests.csproj | 2 +- 11 files changed, 274 insertions(+), 11 deletions(-) create mode 100755 .claude/hooks/install-dotnet.sh create mode 100755 .claude/hooks/nuget-proxy-forwarder.py create mode 100644 .claude/settings.json diff --git a/.claude/hooks/install-dotnet.sh b/.claude/hooks/install-dotnet.sh new file mode 100755 index 0000000..ceeaafd --- /dev/null +++ b/.claude/hooks/install-dotnet.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# Install .NET 10 SDK and essential build tools for development +# This script is intended to be run as a Claude Code Web startup hook +set -euo pipefail + +DOTNET_VERSION="10.0" +INSTALL_DIR="$HOME/.dotnet" + +# Only run in remote Claude Code environments +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +# Install essential build tools if missing +if ! command -v tail &> /dev/null || ! command -v head &> /dev/null; then + echo "Installing essential build tools (coreutils)..." + apt-get update -qq && apt-get install -y -qq coreutils > /dev/null 2>&1 || true +fi + +# Download and run the official Microsoft install script +curl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh +chmod +x /tmp/dotnet-install.sh + +/tmp/dotnet-install.sh --channel $DOTNET_VERSION --install-dir "$INSTALL_DIR" + +echo "Installing additional .NET runtime frameworks..." +/tmp/dotnet-install.sh --channel 8.0 --runtime dotnet --install-dir "$INSTALL_DIR" +/tmp/dotnet-install.sh --channel 9.0 --runtime dotnet --install-dir "$INSTALL_DIR" + +# Clean up +rm -f /tmp/dotnet-install.sh + +# Add to PATH for current session +export DOTNET_ROOT="$INSTALL_DIR" +export PATH="$PATH:$INSTALL_DIR:$INSTALL_DIR/tools" + +# Add to PATH in the CLAUDE_ENV_FILE +if [ -n "$CLAUDE_ENV_FILE" ]; then + echo "export DOTNET_ROOT=\"$INSTALL_DIR\"" >> "$CLAUDE_ENV_FILE" + echo "export PATH=\"\$PATH:$INSTALL_DIR:$INSTALL_DIR/tools\"" >> "$CLAUDE_ENV_FILE" +fi + +# Verify installation +echo ".NET SDK version $(dotnet --version) installed successfully at $INSTALL_DIR/dotnet" + +# Configure NuGet to work with Claude Code proxy +# .NET's HttpClient doesn't properly handle proxy credentials from environment variables, +# so we configure NuGet to use localhost:8888 which will be handled by a proxy forwarder +echo "Configuring NuGet proxy settings..." +NUGET_CONFIG_DIR="$HOME/.nuget/NuGet" +NUGET_CONFIG_FILE="$NUGET_CONFIG_DIR/NuGet.Config" + +mkdir -p "$NUGET_CONFIG_DIR" + +cat > "$NUGET_CONFIG_FILE" << 'EOF' + + + + + + + + + + +EOF + +echo "NuGet configuration updated at $NUGET_CONFIG_FILE" + +# Start NuGet proxy forwarder in background +# This handles the proxy authentication that .NET's HttpClient doesn't support +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROXY_FORWARDER="$SCRIPT_DIR/nuget-proxy-forwarder.py" + +if [ -f "$PROXY_FORWARDER" ]; then + # Check if forwarder is already running + if ! pgrep -f "nuget-proxy-forwarder.py" > /dev/null; then + echo "Starting NuGet proxy forwarder on localhost:8888..." + nohup python3 "$PROXY_FORWARDER" > /dev/null 2>&1 & + sleep 1 + if pgrep -f "nuget-proxy-forwarder.py" > /dev/null; then + echo "Proxy forwarder started successfully" + else + echo "Warning: Proxy forwarder may not have started correctly" + fi + else + echo "NuGet proxy forwarder is already running" + fi +else + echo "Warning: Proxy forwarder script not found at $PROXY_FORWARDER" +fi + +exit 0 diff --git a/.claude/hooks/nuget-proxy-forwarder.py b/.claude/hooks/nuget-proxy-forwarder.py new file mode 100755 index 0000000..d44a7e1 --- /dev/null +++ b/.claude/hooks/nuget-proxy-forwarder.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +""" +NuGet Proxy Forwarder for Claude Code + +.NET's HttpClient doesn't properly handle proxy credentials from environment variables. +This forwarder accepts unauthenticated connections on localhost:8888 and forwards them +to the authenticated Claude Code proxy with proper authentication headers. + +Started automatically by install-dotnet.sh as a background daemon. +""" +import socket +import threading +import os +import base64 +import sys +from urllib.parse import urlparse + +def forward_data(source, destination): + """Forward data from source socket to destination socket""" + try: + while True: + data = source.recv(4096) + if not data: + break + destination.sendall(data) + except: + pass + +def handle_client(client_socket, client_addr, proxy_host, proxy_port, proxy_auth_header): + """Handle a client connection""" + try: + # Read the CONNECT request + request = b"" + while b"\r\n\r\n" not in request: + chunk = client_socket.recv(1) + if not chunk: + return + request += chunk + + request_str = request.decode('utf-8', errors='ignore') + lines = request_str.split('\r\n') + + if not lines[0].startswith('CONNECT'): + client_socket.close() + return + + # Parse CONNECT target + target = lines[0].split(' ')[1] + + # Connect to real proxy + proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + proxy_socket.connect((proxy_host, proxy_port)) + + # Forward CONNECT request with authentication + connect_request = f"CONNECT {target} HTTP/1.1\r\n" + connect_request += f"Host: {target}\r\n" + if proxy_auth_header: + connect_request += proxy_auth_header + connect_request += "\r\n" + + proxy_socket.sendall(connect_request.encode()) + + # Read proxy response + response = b"" + while b"\r\n\r\n" not in response: + chunk = proxy_socket.recv(1) + if not chunk: + client_socket.close() + proxy_socket.close() + return + response += chunk + + # Forward response to client + client_socket.sendall(response) + + response_str = response.decode('utf-8', errors='ignore') + status_line = response_str.split('\r\n')[0] + + # If successful, start bidirectional forwarding + if "200" in status_line: + # Start forwarding threads + client_to_proxy = threading.Thread(target=forward_data, args=(client_socket, proxy_socket)) + proxy_to_client = threading.Thread(target=forward_data, args=(proxy_socket, client_socket)) + + client_to_proxy.daemon = True + proxy_to_client.daemon = True + + client_to_proxy.start() + proxy_to_client.start() + + # Wait for either thread to finish + client_to_proxy.join() + proxy_to_client.join() + + except Exception as e: + pass # Silently handle errors to avoid log spam + finally: + try: + client_socket.close() + except: + pass + try: + proxy_socket.close() + except: + pass + +def main(): + # Parse the real proxy from environment + proxy_url = os.environ.get('HTTPS_PROXY') or os.environ.get('HTTP_PROXY') + if not proxy_url: + print("Error: No HTTPS_PROXY or HTTP_PROXY environment variable set", file=sys.stderr) + sys.exit(1) + + proxy_uri = urlparse(proxy_url) + proxy_host = proxy_uri.hostname + proxy_port = proxy_uri.port or 80 + proxy_auth = proxy_uri.username and proxy_uri.password + + if proxy_auth: + auth_string = f"{proxy_uri.username}:{proxy_uri.password}" + proxy_auth_header = "Proxy-Authorization: Basic " + base64.b64encode(auth_string.encode()).decode() + "\r\n" + else: + proxy_auth_header = "" + + # Create server socket + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + try: + server.bind(('127.0.0.1', 8888)) + server.listen(5) + except OSError as e: + # Port already in use - another forwarder is running + sys.exit(0) + + # Daemonize - close stdout/stderr to run silently in background + sys.stdout.close() + sys.stderr.close() + + try: + while True: + client_sock, client_addr = server.accept() + client_thread = threading.Thread( + target=handle_client, + args=(client_sock, client_addr, proxy_host, proxy_port, proxy_auth_header) + ) + client_thread.daemon = True + client_thread.start() + except KeyboardInterrupt: + server.close() + +if __name__ == '__main__': + main() diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..8a552bb --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/install-dotnet.sh" + } + ] + } + ] + } +} diff --git a/.gitattributes b/.gitattributes index 1ff0c42..0d77001 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto +*.sh eol=lf ############################################################################### # Set default behavior for command prompt diff. diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index e0c0049..9536938 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.gitignore b/.gitignore index 3c4efe2..5ef2421 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ *.userosscache *.sln.docstates +.claude/*.local.json + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/Directory.Packages.props b/Directory.Packages.props index 01f75c3..c8ec184 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,11 +3,11 @@ true - - - + + + - - + + \ No newline at end of file diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj index 792a017..3150f11 100644 --- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj +++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net7.0;net8.0;net9.0 + net8.0;net9.0;net10.0 diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs index 5bd483e..67f3b81 100644 --- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs +++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs @@ -23,10 +23,9 @@ public DataStructure( } - [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net60)] - [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net70)] [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net80)] [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net90)] + [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net10_0)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class EnumerableDataReaderBenchmark diff --git a/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj b/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj index 39b1548..d2aec16 100644 --- a/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj +++ b/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0;net9.0 + net8.0;net9.0;net10.0 diff --git a/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj b/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj index c708326..ac0cff8 100644 --- a/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj +++ b/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0;net9.0 + net8.0;net9.0;net10.0 false