perf: align ParseContentLength with StreamJsonRpc pattern, switch hot… #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| build-test: | |
| name: build & test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Restore | |
| run: dotnet restore ClaudeCodeRoslynLspProxy.slnx | |
| - name: Build | |
| run: dotnet build ClaudeCodeRoslynLspProxy.slnx -c Release --no-restore | |
| - name: Test | |
| run: dotnet run --project tests/ClaudeCodeRoslynLspProxy.Tests/ClaudeCodeRoslynLspProxy.Tests.csproj -c Release --no-build | |
| - name: Pack (dotnet tool) | |
| run: dotnet pack src/ClaudeCodeRoslynLspProxy/ClaudeCodeRoslynLspProxy.csproj -c Release --no-build -o ./artifacts | |
| - name: Smoke test — install as global tool & verify launch | |
| shell: bash | |
| run: | | |
| dotnet tool install --global --add-source ./artifacts ClaudeCodeRoslynLspProxy | |
| export PATH="$PATH:$HOME/.dotnet/tools" | |
| # Tool should print the missing-arg error and exit 2 (not crash). | |
| set +e | |
| ClaudeCodeRoslynLspProxy 2>&1 | |
| code=$? | |
| set -e | |
| if [ "$code" != "2" ]; then | |
| echo "expected exit code 2 (missing --server), got $code" | |
| exit 1 | |
| fi | |
| - name: Upload nupkg | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: nupkg | |
| path: ./artifacts/*.nupkg | |
| validate-plugin: | |
| name: validate plugin manifests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Claude Code | |
| run: npm install -g @anthropic-ai/claude-code | |
| - name: Validate marketplace | |
| run: claude plugin validate . | |
| - name: Validate plugin | |
| run: claude plugin validate ./roslyn-lsp | |
| - name: Schema sanity check (.lsp.json shape) | |
| run: | | |
| python3 - <<'EOF' | |
| import json, sys | |
| with open('roslyn-lsp/.lsp.json') as f: | |
| data = json.load(f) | |
| if 'lspServers' in data: | |
| sys.exit("roslyn-lsp/.lsp.json must NOT have an 'lspServers' wrapper — it is keyed by language name directly") | |
| if 'csharp' not in data: | |
| sys.exit("expected 'csharp' as top-level key in roslyn-lsp/.lsp.json") | |
| required = ['command', 'extensionToLanguage'] | |
| missing = [k for k in required if k not in data['csharp']] | |
| if missing: | |
| sys.exit(f"roslyn-lsp/.lsp.json csharp block missing required keys: {missing}") | |
| EOF | |
| real-roslyn-integration: | |
| name: integration test with real Roslyn LSP | |
| runs-on: ubuntu-latest | |
| needs: build-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Build + pack the proxy | |
| run: | | |
| dotnet build src/ClaudeCodeRoslynLspProxy/ClaudeCodeRoslynLspProxy.csproj -c Release | |
| dotnet pack src/ClaudeCodeRoslynLspProxy/ClaudeCodeRoslynLspProxy.csproj -c Release --no-build -o ./artifacts | |
| - name: Install both dotnet tools globally | |
| run: | | |
| dotnet tool install --global --add-source ./artifacts ClaudeCodeRoslynLspProxy | |
| dotnet tool install --global roslyn-language-server --prerelease | |
| echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH" | |
| - name: Drive a real LSP handshake through proxy → Roslyn | |
| run: | | |
| set -euo pipefail | |
| WS=$(mktemp -d) | |
| cat > "$WS/Test.slnx" <<EOF | |
| <Solution> | |
| <Project Path="Test.csproj" /> | |
| </Solution> | |
| EOF | |
| cat > "$WS/Test.csproj" <<EOF | |
| <Project Sdk="Microsoft.NET.Sdk"> | |
| <PropertyGroup> | |
| <TargetFramework>net10.0</TargetFramework> | |
| </PropertyGroup> | |
| </Project> | |
| EOF | |
| echo "public class IntegrationProbe { public void HelloFromCi() {} }" > "$WS/Probe.cs" | |
| WS_URI="file://$WS" | |
| LOG=/tmp/proxy-integration.log | |
| # Build canned LSP input: initialize + initialized. | |
| python3 - "$WS_URI" > /tmp/lsp-input.bin <<'PYEOF' | |
| import json, sys | |
| ws = sys.argv[1] | |
| def frame(o): | |
| body = json.dumps(o).encode() | |
| return f"Content-Length: {len(body)}\r\n\r\n".encode() + body | |
| sys.stdout.buffer.write(frame({ | |
| "jsonrpc": "2.0", "id": 1, "method": "initialize", | |
| "params": {"processId": None, "rootUri": ws, | |
| "workspaceFolders": [{"uri": ws, "name": "test"}], | |
| "capabilities": {}} | |
| })) | |
| sys.stdout.buffer.write(frame({ | |
| "jsonrpc": "2.0", "method": "initialized", "params": {} | |
| })) | |
| PYEOF | |
| # Give the proxy 25 s to receive the handshake, inject solution/open, | |
| # and have Roslyn start the load. We don't need indexing to complete | |
| # for this assertion — only that solution/open hit the wire. | |
| timeout 25 ClaudeCodeRoslynLspProxy \ | |
| --server roslyn-language-server \ | |
| --log "$LOG" \ | |
| -- --stdio --logLevel Information < /tmp/lsp-input.bin > /tmp/proxy-stdout.bin 2> /tmp/proxy-stderr.log || true | |
| echo "--- proxy.log ---" | |
| cat "$LOG" || true | |
| echo "--- proxy stderr (first 80 lines) ---" | |
| head -80 /tmp/proxy-stderr.log || true | |
| if ! grep -q "solution/open" "$LOG"; then | |
| echo "FAIL: proxy log did not report solution/open injection" | |
| exit 1 | |
| fi | |
| echo "PASS: proxy injected solution/open into the real Roslyn LSP." |