Compare commits

..

10 Commits

Author SHA1 Message Date
a7d4f352b7 build(webapi): update publish artifacts and switch to port 5001
Adjust published appsettings.json to listen on http://localhost:5001 and refresh
static web assets + DLL after rebuild.
2025-10-28 18:18:45 -04:00
86500af544 docs: update agent guide and SKILL docs; standardize Web API port 5001
- Update .claude SKILL to use port 5001 in all examples
- Add repo-root SKILL.md consolidated API guide
- Expand AGENTS.md with rb.ps1 usage and policies
2025-10-28 18:18:45 -04:00
daa60c53dd chore(scripts): add rb.ps1 helper for Roslyn Bridge queries
Auto-detects Web API (5001) or VS plugin (59123), prefers compact JSON output,
adds commands for projects, diagnostics, symbol/ref lookup, history, and
solution-overview summary.
2025-10-28 18:18:44 -04:00
cd0f96ca1e feat(webapi): add solution overview summary endpoint
Adds GET /api/roslyn/solution/overview/summary with json|text|yaml formats.
Parses getsolutionoverview payload and renders compact summary for agents.
2025-10-28 18:18:44 -04:00
cade9dcc47 Update roslyn-bridge skill documentation and settings
- Update SKILL.md with comprehensive WebAPI documentation:
  - New architecture diagram showing WebAPI middleware
  - Complete REST API endpoints documentation
  - WebAPI usage examples and best practices
  - Instance discovery and multi-client support
  - History tracking features
  - Updated troubleshooting guide

- Update settings.local.json:
  - Add auto-approval for WebAPI startup commands
  - Add auto-approval for common build and test commands
  - Streamline development workflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:51:59 -04:00
89a349ae72 Add extension management documentation and utilities
- Add EXTENSION_MANAGEMENT.md with detailed instructions for:
  - Manual installation steps
  - Uninstalling previous versions
  - Troubleshooting common issues
  - Finding extension installation paths
  - Using PowerShell script for cleanup

- Add cleanup-and-reinstall.ps1 PowerShell script for:
  - Automated cleanup of extension directories
  - Restarting devenv /updateconfiguration
  - Rebuilding and reinstalling extension
  - Simplifies development workflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:51:46 -04:00
1cbfba3893 Add WebAPI middleware for multi-instance Roslyn Bridge routing
Add RoslynBridge.WebApi - ASP.NET Core 8.0 middleware that:
- Provides a centralized REST API for accessing multiple VS instances
- Manages instance registry with discovery by port, solution, or PID
- Proxies requests to the appropriate VS instance
- Tracks request/response history for debugging
- Auto-cleanup of stale instances via background service

Features:
- Health endpoints: /api/health, /api/health/ping
- Roslyn endpoints: /api/roslyn/projects, /api/roslyn/diagnostics, etc.
- Instance management: /api/instances (register, heartbeat, unregister)
- History tracking: /api/history, /api/history/stats
- Swagger UI at root (/) for API documentation
- CORS enabled for web applications

Services:
- InstanceRegistryService: Thread-safe registry of VS instances
- HistoryService: In-memory request/response history (max 1000 entries)
- InstanceCleanupService: Background service to remove stale instances
- RoslynBridgeClient: HTTP client for proxying to VS instances

Update RoslynBridge.sln to include RoslynBridge.WebApi project.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:51:33 -04:00
716827a665 Add instance registration service for multi-client WebAPI integration
- Create RegistrationService to register VS instances with WebAPI
  - Registers on startup with: port, process ID, solution info
  - Reads WebApiUrl from ConfigurationService
  - Sends heartbeat every N seconds (configurable)
  - Unregisters on shutdown
  - Gracefully handles registration failures (VS extension works standalone)

- Update RoslynBridgePackage to use RegistrationService
  - Creates BridgeServer and gets actual port used
  - Registers with WebAPI using discovered port
  - Cleans up registration on disposal

This enables the WebAPI middleware to discover and route requests to
the correct Visual Studio instance based on solution path, port, or PID.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:51:20 -04:00
1ad0a98d4d Implement automatic port discovery for multi-instance support
- Update BridgeServer to try multiple ports sequentially
  - Reads DefaultPort and MaxPortRange from ConfigurationService
  - Tries ports from DefaultPort to DefaultPort+MaxPortRange
  - Updates _port field to the port that successfully starts
  - Throws exception if no available ports found

- Make _port field mutable to store discovered port
- Add public Port property to expose the actual port being used
- Make startPort parameter optional (nullable), defaults to config value

- Add WriteRawResponseAsync helper method for future use

This allows multiple Visual Studio instances to run simultaneously,
each with their own Roslyn Bridge server on different ports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:51:08 -04:00
48d85e1906 Add configuration system for RoslynBridge extension
- Add appsettings.json with configurable settings:
  - WebApiUrl: URL of the WebAPI middleware (default: http://localhost:5001)
  - DefaultPort: Starting port for VS instances (default: 59123)
  - MaxPortRange: Number of ports to try (default: 10)
  - HeartbeatIntervalSeconds: Heartbeat interval (default: 60)

- Create ConfigurationService singleton to load settings
  - Loads from appsettings.json in extension directory
  - Falls back to sensible defaults if file missing
  - Provides easy property access

- Update RoslynBridge.csproj to include:
  - ConfigurationService.cs in compilation
  - appsettings.json as content (copied to output and included in VSIX)
  - System.Data reference for future use

This allows users to customize extension behavior without recompiling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:50:55 -04:00
89 changed files with 6252 additions and 105 deletions

View File

@@ -20,7 +20,21 @@
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" /t:Restore,Build /p:Configuration=Debug)",
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" RoslynBridge.sln /t:Restore /t:Build /p:Configuration=Debug)",
"Bash(del \".claude\\init.md\")",
"Bash(git add:*)"
"Bash(git add:*)",
"Bash(dotnet --version:*)",
"Bash(dotnet new:*)",
"Bash(del \"C:\\Users\\AJ\\Desktop\\RoslynBridge\\RoslynBridge.WebApi\\Controllers\\WeatherForecastController.cs\")",
"Bash(del \"C:\\Users\\AJ\\Desktop\\RoslynBridge\\RoslynBridge.WebApi\\WeatherForecast.cs\")",
"Bash(dotnet run:*)",
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" \"C:\\Users\\AJ\\Desktop\\RoslynBridge\\RoslynBridge\\RoslynBridge.csproj\" /t:Restore,Build /p:Configuration=Debug)",
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" RoslynBridge.csproj /t:Build /p:Configuration=Debug)",
"Read(//c/Users/AJ/.claude/skills/roslyn-bridge/**)",
"Bash(dotnet add:*)",
"Bash(dotnet publish:*)",
"Read(//c/Users/AJ/AppData/Local/Microsoft/VisualStudio/**)",
"Bash(powershell -ExecutionPolicy Bypass -File install.ps1 -SkipBuild -SkipPublish)",
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" RoslynBridgeRoslynBridge.csproj /t:Restore,Build /p:Configuration=Debug)",
"Bash(git commit:*)"
],
"deny": [],
"ask": []

View File

@@ -3,73 +3,122 @@ name: roslyn-bridge
description: Use this for C# code analysis, querying .NET projects, finding symbols, getting diagnostics, or any Roslyn/semantic analysis tasks using the bridge server
---
# Roslyn Bridge Testing Guide
# Roslyn Bridge API Guide
Use this guide when testing or accessing the Claude Roslyn Bridge HTTP endpoints.
Use this guide when accessing the Roslyn Bridge for C# code analysis.
## Server Info
- **Base URL**: `http://localhost:59123`
- **Endpoints**:
- `/query` - Main query endpoint for all Roslyn operations
- `/health` - Health check endpoint to verify server is running
- **Method**: POST
## Architecture
```
┌─────────────┐ REST API ┌────────────────────┐ HTTP ┌─────────────────────┐
Claude │ ◄─────────────────► │ Web API (:5001) │ ◄────────────► │ VS Plugin (:59123) │
│ AI │ │ Middleware │ │ Roslyn Bridge │
└─────────────┘ └────────────────────┘ └─────────────────────┘
```
## Recommended: Web API (Port 5001)
**Base URL**: `http://localhost:5001`
The Web API provides a modern RESTful interface with:
- Clean REST endpoints with query parameters
- Full Swagger documentation at `/`
- Request/response history tracking at `/api/history`
- CORS support for web applications
- Better error handling and logging
### Web API Endpoints
**Health & Info:**
- `GET /api/health` - Check health of both Web API and VS plugin
- `GET /api/health/ping` - Simple ping
**Roslyn Operations:**
- `GET /api/roslyn/projects` - Get all projects
- `GET /api/roslyn/solution/overview` - Solution statistics
- `GET /api/roslyn/diagnostics?filePath={path}` - Get errors/warnings
- `GET /api/roslyn/symbol?filePath={path}&line={line}&column={col}` - Get symbol info
- `GET /api/roslyn/references?filePath={path}&line={line}&column={col}` - Find references
- `GET /api/roslyn/symbol/search?symbolName={name}&kind={kind}` - Search symbols
- `POST /api/roslyn/query` - Execute any query (fallback to raw queries)
- `POST /api/roslyn/format` - Format document
- `POST /api/roslyn/project/package/add` - Add NuGet package
- `POST /api/roslyn/project/build` - Build project
**History:**
- `GET /api/history` - All history entries
- `GET /api/history/{id}` - Specific entry
- `GET /api/history/recent?count=50` - Recent entries
- `GET /api/history/stats` - Statistics
- `DELETE /api/history` - Clear history
## Alternative: Direct VS Plugin Access (Port 59123)
**Base URL**: `http://localhost:59123`
Direct access to the Visual Studio plugin (use only if Web API is unavailable):
- **Endpoints**: `/query`, `/health`
- **Method**: POST only
- **Content-Type**: application/json
## ⚠️ CRITICAL: Command Syntax Rules
**ALWAYS use curl with the Bash tool. NEVER pipe curl output to PowerShell.**
### ✅ CORRECT: Using curl with Bash tool
### ✅ CORRECT: Using Web API (Recommended)
```bash
# Test if server is running
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}"
# Health check
curl http://localhost:5001/api/health
# Get diagnostics
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdiagnostics\"}"
# Get all projects (returns full file paths)
curl http://localhost:5001/api/roslyn/projects
# Get document info
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdocument\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}"
# Get solution overview
curl http://localhost:5001/api/roslyn/solution/overview
# Get symbol at position
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsymbol\",\"filePath\":\"C:\\\\Users\\\\AJ\\\\Desktop\\\\PepLib\\\\PepLib\\\\Program.cs\",\"line\":10,\"column\":5}"
# Get diagnostics for entire solution
curl http://localhost:5001/api/roslyn/diagnostics
# Get diagnostics for specific file
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=C:/path/to/file.cs"
# Get symbol at position (use paths from /projects response)
curl "http://localhost:5001/api/roslyn/symbol?filePath=C:/path/to/file.cs&line=10&column=5"
# Find all references
curl "http://localhost:5001/api/roslyn/references?filePath=C:/path/to/file.cs&line=18&column=30"
# Search for symbols
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=MyClass&kind=class"
# Get recent history
curl http://localhost:5001/api/history/recent?count=10
# Get history statistics
curl http://localhost:5001/api/history/stats
```
**IMPORTANT curl syntax rules:**
- Use `-X POST` (NOT `-Method POST`)
- Use `-H` for headers (NOT `-Headers`)
- Use `-d` for data (NOT `-Body`)
- Escape backslashes in file paths: `\\\\` becomes `\\` in JSON
- For GET requests, no `-X GET` needed
- Use quotes around URLs with query parameters
- Forward slashes `/` work in file paths (Windows accepts both `/` and `\`)
- **DO NOT pipe to PowerShell** - the JSON output is already formatted
### ❌ WRONG: DO NOT DO THIS
### Using Direct VS Plugin Access (Fallback)
```bash
# NEVER pipe curl to PowerShell - this will fail on Windows
curl ... | powershell -Command "$json = $input | ..." # ❌ WRONG
# Health check - verify VS plugin is running
curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"
# Get projects via POST
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}"
# Get symbol at position
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsymbol\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\",\"line\":10,\"column\":5}"
```
### Using PowerShell (Alternative)
Run these commands **directly in PowerShell**, NOT via `powershell -Command`:
```powershell
# Test server
$body = @{queryType='getprojects'} | ConvertTo-Json
Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -ContentType 'application/json'
# Get document
$body = @{
queryType='getdocument'
filePath='C:\path\to\file.cs'
} | ConvertTo-Json
Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -ContentType 'application/json'
```
**IMPORTANT PowerShell rules:**
- Run directly in PowerShell console, NOT via `bash -c` or `powershell -Command`
- Use single quotes around URI and content type
- File paths don't need escaping in PowerShell hash tables
**Note:** In POST requests to VS plugin, escape backslashes: `\\\\` becomes `\\` in JSON
## Quick Reference: All Endpoints
@@ -115,82 +164,104 @@ Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -
| `restorepackages` | `projectName` | - | Restore NuGet packages |
| `createdirectory` | `directoryPath` | - | Create a new directory |
## Common Test Examples
## Common Workflow Examples
### Typical Code Analysis Workflow
```bash
# Health check - verify server is running
curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"
# Step 1: Check if services are healthy
curl http://localhost:5001/api/health
# Get all projects in solution
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}"
# Step 2: Get all projects and their files
curl http://localhost:5001/api/roslyn/projects
# Response includes: {"data": [{"documents": ["C:/Full/Path/To/File.cs", ...]}]}
# Get solution overview
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsolutionoverview\"}"
# Step 3: Use the full paths from Step 2 in subsequent queries
FILE="C:/Users/AJ/Desktop/MyProject/Program.cs"
# Get diagnostics for entire solution
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdiagnostics\"}"
# Get diagnostics (errors/warnings) for a file
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=$FILE"
# Get semantic model for a file
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsemanticmodel\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}"
# Get symbol information at a specific location
curl "http://localhost:5001/api/roslyn/symbol?filePath=$FILE&line=15&column=10"
# Get syntax tree for a file
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsyntaxtree\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}"
# Find all references to that symbol
curl "http://localhost:5001/api/roslyn/references?filePath=$FILE&line=15&column=10"
# Find all classes containing "Helper"
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"searchcode\",\"symbolName\":\".*Helper\",\"parameters\":{\"scope\":\"classes\"}}"
# Step 4: View your query history
curl http://localhost:5001/api/history/recent?count=5
# Format a document
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"formatdocument\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}"
# Step 5: Get statistics about your queries
curl http://localhost:5001/api/history/stats
```
### Advanced Query Examples
```bash
# Search for all classes with "Service" in the name
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=Service&kind=class"
# Get solution-wide statistics
curl http://localhost:5001/api/roslyn/solution/overview
# For complex queries not available as REST endpoints, use POST /query
curl -X POST http://localhost:5001/api/roslyn/query \
-H "Content-Type: application/json" \
-d '{"queryType":"searchcode","symbolName":".*Controller","parameters":{"scope":"classes"}}'
# Build a project
curl -X POST "http://localhost:5001/api/roslyn/project/build?projectName=MyProject"
# Add NuGet package
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"addnugetpackage\",\"projectName\":\"RoslynBridge\",\"packageName\":\"Newtonsoft.Json\"}"
# Add NuGet package with version
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"addnugetpackage\",\"projectName\":\"RoslynBridge\",\"packageName\":\"Newtonsoft.Json\",\"version\":\"13.0.3\"}"
# Remove NuGet package
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"removenugetpackage\",\"projectName\":\"RoslynBridge\",\"packageName\":\"Newtonsoft.Json\"}"
# Build project
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"buildproject\",\"projectName\":\"RoslynBridge\"}"
# Build with configuration
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"buildproject\",\"projectName\":\"RoslynBridge\",\"configuration\":\"Release\"}"
# Clean project
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"cleanproject\",\"projectName\":\"RoslynBridge\"}"
# Restore packages
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"restorepackages\",\"projectName\":\"RoslynBridge\"}"
# Create directory
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"createdirectory\",\"directoryPath\":\"C:\\\\path\\\\to\\\\new\\\\directory\"}"
curl -X POST "http://localhost:5001/api/roslyn/project/package/add?projectName=MyProject&packageName=Newtonsoft.Json&version=13.0.3"
```
## Response Format
Success:
All endpoints return JSON in this format:
**Success:**
```json
{
"success": true,
"message": "Optional message",
"data": { /* Response data */ }
"data": { /* Response data varies by endpoint */ },
"error": null
}
```
Error:
**Error:**
```json
{
"success": false,
"error": "Error message",
"data": null
"message": null,
"data": null,
"error": "Error description"
}
```
## Notes
## Important Notes
- Line numbers are **1-based**
- Column numbers are **0-based**
- File paths in JSON need escaped backslashes: `C:\\path\\to\\file.cs`
- All workspace modifications use VS threading model (`JoinableTaskFactory.SwitchToMainThreadAsync()`)
- The Visual Studio extension must be running for endpoints to work
- Line numbers are **1-based** (first line is 1)
- Column numbers are **0-based** (first column is 0)
- **Use full paths from `/api/roslyn/projects` response** in other endpoints
- Forward slashes `/` work in file paths (Windows accepts both `/` and `\`)
- Both the Web API (port 5000) and VS Plugin (port 59123) must be running
- All workspace modifications use VS threading model
- Request history is automatically tracked in the Web API
## Troubleshooting
**"Failed to connect" error:**
1. Check if Web API is running: `curl http://localhost:5001/api/health/ping`
2. Check if VS Plugin is running: `curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"`
3. Ensure Visual Studio is open with a solution loaded
**"vsPluginStatus": "Disconnected":**
- The VS Plugin (port 59123) is not running
- Open Visual Studio and ensure the RoslynBridge extension is loaded
**Empty or null data:**
- Ensure you're using the correct file paths from `/api/roslyn/projects`
- Verify line/column numbers are within the file bounds
- Check that the file is part of the currently open VS solution

View File

@@ -37,3 +37,56 @@
- Server binds to `http://localhost:59123/` and accepts only POST; CORS is permissive for local tooling. Do not expose externally.
- Endpoints: `/query` (main), `/health` route exists but still requires POST.
- Adjust port/paths in `RoslynBridge/Constants/ServerConstants.cs` if needed.
## Agent Script Usage: rb.ps1 (for AI agents)
This section is for the agent only. Use `rb.ps1` to minimize tokens and speed up local Roslyn queries.
- Location: `rb.ps1` (repo root). Invoke with PowerShell.
- Auto-detects endpoints: prefers Web API (`http://localhost:5001`), falls back to VS plugin (`http://localhost:59123`).
- Output defaults to compact JSON. Use `-Raw` to emit raw response strings, `-Pretty` only for debugging.
- Override detection with `-BaseUrl http://localhost:5001` if needed; `-NoDetect` forces Web API pathing.
Examples (run from repo root):
```powershell
# Health and discovery
powershell -NoProfile -Command .\rb.ps1 health
powershell -NoProfile -Command .\rb.ps1 projects -Raw
powershell -NoProfile -Command .\rb.ps1 overview
# File-specific queries (use full paths from `projects`)
powershell -NoProfile -Command .\rb.ps1 diagnostics -FilePath C:/full/path/File.cs
powershell -NoProfile -Command .\rb.ps1 symbol -FilePath C:/full/path/File.cs -Line 12 -Column 4
powershell -NoProfile -Command .\rb.ps1 references -FilePath C:/full/path/File.cs -Line 12 -Column 4 -Raw
# Symbol search
powershell -NoProfile -Command .\rb.ps1 search -SymbolName Service -Kind class
# Project operations
powershell -NoProfile -Command .\rb.ps1 build -ProjectName MyProject
powershell -NoProfile -Command .\rb.ps1 package-add -ProjectName MyProject -PackageName Newtonsoft.Json -Version 13.0.3
# History (Web API only)
powershell -NoProfile -Command .\rb.ps1 history-recent -Count 10
powershell -NoProfile -Command .\rb.ps1 history-stats
# Compact solution overview summary (uses API-level summary)
powershell -NoProfile -Command .\rb.ps1 solution-overview -Format yaml -Top 5
```
Key behaviors and assumptions:
- Lines are 1-based; columns 0-based.
- Forward slashes `/` are accepted in file paths.
- Prefer `-Raw` to minimize token usage when chaining outputs.
- If Web API is down, commands transparently fall back to the plugin when possible. History endpoints require Web API.
- If auto-detection is ambiguous, set `-BaseUrl` explicitly.
Policy (agent-only):
- Do NOT call `curl`, `Invoke-RestMethod`, or `Invoke-WebRequest` directly to Roslyn Bridge.
- Always invoke `rb.ps1` for all Roslyn-related queries and operations.
- Prefer `-Raw` output and parse only whats needed to reduce tokens.
- Keep timeouts short unless debugging (`-TimeoutSec` can be raised temporarily).
- Avoid pretty output unless explicitly needed (`-Pretty`).

View File

@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Mvc;
using RoslynBridge.WebApi.Models;
using RoslynBridge.WebApi.Services;
namespace RoslynBridge.WebApi.Controllers;
/// <summary>
/// Health check and status controller
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class HealthController : ControllerBase
{
private readonly IRoslynBridgeClient _bridgeClient;
private readonly ILogger<HealthController> _logger;
public HealthController(IRoslynBridgeClient bridgeClient, ILogger<HealthController> logger)
{
_bridgeClient = bridgeClient;
_logger = logger;
}
/// <summary>
/// Get the health status of the middleware and Visual Studio plugin
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Health status information</returns>
/// <response code="200">Service is healthy</response>
/// <response code="503">Service is unhealthy</response>
[HttpGet]
[ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status503ServiceUnavailable)]
public async Task<ActionResult<HealthCheckResponse>> GetHealth(CancellationToken cancellationToken)
{
var response = new HealthCheckResponse();
try
{
var isVsPluginHealthy = await _bridgeClient.IsHealthyAsync(null, cancellationToken);
response.VsPluginStatus = isVsPluginHealthy ? "Connected" : "Disconnected";
if (!isVsPluginHealthy)
{
response.Status = "Degraded";
_logger.LogWarning("Visual Studio plugin is not accessible");
return StatusCode(StatusCodes.Status503ServiceUnavailable, response);
}
_logger.LogInformation("Health check passed");
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Health check failed");
response.Status = "Unhealthy";
response.VsPluginStatus = "Error";
return StatusCode(StatusCodes.Status503ServiceUnavailable, response);
}
}
/// <summary>
/// Simple ping endpoint
/// </summary>
/// <returns>Pong response</returns>
[HttpGet("ping")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
public IActionResult Ping()
{
return Ok(new { message = "pong", timestamp = DateTime.UtcNow });
}
}

View File

@@ -0,0 +1,117 @@
using Microsoft.AspNetCore.Mvc;
using RoslynBridge.WebApi.Models;
using RoslynBridge.WebApi.Services;
namespace RoslynBridge.WebApi.Controllers;
/// <summary>
/// Controller for accessing query history
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class HistoryController : ControllerBase
{
private readonly IHistoryService _historyService;
private readonly ILogger<HistoryController> _logger;
public HistoryController(IHistoryService historyService, ILogger<HistoryController> logger)
{
_historyService = historyService;
_logger = logger;
}
/// <summary>
/// Get all history entries
/// </summary>
/// <returns>List of all history entries</returns>
/// <response code="200">Returns the list of history entries</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<QueryHistoryEntry>), StatusCodes.Status200OK)]
public ActionResult<IEnumerable<QueryHistoryEntry>> GetAll()
{
var entries = _historyService.GetAll();
return Ok(entries);
}
/// <summary>
/// Get a specific history entry by ID
/// </summary>
/// <param name="id">The history entry ID</param>
/// <returns>The history entry</returns>
/// <response code="200">Returns the history entry</response>
/// <response code="404">Entry not found</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(QueryHistoryEntry), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryHistoryEntry> GetById(string id)
{
var entry = _historyService.GetById(id);
if (entry == null)
{
return NotFound(new { message = $"History entry {id} not found" });
}
return Ok(entry);
}
/// <summary>
/// Get recent history entries
/// </summary>
/// <param name="count">Number of entries to return (default: 50, max: 500)</param>
/// <returns>List of recent history entries</returns>
/// <response code="200">Returns the list of recent entries</response>
[HttpGet("recent")]
[ProducesResponseType(typeof(IEnumerable<QueryHistoryEntry>), StatusCodes.Status200OK)]
public ActionResult<IEnumerable<QueryHistoryEntry>> GetRecent([FromQuery] int count = 50)
{
if (count > 500) count = 500;
if (count < 1) count = 1;
var entries = _historyService.GetRecent(count);
return Ok(entries);
}
/// <summary>
/// Get history statistics
/// </summary>
/// <returns>Statistics about history entries</returns>
[HttpGet("stats")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
public ActionResult GetStats()
{
var entries = _historyService.GetAll();
var stats = new
{
totalEntries = _historyService.GetCount(),
successfulRequests = entries.Count(e => e.Success),
failedRequests = entries.Count(e => !e.Success),
averageDurationMs = entries.Any() ? entries.Average(e => e.DurationMs) : 0,
oldestEntry = entries.Any() ? entries.Last().Timestamp : (DateTime?)null,
newestEntry = entries.Any() ? entries.First().Timestamp : (DateTime?)null,
topPaths = entries
.GroupBy(e => e.Path)
.OrderByDescending(g => g.Count())
.Take(10)
.Select(g => new { path = g.Key, count = g.Count() })
};
return Ok(stats);
}
/// <summary>
/// Clear all history entries
/// </summary>
/// <returns>Confirmation message</returns>
/// <response code="200">History cleared successfully</response>
[HttpDelete]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
public ActionResult Clear()
{
var count = _historyService.GetCount();
_historyService.Clear();
_logger.LogInformation("History cleared: {Count} entries removed", count);
return Ok(new { message = $"History cleared: {count} entries removed" });
}
}

View File

@@ -0,0 +1,168 @@
using Microsoft.AspNetCore.Mvc;
using RoslynBridge.WebApi.Models;
using RoslynBridge.WebApi.Services;
namespace RoslynBridge.WebApi.Controllers;
/// <summary>
/// Controller for managing Visual Studio instance registrations
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class InstancesController : ControllerBase
{
private readonly IInstanceRegistryService _registryService;
private readonly ILogger<InstancesController> _logger;
public InstancesController(
IInstanceRegistryService registryService,
ILogger<InstancesController> logger)
{
_registryService = registryService;
_logger = logger;
}
/// <summary>
/// Register a new Visual Studio instance
/// </summary>
/// <param name="instance">Instance information</param>
/// <returns>Registration result</returns>
[HttpPost("register")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Register([FromBody] VSInstanceInfo instance)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_registryService.Register(instance);
return Ok(new
{
success = true,
message = "Instance registered successfully",
processId = instance.ProcessId,
port = instance.Port
});
}
/// <summary>
/// Unregister a Visual Studio instance
/// </summary>
/// <param name="processId">Process ID of the instance to unregister</param>
/// <returns>Unregistration result</returns>
[HttpPost("unregister/{processId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult Unregister(int processId)
{
var removed = _registryService.Unregister(processId);
if (!removed)
{
return NotFound(new { success = false, message = "Instance not found" });
}
return Ok(new { success = true, message = "Instance unregistered successfully" });
}
/// <summary>
/// Update heartbeat for a Visual Studio instance
/// </summary>
/// <param name="processId">Process ID of the instance</param>
/// <returns>Heartbeat update result</returns>
[HttpPost("heartbeat/{processId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult Heartbeat(int processId)
{
var updated = _registryService.UpdateHeartbeat(processId);
if (!updated)
{
return NotFound(new { success = false, message = "Instance not found" });
}
return Ok(new { success = true, message = "Heartbeat updated" });
}
/// <summary>
/// Get all registered Visual Studio instances
/// </summary>
/// <returns>List of registered instances</returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<VSInstanceInfo>), StatusCodes.Status200OK)]
public IActionResult GetAll()
{
var instances = _registryService.GetAllInstances();
return Ok(instances);
}
/// <summary>
/// Get instance by process ID
/// </summary>
/// <param name="processId">Process ID</param>
/// <returns>Instance information</returns>
[HttpGet("by-pid/{processId}")]
[ProducesResponseType(typeof(VSInstanceInfo), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetByProcessId(int processId)
{
var instance = _registryService.GetByProcessId(processId);
if (instance == null)
{
return NotFound(new { success = false, message = "Instance not found" });
}
return Ok(instance);
}
/// <summary>
/// Get instance by solution path
/// </summary>
/// <param name="solutionPath">Solution file path</param>
/// <returns>Instance information</returns>
[HttpGet("by-solution")]
[ProducesResponseType(typeof(VSInstanceInfo), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetBySolutionPath([FromQuery] string solutionPath)
{
if (string.IsNullOrEmpty(solutionPath))
{
return BadRequest(new { success = false, message = "Solution path is required" });
}
var instance = _registryService.GetBySolutionPath(solutionPath);
if (instance == null)
{
return NotFound(new { success = false, message = "No instance found for this solution" });
}
return Ok(instance);
}
/// <summary>
/// Get instance by port
/// </summary>
/// <param name="port">Port number</param>
/// <returns>Instance information</returns>
[HttpGet("by-port/{port}")]
[ProducesResponseType(typeof(VSInstanceInfo), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetByPort(int port)
{
var instance = _registryService.GetByPort(port);
if (instance == null)
{
return NotFound(new { success = false, message = "Instance not found" });
}
return Ok(instance);
}
}

View File

@@ -0,0 +1,410 @@
using Microsoft.AspNetCore.Mvc;
using RoslynBridge.WebApi.Models;
using System.Text;
using System.Text.Json;
using System.Linq;
using RoslynBridge.WebApi.Services;
namespace RoslynBridge.WebApi.Controllers;
/// <summary>
/// Controller for Roslyn code analysis operations
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class RoslynController : ControllerBase
{
private readonly IRoslynBridgeClient _bridgeClient;
private readonly ILogger<RoslynController> _logger;
public RoslynController(IRoslynBridgeClient bridgeClient, ILogger<RoslynController> logger)
{
_bridgeClient = bridgeClient;
_logger = logger;
}
/// <summary>
/// Execute a Roslyn query
/// </summary>
/// <param name="request">The query request</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The query result</returns>
/// <response code="200">Query executed successfully</response>
/// <response code="400">Invalid request</response>
/// <response code="500">Internal server error</response>
[HttpPost("query")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<RoslynQueryResponse>> ExecuteQuery(
[FromBody] RoslynQueryRequest request,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_logger.LogInformation("Received query request: {QueryType}", request.QueryType);
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
if (!result.Success)
{
_logger.LogWarning("Query failed: {Error}", result.Error);
}
return Ok(result);
}
/// <summary>
/// Get all projects in the solution
/// </summary>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of projects</returns>
[HttpGet("projects")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> GetProjects(
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest { QueryType = "getprojects" };
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Get solution overview
/// </summary>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Solution statistics and overview</returns>
[HttpGet("solution/overview")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> GetSolutionOverview(
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest { QueryType = "getsolutionoverview" };
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Get a compact summary of the solution overview
/// </summary>
/// <param name="topNProjects">How many projects to list (by file count)</param>
/// <param name="includeNamespaces">Include top-level namespaces in summary</param>
/// <param name="format">One of: json | text | yaml</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Compact summary as JSON or plain text</returns>
[HttpGet("solution/overview/summary")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<IActionResult> GetSolutionOverviewSummary(
[FromQuery] int topNProjects = 5,
[FromQuery] bool includeNamespaces = true,
[FromQuery] string? format = "json",
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest { QueryType = "getsolutionoverview" };
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
if (!result.Success)
{
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
{
return Ok(result);
}
var err = result.Error ?? "Unknown error";
return Content($"error: {err}", "text/plain", Encoding.UTF8);
}
// Expect Data as JsonElement; shape per Models.SolutionOverview
int projectCount = 0;
int documentCount = 0;
var projectsSummary = new List<(string Name, int FileCount)>();
var topLevelNamespaces = new List<string>();
if (result.Data is JsonElement dataEl && dataEl.ValueKind == JsonValueKind.Object)
{
if (dataEl.TryGetProperty("projectCount", out var pc)) projectCount = pc.GetInt32();
if (dataEl.TryGetProperty("documentCount", out var dc)) documentCount = dc.GetInt32();
if (includeNamespaces && dataEl.TryGetProperty("topLevelNamespaces", out var nsEl) && nsEl.ValueKind == JsonValueKind.Array)
{
foreach (var ns in nsEl.EnumerateArray())
{
if (ns.ValueKind == JsonValueKind.String)
{
topLevelNamespaces.Add(ns.GetString() ?? string.Empty);
}
}
}
if (dataEl.TryGetProperty("projects", out var prEl) && prEl.ValueKind == JsonValueKind.Array)
{
foreach (var p in prEl.EnumerateArray())
{
string name = p.TryGetProperty("name", out var nEl) && nEl.ValueKind == JsonValueKind.String ? (nEl.GetString() ?? string.Empty) : string.Empty;
int files = p.TryGetProperty("fileCount", out var fEl) && fEl.ValueKind == JsonValueKind.Number ? fEl.GetInt32() : 0;
projectsSummary.Add((name, files));
}
}
}
var topProjects = projectsSummary
.OrderByDescending(p => p.FileCount)
.ThenBy(p => p.Name)
.Take(Math.Max(0, topNProjects))
.ToList();
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
{
var compact = new
{
projectCount,
documentCount,
projects = topProjects.Select(p => new { name = p.Name, files = p.FileCount }).ToList(),
topLevelNamespaces = includeNamespaces ? topLevelNamespaces : null
};
return Ok(new RoslynQueryResponse
{
Success = true,
Message = "Compact solution overview",
Data = compact,
Error = null
});
}
// text or yaml: render a compact string
var sb = new StringBuilder();
if (string.Equals(format, "yaml", StringComparison.OrdinalIgnoreCase))
{
sb.AppendLine($"projectCount: {projectCount}");
sb.AppendLine($"documentCount: {documentCount}");
if (includeNamespaces)
{
sb.Append("topLevelNamespaces: [");
sb.Append(string.Join(", ", topLevelNamespaces));
sb.AppendLine("]");
}
sb.AppendLine("projects:");
foreach (var p in topProjects)
{
sb.AppendLine($" - name: {p.Name}");
sb.AppendLine($" files: {p.FileCount}");
}
}
else
{
// text
sb.AppendLine($"Projects: {projectCount} | Documents: {documentCount}");
if (includeNamespaces && topLevelNamespaces.Count > 0)
{
sb.AppendLine($"TopNamespaces: {string.Join(", ", topLevelNamespaces)}");
}
sb.AppendLine("Top Projects:");
foreach (var p in topProjects)
{
sb.AppendLine($" - {p.Name} ({p.FileCount})");
}
}
return Content(sb.ToString(), "text/plain", Encoding.UTF8);
}
/// <summary>
/// Get diagnostics (errors and warnings)
/// </summary>
/// <param name="filePath">Optional file path to filter diagnostics</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of diagnostics</returns>
[HttpGet("diagnostics")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> GetDiagnostics(
[FromQuery] string? filePath = null,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "getdiagnostics",
FilePath = filePath
};
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Get symbol information at a specific position
/// </summary>
/// <param name="filePath">File path</param>
/// <param name="line">Line number (1-based)</param>
/// <param name="column">Column number (0-based)</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Symbol information</returns>
[HttpGet("symbol")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> GetSymbol(
[FromQuery] string filePath,
[FromQuery] int line,
[FromQuery] int column,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "getsymbol",
FilePath = filePath,
Line = line,
Column = column
};
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Find all references to a symbol
/// </summary>
/// <param name="filePath">File path</param>
/// <param name="line">Line number (1-based)</param>
/// <param name="column">Column number (0-based)</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of references</returns>
[HttpGet("references")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> FindReferences(
[FromQuery] string filePath,
[FromQuery] int line,
[FromQuery] int column,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "findreferences",
FilePath = filePath,
Line = line,
Column = column
};
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Search for symbols by name
/// </summary>
/// <param name="symbolName">Symbol name or pattern</param>
/// <param name="kind">Optional symbol kind filter</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of matching symbols</returns>
[HttpGet("symbol/search")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> FindSymbol(
[FromQuery] string symbolName,
[FromQuery] string? kind = null,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "findsymbol",
SymbolName = symbolName
};
if (!string.IsNullOrEmpty(kind))
{
request.Parameters = new Dictionary<string, string> { ["kind"] = kind };
}
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Format a document
/// </summary>
/// <param name="filePath">File path to format</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Format operation result</returns>
[HttpPost("format")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> FormatDocument(
[FromBody] string filePath,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "formatdocument",
FilePath = filePath
};
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Add a NuGet package to a project
/// </summary>
/// <param name="projectName">Project name</param>
/// <param name="packageName">NuGet package name</param>
/// <param name="version">Optional package version</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Operation result</returns>
[HttpPost("project/package/add")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> AddNuGetPackage(
[FromQuery] string projectName,
[FromQuery] string packageName,
[FromQuery] string? version = null,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "addnugetpackage",
ProjectName = projectName,
PackageName = packageName,
Version = version
};
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
/// <summary>
/// Build a project
/// </summary>
/// <param name="projectName">Project name</param>
/// <param name="configuration">Build configuration (Debug/Release)</param>
/// <param name="instancePort">Optional: specific VS instance port to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Build result</returns>
[HttpPost("project/build")]
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<RoslynQueryResponse>> BuildProject(
[FromQuery] string projectName,
[FromQuery] string? configuration = null,
[FromQuery] int? instancePort = null,
CancellationToken cancellationToken = default)
{
var request = new RoslynQueryRequest
{
QueryType = "buildproject",
ProjectName = projectName,
Configuration = configuration
};
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
return Ok(result);
}
}

View File

@@ -0,0 +1,132 @@
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using RoslynBridge.WebApi.Models;
using RoslynBridge.WebApi.Services;
namespace RoslynBridge.WebApi.Middleware;
/// <summary>
/// Middleware to capture and log all Roslyn API requests and responses
/// </summary>
public class HistoryMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<HistoryMiddleware> _logger;
public HistoryMiddleware(RequestDelegate next, ILogger<HistoryMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IHistoryService historyService)
{
// Only track Roslyn API endpoints
if (!context.Request.Path.StartsWithSegments("/api/roslyn"))
{
await _next(context);
return;
}
var stopwatch = Stopwatch.StartNew();
var historyEntry = new QueryHistoryEntry
{
Timestamp = DateTime.UtcNow,
Path = context.Request.Path,
Method = context.Request.Method,
ClientIp = context.Connection.RemoteIpAddress?.ToString()
};
// Capture request
RoslynQueryRequest? request = null;
if (context.Request.Method == "POST" && context.Request.ContentLength > 0)
{
context.Request.EnableBuffering();
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true);
var requestBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
try
{
request = JsonSerializer.Deserialize<RoslynQueryRequest>(requestBody, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
historyEntry.Request = request;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to deserialize request for history");
}
}
// Capture response
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
try
{
await _next(context);
stopwatch.Stop();
historyEntry.DurationMs = stopwatch.ElapsedMilliseconds;
historyEntry.Success = context.Response.StatusCode < 400;
// Read response
responseBody.Seek(0, SeekOrigin.Begin);
var responseText = await new StreamReader(responseBody).ReadToEndAsync();
responseBody.Seek(0, SeekOrigin.Begin);
try
{
var response = JsonSerializer.Deserialize<RoslynQueryResponse>(responseText, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
historyEntry.Response = response;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to deserialize response for history");
}
// Copy response back
await responseBody.CopyToAsync(originalBodyStream);
}
catch (Exception ex)
{
stopwatch.Stop();
historyEntry.DurationMs = stopwatch.ElapsedMilliseconds;
historyEntry.Success = false;
_logger.LogError(ex, "Error in request processing");
throw;
}
finally
{
context.Response.Body = originalBodyStream;
// Add to history
try
{
historyService.Add(historyEntry);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to add entry to history");
}
}
}
}
/// <summary>
/// Extension methods for adding history middleware
/// </summary>
public static class HistoryMiddlewareExtensions
{
public static IApplicationBuilder UseHistoryTracking(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HistoryMiddleware>();
}
}

View File

@@ -0,0 +1,32 @@
namespace RoslynBridge.WebApi.Models;
/// <summary>
/// Health check response for service status
/// </summary>
public class HealthCheckResponse
{
/// <summary>
/// Overall health status
/// </summary>
public string Status { get; set; } = "Healthy";
/// <summary>
/// Web API service status
/// </summary>
public string WebApiStatus { get; set; } = "Running";
/// <summary>
/// Visual Studio plugin connection status
/// </summary>
public string VsPluginStatus { get; set; } = "Unknown";
/// <summary>
/// Timestamp of the health check
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
/// <summary>
/// API version
/// </summary>
public string Version { get; set; } = "1.0.0";
}

View File

@@ -0,0 +1,52 @@
namespace RoslynBridge.WebApi.Models;
/// <summary>
/// Represents a single query history entry with request and response information
/// </summary>
public class QueryHistoryEntry
{
/// <summary>
/// Unique identifier for this history entry
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Timestamp when the request was received
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
/// <summary>
/// The endpoint path that was called
/// </summary>
public string Path { get; set; } = string.Empty;
/// <summary>
/// HTTP method used
/// </summary>
public string Method { get; set; } = string.Empty;
/// <summary>
/// The Roslyn query request
/// </summary>
public RoslynQueryRequest? Request { get; set; }
/// <summary>
/// The Roslyn query response
/// </summary>
public RoslynQueryResponse? Response { get; set; }
/// <summary>
/// Request duration in milliseconds
/// </summary>
public long DurationMs { get; set; }
/// <summary>
/// Whether the request was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Client IP address
/// </summary>
public string? ClientIp { get; set; }
}

View File

@@ -0,0 +1,65 @@
using System.ComponentModel.DataAnnotations;
namespace RoslynBridge.WebApi.Models;
/// <summary>
/// Request model for Roslyn query operations
/// </summary>
public class RoslynQueryRequest
{
/// <summary>
/// Type of query to execute (e.g., "getsymbol", "getdocument", "findreferences")
/// </summary>
[Required]
public string QueryType { get; set; } = string.Empty;
/// <summary>
/// File path for file-based operations
/// </summary>
public string? FilePath { get; set; }
/// <summary>
/// Symbol name for symbol-based operations
/// </summary>
public string? SymbolName { get; set; }
/// <summary>
/// Line number (1-based) for position-based operations
/// </summary>
public int? Line { get; set; }
/// <summary>
/// Column number (0-based) for position-based operations
/// </summary>
public int? Column { get; set; }
/// <summary>
/// Additional parameters for the query
/// </summary>
public Dictionary<string, string>? Parameters { get; set; }
/// <summary>
/// Project name for project operations
/// </summary>
public string? ProjectName { get; set; }
/// <summary>
/// Package name for NuGet operations
/// </summary>
public string? PackageName { get; set; }
/// <summary>
/// Version for NuGet package operations
/// </summary>
public string? Version { get; set; }
/// <summary>
/// Build configuration (Debug/Release)
/// </summary>
public string? Configuration { get; set; }
/// <summary>
/// Directory path for directory operations
/// </summary>
public string? DirectoryPath { get; set; }
}

View File

@@ -0,0 +1,27 @@
namespace RoslynBridge.WebApi.Models;
/// <summary>
/// Response model for Roslyn query operations
/// </summary>
public class RoslynQueryResponse
{
/// <summary>
/// Indicates whether the operation was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Optional message providing additional context
/// </summary>
public string? Message { get; set; }
/// <summary>
/// The response data (structure varies by query type)
/// </summary>
public object? Data { get; set; }
/// <summary>
/// Error message if the operation failed
/// </summary>
public string? Error { get; set; }
}

View File

@@ -0,0 +1,42 @@
namespace RoslynBridge.WebApi.Models;
/// <summary>
/// Information about a registered Visual Studio instance
/// </summary>
public class VSInstanceInfo
{
/// <summary>
/// The port number where this VS instance is listening
/// </summary>
public int Port { get; set; }
/// <summary>
/// The process ID of the Visual Studio instance
/// </summary>
public int ProcessId { get; set; }
/// <summary>
/// The solution file path (if any solution is open)
/// </summary>
public string? SolutionPath { get; set; }
/// <summary>
/// The solution name (if any solution is open)
/// </summary>
public string? SolutionName { get; set; }
/// <summary>
/// When this instance was registered
/// </summary>
public DateTime RegisteredAt { get; set; }
/// <summary>
/// Last heartbeat time
/// </summary>
public DateTime LastHeartbeat { get; set; }
/// <summary>
/// List of project names in the solution
/// </summary>
public List<string> Projects { get; set; } = new();
}

View File

@@ -0,0 +1,129 @@
using Microsoft.OpenApi.Models;
using RoslynBridge.WebApi.Middleware;
using RoslynBridge.WebApi.Services;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
// Add Windows Service support
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "RoslynBridge Web API";
});
// Configure CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Add controllers
builder.Services.AddControllers();
// Register history service as singleton for in-memory storage
builder.Services.AddSingleton<IHistoryService, HistoryService>();
// Register instance registry service as singleton
builder.Services.AddSingleton<IInstanceRegistryService, InstanceRegistryService>();
// Register background service for cleaning up stale instances
builder.Services.AddHostedService<InstanceCleanupService>();
// Configure HttpClient for Roslyn Bridge
var roslynBridgeUrl = builder.Configuration.GetValue<string>("RoslynBridge:BaseUrl") ?? "http://localhost:59123";
builder.Services.AddHttpClient<IRoslynBridgeClient, RoslynBridgeClient>(client =>
{
client.BaseAddress = new Uri(roslynBridgeUrl);
client.Timeout = TimeSpan.FromSeconds(30);
});
// Configure Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Roslyn Bridge Web API",
Version = "v1.0",
Description = "Modern web API middleware for Roslyn Bridge - connecting Claude AI to Visual Studio code analysis",
Contact = new OpenApiContact
{
Name = "Roslyn Bridge",
Url = new Uri("https://github.com/yourusername/roslynbridge")
}
});
// Include XML comments for better documentation
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename);
if (File.Exists(xmlPath))
{
options.IncludeXmlComments(xmlPath);
}
// Add operation tags
options.TagActionsBy(api =>
{
if (api.GroupName != null)
{
return new[] { api.GroupName };
}
if (api.ActionDescriptor is Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor controllerActionDescriptor)
{
return new[] { controllerActionDescriptor.ControllerName };
}
return new[] { "Unknown" };
});
});
// Add health checks
builder.Services.AddHealthChecks();
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Enable Swagger in all environments for easy access
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Roslyn Bridge API v1");
options.RoutePrefix = string.Empty; // Serve Swagger UI at root
options.DocumentTitle = "Roslyn Bridge API";
options.DisplayRequestDuration();
});
app.UseHttpsRedirection();
// Enable CORS
app.UseCors("AllowAll");
// Enable history tracking middleware (must be before authorization and controllers)
app.UseHistoryTracking();
app.UseAuthorization();
// Map controllers
app.MapControllers();
// Map health check endpoint
app.MapHealthChecks("/health");
// Log startup information
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Roslyn Bridge Web API started");
logger.LogInformation("Swagger UI available at: {Url}", app.Environment.IsDevelopment() ? "https://localhost:7001" : "/");
logger.LogInformation("Connected to Roslyn Bridge at: {Url}", roslynBridgeUrl);
app.Run();

View File

@@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43446",
"sslPort": 44347
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5113",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7070;http://localhost:5113",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,378 @@
# Roslyn Bridge Web API
A modern ASP.NET Core web API that acts as middleware between Claude AI and the Roslyn Bridge Visual Studio plugin, providing RESTful access to C# code analysis capabilities.
## Overview
This web API serves as a bridge between external clients (like Claude AI) and the Visual Studio Roslyn Bridge plugin. It provides:
- **Modern RESTful API** with comprehensive Swagger/OpenAPI documentation
- **CORS-enabled** for web application access
- **Health monitoring** for both the web API and Visual Studio plugin connection
- **Simplified endpoints** for common Roslyn operations
- **Type-safe models** with validation
## Architecture
```
┌─────────────┐ HTTP/REST ┌──────────────────┐ HTTP ┌─────────────────────┐
│ Claude │ ◄─────────────────► │ Web API (5000) │ ◄────────────► │ VS Plugin (59123) │
│ AI │ │ Middleware │ │ Roslyn Bridge │
└─────────────┘ └──────────────────┘ └─────────────────────┘
```
## Getting Started
### Prerequisites
- .NET 8.0 SDK or later
- Visual Studio with Roslyn Bridge plugin running (default port: 59123)
### Quick Installation
**Option 1: Automated Installation**
Open PowerShell as Administrator:
```powershell
cd RoslynBridge.WebApi
.\install.ps1 -InstallService -StartService
```
This will build, publish, install as a Windows Service, and start the API automatically.
For more detailed service setup options, see [SERVICE_SETUP.md](SERVICE_SETUP.md).
**Option 2: Development Mode**
1. **Start the Visual Studio plugin** (it should be running on port 59123)
2. **Run the Web API:**
```bash
cd RoslynBridge.WebApi
dotnet run
```
3. **Access Swagger UI:**
- Navigate to: `http://localhost:5000`
- Or: `https://localhost:7001` (with HTTPS)
### Configuration
Edit `appsettings.json` to configure the connection:
```json
{
"RoslynBridge": {
"BaseUrl": "http://localhost:59123",
"TimeoutSeconds": 30
}
}
```
## API Endpoints
### Health Endpoints
- **GET /api/health** - Check health status of Web API and VS plugin
- **GET /api/health/ping** - Simple ping endpoint
### Roslyn Query Endpoints
- **POST /api/roslyn/query** - Execute any Roslyn query
- **GET /api/roslyn/projects** - Get all projects in solution
- **GET /api/roslyn/solution/overview** - Get solution statistics
- **GET /api/roslyn/diagnostics** - Get errors and warnings
- **GET /api/roslyn/symbol** - Get symbol information at position
- **GET /api/roslyn/references** - Find all references to symbol
- **GET /api/roslyn/symbol/search** - Search for symbols by name
### Refactoring Endpoints
- **POST /api/roslyn/format** - Format a document
- **POST /api/roslyn/project/package/add** - Add NuGet package
- **POST /api/roslyn/project/build** - Build a project
## Example Usage
### Using curl
```bash
# Health check
curl http://localhost:5000/api/health
# Get all projects
curl http://localhost:5000/api/roslyn/projects
# Get solution overview
curl http://localhost:5000/api/roslyn/solution/overview
# Execute custom query
curl -X POST http://localhost:5000/api/roslyn/query \
-H "Content-Type: application/json" \
-d '{
"queryType": "getsymbol",
"filePath": "C:\\path\\to\\file.cs",
"line": 10,
"column": 5
}'
# Search for symbols
curl "http://localhost:5000/api/roslyn/symbol/search?symbolName=MyClass"
# Get diagnostics
curl "http://localhost:5000/api/roslyn/diagnostics"
```
### Using JavaScript/TypeScript
```typescript
const response = await fetch('http://localhost:5000/api/roslyn/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
queryType: 'getprojects'
})
});
const result = await response.json();
console.log(result.data);
```
### Using C#
```csharp
using var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:5000");
var request = new RoslynQueryRequest
{
QueryType = "getsolutionoverview"
};
var response = await client.PostAsJsonAsync("/api/roslyn/query", request);
var result = await response.Content.ReadFromJsonAsync<RoslynQueryResponse>();
```
## Query Types
The following query types are supported:
### Code Analysis
- `getprojects` - Get all projects
- `getdocument` - Get document information
- `getsymbol` - Get symbol at position
- `getsemanticmodel` - Get semantic model
- `getsyntaxtree` - Get syntax tree
- `getdiagnostics` - Get compilation errors/warnings
- `findreferences` - Find all references
- `findsymbol` - Find symbols by name
- `gettypemembers` - Get type members
- `gettypehierarchy` - Get type hierarchy
- `findimplementations` - Find implementations
- `getnamespacetypes` - Get namespace types
- `getcallhierarchy` - Get call hierarchy
- `getsolutionoverview` - Get solution overview
- `getsymbolcontext` - Get symbol context
- `searchcode` - Search code patterns
### Refactoring
- `formatdocument` - Format document
- `organizeusings` - Organize using statements
- `renamesymbol` - Rename symbol
- `addmissingusing` - Add missing using
- `applycodefix` - Apply code fix
### Project Operations
- `addnugetpackage` - Add NuGet package
- `removenugetpackage` - Remove NuGet package
- `buildproject` - Build project
- `cleanproject` - Clean project
- `restorepackages` - Restore packages
- `createdirectory` - Create directory
## Response Format
All responses follow this structure:
```json
{
"success": true,
"message": "Optional message",
"data": { ... },
"error": null
}
```
Error responses:
```json
{
"success": false,
"message": null,
"data": null,
"error": "Error description"
}
```
## Features
### CORS Support
The API is configured with CORS enabled for all origins, making it accessible from web applications.
### Swagger/OpenAPI
Comprehensive API documentation is available at the root URL (`/`) with:
- Detailed endpoint descriptions
- Request/response models
- Try-it-out functionality
- XML documentation comments
### Health Checks
Built-in health checks monitor:
- Web API service status
- Visual Studio plugin connectivity
- Request/response timing
### Logging
Structured logging with different levels:
- Information: General operations
- Warning: Non-critical issues
- Error: Failed operations
- Debug: Detailed tracing (Development only)
## Development
### Project Structure
```
RoslynBridge.WebApi/
├── Controllers/
│ ├── HealthController.cs # Health check endpoints
│ └── RoslynController.cs # Roslyn operation endpoints
├── Models/
│ ├── RoslynQueryRequest.cs # Request DTOs
│ ├── RoslynQueryResponse.cs # Response DTOs
│ └── HealthCheckResponse.cs # Health check models
├── Services/
│ ├── IRoslynBridgeClient.cs # Client interface
│ └── RoslynBridgeClient.cs # HTTP client implementation
├── Program.cs # Application configuration
├── appsettings.json # Configuration
└── README.md # This file
```
### Building
```bash
dotnet build
```
### Running Tests
```bash
dotnet test
```
### Publishing
```bash
dotnet publish -c Release -o ./publish
```
## Deployment
### Docker (Optional)
Create a `Dockerfile`:
```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 5000
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["RoslynBridge.WebApi.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "RoslynBridge.WebApi.dll"]
```
Build and run:
```bash
docker build -t roslyn-bridge-api .
docker run -p 5000:5000 roslyn-bridge-api
```
## Integration with Claude
This API is designed to work seamlessly with Claude AI for code analysis tasks:
1. Claude can query project structure
2. Analyze code for errors and warnings
3. Search for symbols and references
4. Suggest refactorings
5. Navigate code hierarchies
Example Claude prompt:
```
"Using the Roslyn Bridge API at http://localhost:5000,
analyze the current solution and identify any code quality issues."
```
## Troubleshooting
### Visual Studio Plugin Not Connected
Error: `{"vsPluginStatus": "Disconnected"}`
**Solution:**
1. Ensure Visual Studio is running
2. Verify Roslyn Bridge plugin is installed and active
3. Check plugin is listening on port 59123
4. Update `appsettings.json` if using a different port
### CORS Errors
If accessing from a web app:
- Verify CORS is enabled in `Program.cs`
- Check browser console for specific CORS errors
- Ensure the origin is allowed
### Timeout Errors
Increase timeout in `appsettings.json`:
```json
{
"RoslynBridge": {
"TimeoutSeconds": 60
}
}
```
## License
This project is part of the Roslyn Bridge suite.
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
## Support
For issues and questions:
- Check the Swagger documentation at `/`
- Review the logs in the console output
- Verify the Visual Studio plugin is running

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,351 @@
# Roslyn Bridge Web API - Windows Service Setup
This guide explains how to install and run the Roslyn Bridge Web API as a Windows Service for always-on operation.
## Prerequisites
- **Administrator privileges** required for service installation
- **.NET 8.0 Runtime** or SDK installed
- **Visual Studio** with Roslyn Bridge extension must be running (the VS plugin on port 59123 is required)
## Quick Start
### Option 1: Automated Installation (Recommended)
Open PowerShell **as Administrator** and run:
```powershell
# Navigate to the project directory
cd C:\Path\To\RoslynBridge.WebApi
# Complete installation (build, publish, install service, and start)
.\install.ps1 -InstallService -StartService
```
This single command will:
1. Check prerequisites (.NET SDK)
2. Restore NuGet packages
3. Build the project
4. Publish to `./publish`
5. Install as Windows Service
6. Start the service
### Option 2: Manual Step-by-Step Installation
#### 1. Publish the Application
```powershell
# Navigate to the project directory
cd C:\Path\To\RoslynBridge.WebApi
# Publish the application for Release
dotnet publish -c Release -o publish
```
This creates a self-contained deployment in the `publish` folder.
#### 2. Install as Windows Service
Open PowerShell **as Administrator**:
```powershell
# Install the service
.\install-service.ps1 -Action Install
# Start the service
.\install-service.ps1 -Action Start
# Check service status
.\install-service.ps1 -Action Status
```
### 3. Verify Installation
1. Open Windows Services (`services.msc`)
2. Look for "Roslyn Bridge Web API"
3. Verify it's running
4. Test the API: http://localhost:5000/api/health
## Installation Script Options
The `install.ps1` script provides several options for different scenarios:
```powershell
# Full automated installation
.\install.ps1 -InstallService -StartService
# Build and publish only (no service installation)
.\install.ps1
# Skip build, just publish and install
.\install.ps1 -SkipBuild -InstallService
# Debug build instead of Release
.\install.ps1 -Configuration Debug
# Custom publish path
.\install.ps1 -PublishPath "C:\MyApp\Publish" -InstallService
# Reinstall after code changes
.\install.ps1 -InstallService
```
## Service Management Commands
```powershell
# Install service
.\install-service.ps1 -Action Install
# Start service
.\install-service.ps1 -Action Start
# Stop service
.\install-service.ps1 -Action Stop
# Restart service
.\install-service.ps1 -Action Restart
# Check status
.\install-service.ps1 -Action Status
# Uninstall service
.\install-service.ps1 -Action Uninstall
```
## Configuration
### Service Settings
The service is configured with:
- **Service Name**: `RoslynBridgeWebApi`
- **Display Name**: Roslyn Bridge Web API
- **Startup Type**: Automatic (starts with Windows)
- **Port**: 5000 (HTTP)
### Changing the Port
Edit `appsettings.json` before publishing:
```json
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:YOUR_PORT"
}
}
}
}
```
### Logging
When running as a service, logs are written to:
- **Windows Event Log**: Application → "Roslyn Bridge Web API"
- **Console**: Not available when running as service
To view logs:
1. Open Event Viewer
2. Navigate to: Windows Logs → Application
3. Filter by source: "Roslyn Bridge Web API"
## Troubleshooting
### Service Won't Start
**Symptom**: Service starts then immediately stops
**Possible Causes**:
1. Port 5000 is already in use
2. Visual Studio plugin (port 59123) is not running
3. Missing .NET runtime
**Solutions**:
```powershell
# Check if port 5000 is in use
netstat -ano | findstr :5000
# Check if VS plugin is accessible
curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"
# Check Event Log for errors
Get-EventLog -LogName Application -Source "Roslyn Bridge Web API" -Newest 10
```
### Service Installed but API Not Responding
**Check the following**:
1. Service is running: `.\install-service.ps1 -Action Status`
2. Visual Studio is open with a solution loaded
3. Firewall isn't blocking port 5000
### Updating the Service
When you update the code:
**Option 1: Using install.ps1 (Recommended)**
```powershell
# Stop service, rebuild, republish, and restart
.\install-service.ps1 -Action Stop
.\install.ps1
.\install-service.ps1 -Action Start
```
**Option 2: Manual Method**
```powershell
# 1. Stop the service
.\install-service.ps1 -Action Stop
# 2. Republish
dotnet publish -c Release -o publish
# 3. Restart the service
.\install-service.ps1 -Action Start
```
**Option 3: Full Reinstall**
```powershell
# Uninstall, republish, and reinstall
.\install-service.ps1 -Action Uninstall
.\install.ps1 -InstallService -StartService
```
## Alternative: Manual Service Installation
If you prefer to use `sc.exe` directly:
```cmd
# Install
sc create RoslynBridgeWebApi binPath="C:\Path\To\publish\RoslynBridge.WebApi.exe" start=auto
# Start
sc start RoslynBridgeWebApi
# Stop
sc stop RoslynBridgeWebApi
# Delete
sc delete RoslynBridgeWebApi
```
## Running in Development
For development, you don't need to install as a service. Just run:
```powershell
dotnet run --urls "http://localhost:5000"
```
The application will work the same way but won't persist after closing the terminal.
## Security Considerations
### Production Deployment
For production environments:
1. **Use HTTPS**: Configure SSL certificates in `appsettings.json`
2. **Restrict CORS**: Update the CORS policy to allow only specific origins
3. **Add Authentication**: Consider adding API key or OAuth authentication
4. **Firewall**: Only allow access from trusted IPs
### Network Access
By default, the service only listens on `localhost:5000`. To allow external access:
```json
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://0.0.0.0:5000"
}
}
}
}
```
**Warning**: Only do this in trusted networks. Add authentication first!
## Service Lifecycle
The service:
1. **Starts automatically** when Windows boots
2. **Requires Visual Studio** to be running (with a solution loaded) for full functionality
3. **Tracks request history** in memory (lost on restart)
4. **Logs to Event Log** for monitoring
## Monitoring
### Check Service Status
```powershell
# PowerShell
Get-Service RoslynBridgeWebApi
# Or use the script
.\install-service.ps1 -Action Status
```
### View Logs
```powershell
# View recent logs
Get-EventLog -LogName Application -Source "Roslyn Bridge Web API" -Newest 20
# Monitor logs in real-time
Get-EventLog -LogName Application -Source "Roslyn Bridge Web API" -Newest 1 -AsString
# Press Ctrl+C to stop
```
### Test API Health
```powershell
# Quick ping
curl http://localhost:5000/api/health/ping
# Full health check (includes VS plugin status)
curl http://localhost:5000/api/health
# View Swagger documentation
Start-Process "http://localhost:5000"
```
## Uninstalling
To completely remove the service:
```powershell
# Stop and uninstall
.\install-service.ps1 -Action Uninstall
# Optional: Delete the publish folder
Remove-Item -Path ".\publish" -Recurse -Force
```
## Additional Resources
- **Swagger UI**: http://localhost:5000 (when service is running)
- **Health Check**: http://localhost:5000/api/health
- **History Stats**: http://localhost:5000/api/history/stats
- **Event Viewer**: Windows Logs → Application → "Roslyn Bridge Web API"
## FAQ
**Q: Do I need to keep Visual Studio open?**
A: Yes, the VS plugin (port 59123) must be running. The Web API is just a middleware layer.
**Q: Can I run multiple instances?**
A: Yes, but change the port in `appsettings.json` for each instance.
**Q: What happens if the service can't connect to VS plugin?**
A: API calls will return errors, but the service remains running. History and health endpoints still work.
**Q: Can I use this without the Web API?**
A: Yes, you can call the VS plugin directly on port 59123 using POST requests (see roslyn-bridge skill).
**Q: How do I backup request history?**
A: History is in-memory only. Consider implementing database persistence for production use.

View File

@@ -0,0 +1,59 @@
using System.Collections.Concurrent;
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// In-memory implementation of history service
/// </summary>
public class HistoryService : IHistoryService
{
private readonly ConcurrentQueue<QueryHistoryEntry> _entries = new();
private readonly ILogger<HistoryService> _logger;
private readonly int _maxEntries;
public HistoryService(ILogger<HistoryService> logger, IConfiguration configuration)
{
_logger = logger;
_maxEntries = configuration.GetValue<int>("History:MaxEntries", 1000);
}
public void Add(QueryHistoryEntry entry)
{
_entries.Enqueue(entry);
// Trim old entries if we exceed max
while (_entries.Count > _maxEntries)
{
_entries.TryDequeue(out _);
}
_logger.LogDebug("Added history entry: {Id} - {Path}", entry.Id, entry.Path);
}
public IEnumerable<QueryHistoryEntry> GetAll()
{
return _entries.Reverse();
}
public QueryHistoryEntry? GetById(string id)
{
return _entries.FirstOrDefault(e => e.Id == id);
}
public IEnumerable<QueryHistoryEntry> GetRecent(int count = 50)
{
return _entries.Reverse().Take(count);
}
public void Clear()
{
_entries.Clear();
_logger.LogInformation("History cleared");
}
public int GetCount()
{
return _entries.Count;
}
}

View File

@@ -0,0 +1,46 @@
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// Service for managing query history
/// </summary>
public interface IHistoryService
{
/// <summary>
/// Add a new history entry
/// </summary>
/// <param name="entry">The history entry to add</param>
void Add(QueryHistoryEntry entry);
/// <summary>
/// Get all history entries
/// </summary>
/// <returns>List of all history entries</returns>
IEnumerable<QueryHistoryEntry> GetAll();
/// <summary>
/// Get a specific history entry by ID
/// </summary>
/// <param name="id">The entry ID</param>
/// <returns>The history entry, or null if not found</returns>
QueryHistoryEntry? GetById(string id);
/// <summary>
/// Get recent history entries
/// </summary>
/// <param name="count">Number of entries to return</param>
/// <returns>List of recent history entries</returns>
IEnumerable<QueryHistoryEntry> GetRecent(int count = 50);
/// <summary>
/// Clear all history entries
/// </summary>
void Clear();
/// <summary>
/// Get total count of history entries
/// </summary>
/// <returns>Total number of entries</returns>
int GetCount();
}

View File

@@ -0,0 +1,49 @@
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// Service for managing registered Visual Studio instances
/// </summary>
public interface IInstanceRegistryService
{
/// <summary>
/// Register a new Visual Studio instance
/// </summary>
void Register(VSInstanceInfo instance);
/// <summary>
/// Unregister a Visual Studio instance by process ID
/// </summary>
bool Unregister(int processId);
/// <summary>
/// Update heartbeat for an instance
/// </summary>
bool UpdateHeartbeat(int processId);
/// <summary>
/// Get all registered instances
/// </summary>
IEnumerable<VSInstanceInfo> GetAllInstances();
/// <summary>
/// Get instance by process ID
/// </summary>
VSInstanceInfo? GetByProcessId(int processId);
/// <summary>
/// Get instance by solution path
/// </summary>
VSInstanceInfo? GetBySolutionPath(string solutionPath);
/// <summary>
/// Get instance by port
/// </summary>
VSInstanceInfo? GetByPort(int port);
/// <summary>
/// Remove stale instances (no heartbeat for specified timeout)
/// </summary>
void RemoveStaleInstances(TimeSpan timeout);
}

View File

@@ -0,0 +1,26 @@
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// Interface for communicating with the Roslyn Bridge Visual Studio plugin
/// </summary>
public interface IRoslynBridgeClient
{
/// <summary>
/// Execute a query against the Roslyn Bridge server
/// </summary>
/// <param name="request">The query request</param>
/// <param name="instancePort">Optional port of specific VS instance to target</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The query response</returns>
Task<RoslynQueryResponse> ExecuteQueryAsync(RoslynQueryRequest request, int? instancePort = null, CancellationToken cancellationToken = default);
/// <summary>
/// Check if the Roslyn Bridge server is healthy
/// </summary>
/// <param name="instancePort">Optional port of specific VS instance to check</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if healthy, false otherwise</returns>
Task<bool> IsHealthyAsync(int? instancePort = null, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,50 @@
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// Background service that periodically removes stale VS instances
/// </summary>
public class InstanceCleanupService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<InstanceCleanupService> _logger;
private readonly TimeSpan _cleanupInterval = TimeSpan.FromMinutes(1);
private readonly TimeSpan _staleTimeout = TimeSpan.FromMinutes(5);
public InstanceCleanupService(
IServiceProvider serviceProvider,
ILogger<InstanceCleanupService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Instance cleanup service started");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(_cleanupInterval, stoppingToken);
// Get the registry service from scope
using var scope = _serviceProvider.CreateScope();
var registryService = scope.ServiceProvider.GetRequiredService<IInstanceRegistryService>();
registryService.RemoveStaleInstances(_staleTimeout);
}
catch (OperationCanceledException)
{
// Expected when stopping
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during instance cleanup");
}
}
_logger.LogInformation("Instance cleanup service stopped");
}
}

View File

@@ -0,0 +1,115 @@
using System.Collections.Concurrent;
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// Thread-safe in-memory registry for Visual Studio instances
/// </summary>
public class InstanceRegistryService : IInstanceRegistryService
{
private readonly ConcurrentDictionary<int, VSInstanceInfo> _instances = new();
private readonly ILogger<InstanceRegistryService> _logger;
public InstanceRegistryService(ILogger<InstanceRegistryService> logger)
{
_logger = logger;
}
public void Register(VSInstanceInfo instance)
{
instance.RegisteredAt = DateTime.UtcNow;
instance.LastHeartbeat = DateTime.UtcNow;
_instances.AddOrUpdate(instance.ProcessId, instance, (_, existing) =>
{
// Update existing instance
existing.Port = instance.Port;
existing.SolutionPath = instance.SolutionPath;
existing.SolutionName = instance.SolutionName;
existing.Projects = instance.Projects;
existing.LastHeartbeat = DateTime.UtcNow;
return existing;
});
_logger.LogInformation(
"Registered VS instance: PID={ProcessId}, Port={Port}, Solution={Solution}",
instance.ProcessId,
instance.Port,
instance.SolutionName ?? "None");
}
public bool Unregister(int processId)
{
var removed = _instances.TryRemove(processId, out var instance);
if (removed)
{
_logger.LogInformation(
"Unregistered VS instance: PID={ProcessId}, Solution={Solution}",
processId,
instance?.SolutionName ?? "None");
}
return removed;
}
public bool UpdateHeartbeat(int processId)
{
if (_instances.TryGetValue(processId, out var instance))
{
instance.LastHeartbeat = DateTime.UtcNow;
_logger.LogDebug("Updated heartbeat for VS instance: PID={ProcessId}", processId);
return true;
}
return false;
}
public IEnumerable<VSInstanceInfo> GetAllInstances()
{
return _instances.Values.ToList();
}
public VSInstanceInfo? GetByProcessId(int processId)
{
_instances.TryGetValue(processId, out var instance);
return instance;
}
public VSInstanceInfo? GetBySolutionPath(string solutionPath)
{
if (string.IsNullOrEmpty(solutionPath))
return null;
var normalizedPath = Path.GetFullPath(solutionPath).ToLowerInvariant();
return _instances.Values.FirstOrDefault(i =>
!string.IsNullOrEmpty(i.SolutionPath) &&
Path.GetFullPath(i.SolutionPath).ToLowerInvariant() == normalizedPath);
}
public VSInstanceInfo? GetByPort(int port)
{
return _instances.Values.FirstOrDefault(i => i.Port == port);
}
public void RemoveStaleInstances(TimeSpan timeout)
{
var cutoff = DateTime.UtcNow - timeout;
var staleInstances = _instances.Values
.Where(i => i.LastHeartbeat < cutoff)
.ToList();
foreach (var instance in staleInstances)
{
if (_instances.TryRemove(instance.ProcessId, out _))
{
_logger.LogWarning(
"Removed stale VS instance: PID={ProcessId}, LastHeartbeat={LastHeartbeat}",
instance.ProcessId,
instance.LastHeartbeat);
}
}
}
}

View File

@@ -0,0 +1,174 @@
using System.Text;
using System.Text.Json;
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
/// <summary>
/// HTTP client for communicating with the Roslyn Bridge Visual Studio plugin
/// </summary>
public class RoslynBridgeClient : IRoslynBridgeClient
{
private readonly HttpClient _httpClient;
private readonly IInstanceRegistryService _registryService;
private readonly ILogger<RoslynBridgeClient> _logger;
private readonly JsonSerializerOptions _jsonOptions;
public RoslynBridgeClient(
HttpClient httpClient,
IInstanceRegistryService registryService,
ILogger<RoslynBridgeClient> logger)
{
_httpClient = httpClient;
_registryService = registryService;
_logger = logger;
_jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
public async Task<RoslynQueryResponse> ExecuteQueryAsync(
RoslynQueryRequest request,
int? instancePort = null,
CancellationToken cancellationToken = default)
{
try
{
var targetPort = await ResolveInstancePortAsync(instancePort, request);
if (targetPort == null)
{
return new RoslynQueryResponse
{
Success = false,
Error = "No Visual Studio instance available"
};
}
_logger.LogInformation("Executing query: {QueryType} on port {Port}", request.QueryType, targetPort);
var json = JsonSerializer.Serialize(request, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"http://localhost:{targetPort}/query";
var response = await _httpClient.PostAsync(url, content, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogError("Query failed with status {StatusCode}: {Error}", response.StatusCode, errorContent);
return new RoslynQueryResponse
{
Success = false,
Error = $"Request failed with status {response.StatusCode}: {errorContent}"
};
}
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
var result = JsonSerializer.Deserialize<RoslynQueryResponse>(responseContent, _jsonOptions);
if (result == null)
{
return new RoslynQueryResponse
{
Success = false,
Error = "Failed to deserialize response"
};
}
_logger.LogInformation("Query executed successfully: {Success}", result.Success);
return result;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP request failed while executing query");
return new RoslynQueryResponse
{
Success = false,
Error = $"Failed to connect to Roslyn Bridge server: {ex.Message}"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error while executing query");
return new RoslynQueryResponse
{
Success = false,
Error = $"Unexpected error: {ex.Message}"
};
}
}
public async Task<bool> IsHealthyAsync(int? instancePort = null, CancellationToken cancellationToken = default)
{
try
{
var targetPort = await ResolveInstancePortAsync(instancePort, null);
if (targetPort == null)
{
return false;
}
var request = new RoslynQueryRequest { QueryType = "health" };
var json = JsonSerializer.Serialize(request, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"http://localhost:{targetPort}/health";
var response = await _httpClient.PostAsync(url, content, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Health check failed");
return false;
}
}
/// <summary>
/// Resolves which VS instance port to use based on provided hints
/// </summary>
private Task<int?> ResolveInstancePortAsync(int? explicitPort, RoslynQueryRequest? request)
{
// If explicit port specified, use it
if (explicitPort.HasValue)
{
return Task.FromResult<int?>(explicitPort.Value);
}
// Try to find instance by solution path from request
if (request != null && !string.IsNullOrEmpty(request.FilePath))
{
// Extract solution path by looking for .sln file in the path hierarchy
var directory = Path.GetDirectoryName(request.FilePath);
while (!string.IsNullOrEmpty(directory))
{
var solutionFiles = Directory.GetFiles(directory, "*.sln");
if (solutionFiles.Length > 0)
{
var instance = _registryService.GetBySolutionPath(solutionFiles[0]);
if (instance != null)
{
_logger.LogDebug("Found instance by solution path: {SolutionPath}", solutionFiles[0]);
return Task.FromResult<int?>(instance.Port);
}
}
directory = Path.GetDirectoryName(directory);
}
}
// Fall back to first available instance
var instances = _registryService.GetAllInstances().ToList();
if (instances.Any())
{
_logger.LogDebug("Using first available instance: port {Port}", instances[0].Port);
return Task.FromResult<int?>(instances[0].Port);
}
_logger.LogWarning("No Visual Studio instances registered");
return Task.FromResult<int?>(null);
}
}

View File

@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"RoslynBridge.WebApi": "Debug"
}
},
"RoslynBridge": {
"BaseUrl": "http://localhost:59123"
}
}

View File

@@ -0,0 +1,33 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"RoslynBridge.WebApi": "Information"
},
"EventLog": {
"SourceName": "Roslyn Bridge Web API",
"LogName": "Application",
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
},
"AllowedHosts": "*",
"RoslynBridge": {
"BaseUrl": "http://localhost:59123",
"TimeoutSeconds": 30
},
"History": {
"MaxEntries": 1000,
"Enabled": true
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5001"
}
}
}
}

View File

@@ -0,0 +1,361 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Complete installation script for Roslyn Bridge Web API
.DESCRIPTION
This script performs a complete installation of the Roslyn Bridge Web API:
- Checks prerequisites (.NET SDK)
- Restores NuGet packages
- Builds the project
- Publishes the release build
- Optionally installs as Windows Service
- Tests the installation
.PARAMETER SkipBuild
Skip the build step (use existing build)
.PARAMETER SkipPublish
Skip the publish step (use existing publish)
.PARAMETER InstallService
Install as Windows Service after publishing
.PARAMETER StartService
Start the service after installation (requires -InstallService)
.PARAMETER Configuration
Build configuration (Debug or Release). Default: Release
.PARAMETER PublishPath
Path where the application will be published. Default: ./publish
.EXAMPLE
.\install.ps1
Full installation without service setup
.EXAMPLE
.\install.ps1 -InstallService -StartService
Full installation with automatic service setup and start
.EXAMPLE
.\install.ps1 -Configuration Debug
Install debug build instead of release
#>
param(
[Parameter(Mandatory=$false)]
[switch]$SkipBuild,
[Parameter(Mandatory=$false)]
[switch]$SkipPublish,
[Parameter(Mandatory=$false)]
[switch]$InstallService,
[Parameter(Mandatory=$false)]
[switch]$StartService,
[Parameter(Mandatory=$false)]
[ValidateSet("Debug", "Release")]
[string]$Configuration = "Release",
[Parameter(Mandatory=$false)]
[string]$PublishPath = ".\publish"
)
# Script configuration
$ErrorActionPreference = "Stop"
$ProjectName = "RoslynBridge.WebApi"
$ProjectFile = ".\RoslynBridge.WebApi.csproj"
# Color output helper
function Write-ColorOutput {
param(
[string]$Message,
[string]$Color = "White"
)
Write-Host $Message -ForegroundColor $Color
}
# Banner
function Show-Banner {
Write-ColorOutput "`n============================================================" "Cyan"
Write-ColorOutput " Roslyn Bridge Web API - Installation Script" "Cyan"
Write-ColorOutput "============================================================`n" "Cyan"
}
# Step counter
$script:stepNumber = 0
function Write-Step {
param([string]$Message)
$script:stepNumber++
Write-ColorOutput "`n[$script:stepNumber] $Message" "Yellow"
Write-ColorOutput ("-" * 60) "DarkGray"
}
# Check prerequisites
function Test-Prerequisites {
Write-Step "Checking Prerequisites"
# Check for .NET SDK
Write-Host "Checking for .NET SDK... " -NoNewline
try {
$dotnetVersion = dotnet --version 2>$null
if ($LASTEXITCODE -eq 0) {
Write-ColorOutput "Found v$dotnetVersion" "Green"
} else {
throw "dotnet command failed"
}
}
catch {
Write-ColorOutput "NOT FOUND" "Red"
Write-ColorOutput "`nError: .NET SDK is not installed or not in PATH." "Red"
Write-ColorOutput "Please download and install from: https://dot.net" "Yellow"
exit 1
}
# Check for project file
Write-Host "Checking for project file... " -NoNewline
if (Test-Path $ProjectFile) {
Write-ColorOutput "Found" "Green"
} else {
Write-ColorOutput "NOT FOUND" "Red"
Write-ColorOutput "`nError: Project file not found: $ProjectFile" "Red"
Write-ColorOutput "Please run this script from the RoslynBridge.WebApi directory." "Yellow"
exit 1
}
Write-ColorOutput "`nAll prerequisites satisfied!" "Green"
}
# Restore NuGet packages
function Restore-Packages {
Write-Step "Restoring NuGet Packages"
try {
dotnet restore $ProjectFile
if ($LASTEXITCODE -ne 0) {
throw "dotnet restore failed with exit code $LASTEXITCODE"
}
Write-ColorOutput "`nPackages restored successfully!" "Green"
}
catch {
Write-ColorOutput "`nError during package restore: $_" "Red"
exit 1
}
}
# Build project
function Build-Project {
Write-Step "Building Project ($Configuration)"
try {
dotnet build $ProjectFile -c $Configuration --no-restore
if ($LASTEXITCODE -ne 0) {
throw "dotnet build failed with exit code $LASTEXITCODE"
}
Write-ColorOutput "`nBuild completed successfully!" "Green"
}
catch {
Write-ColorOutput "`nError during build: $_" "Red"
exit 1
}
}
# Publish project
function Publish-Project {
Write-Step "Publishing Project"
Write-ColorOutput "Configuration: $Configuration" "Gray"
Write-ColorOutput "Output Path: $PublishPath" "Gray"
try {
# Clean publish directory if it exists
if (Test-Path $PublishPath) {
Write-Host "`nCleaning existing publish directory... " -NoNewline
Remove-Item -Path $PublishPath -Recurse -Force
Write-ColorOutput "Done" "Green"
}
# Publish
dotnet publish $ProjectFile -c $Configuration -o $PublishPath --no-build --no-restore
if ($LASTEXITCODE -ne 0) {
throw "dotnet publish failed with exit code $LASTEXITCODE"
}
# Verify executable exists
$exePath = Join-Path $PublishPath "$ProjectName.exe"
if (Test-Path $exePath) {
Write-ColorOutput "`nProject published successfully!" "Green"
Write-ColorOutput "Executable: $exePath" "Gray"
# Show publish directory size
$publishSize = (Get-ChildItem $PublishPath -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB
Write-ColorOutput ("Publish size: {0:N2} MB" -f $publishSize) "Gray"
} else {
throw "Executable not found after publish: $exePath"
}
}
catch {
Write-ColorOutput "`nError during publish: $_" "Red"
exit 1
}
}
# Install Windows Service
function Install-WindowsService {
Write-Step "Installing Windows Service"
if (-not (Test-Path ".\install-service.ps1")) {
Write-ColorOutput "Error: install-service.ps1 not found" "Red"
return $false
}
try {
& ".\install-service.ps1" -Action Install -PublishPath $PublishPath
return $true
}
catch {
Write-ColorOutput "Error installing service: $_" "Red"
return $false
}
}
# Start Windows Service
function Start-WindowsService {
Write-Step "Starting Windows Service"
if (-not (Test-Path ".\install-service.ps1")) {
Write-ColorOutput "Error: install-service.ps1 not found" "Red"
return $false
}
try {
& ".\install-service.ps1" -Action Start
return $true
}
catch {
Write-ColorOutput "Error starting service: $_" "Red"
return $false
}
}
# Test installation
function Test-Installation {
Write-Step "Installation Summary"
$publishFullPath = Resolve-Path $PublishPath -ErrorAction SilentlyContinue
if ($publishFullPath) {
Write-ColorOutput "`nPublished to: $publishFullPath" "Green"
$exePath = Join-Path $publishFullPath "$ProjectName.exe"
if (Test-Path $exePath) {
Write-ColorOutput "Executable: $exePath" "Green"
}
}
if ($InstallService) {
$service = Get-Service -Name "RoslynBridgeWebApi" -ErrorAction SilentlyContinue
if ($service) {
Write-ColorOutput "`nWindows Service Status:" "Cyan"
Write-ColorOutput " Name: $($service.Name)" "Gray"
Write-ColorOutput " Status: $($service.Status)" $(if ($service.Status -eq "Running") { "Green" } else { "Yellow" })
Write-ColorOutput " Type: $($service.StartType)" "Gray"
}
}
}
# Show next steps
function Show-NextSteps {
Write-Step "Next Steps"
if ($InstallService) {
if ($StartService) {
Write-ColorOutput "`nThe service is now running!" "Green"
Write-ColorOutput "`nAPI should be available at:" "Cyan"
Write-ColorOutput " http://localhost:5000" "White"
Write-ColorOutput " https://localhost:7001 (HTTPS)" "White"
Write-ColorOutput "`nSwagger UI:" "Cyan"
Write-ColorOutput " http://localhost:5000" "White"
Write-ColorOutput "`nTo manage the service:" "Yellow"
Write-ColorOutput " .\install-service.ps1 -Action Status" "White"
Write-ColorOutput " .\install-service.ps1 -Action Stop" "White"
Write-ColorOutput " .\install-service.ps1 -Action Restart" "White"
Write-ColorOutput " .\install-service.ps1 -Action Uninstall" "White"
} else {
Write-ColorOutput "`nService installed but not started." "Yellow"
Write-ColorOutput "`nTo start the service:" "Cyan"
Write-ColorOutput " .\install-service.ps1 -Action Start" "White"
}
} else {
Write-ColorOutput "`nTo run the application manually:" "Cyan"
Write-ColorOutput " cd $PublishPath" "White"
Write-ColorOutput " .\$ProjectName.exe" "White"
Write-ColorOutput "`nTo install as a Windows Service:" "Cyan"
Write-ColorOutput " .\install-service.ps1 -Action Install -PublishPath $PublishPath" "White"
Write-ColorOutput " .\install-service.ps1 -Action Start" "White"
Write-ColorOutput "`nOr run this script again with -InstallService -StartService flags" "Gray"
}
Write-ColorOutput "`nFor more information, see README.md" "Gray"
}
# Main execution
try {
Show-Banner
# Show configuration
Write-ColorOutput "Installation Configuration:" "Cyan"
Write-ColorOutput " Configuration: $Configuration" "Gray"
Write-ColorOutput " Publish Path: $PublishPath" "Gray"
Write-ColorOutput " Install Service: $InstallService" "Gray"
Write-ColorOutput " Start Service: $StartService" "Gray"
Write-ColorOutput " Skip Build: $SkipBuild" "Gray"
Write-ColorOutput " Skip Publish: $SkipPublish" "Gray"
# Execute installation steps
Test-Prerequisites
if (-not $SkipBuild) {
Restore-Packages
Build-Project
} else {
Write-ColorOutput "`nSkipping build (using existing build)" "Yellow"
}
if (-not $SkipPublish) {
Publish-Project
} else {
Write-ColorOutput "`nSkipping publish (using existing publish)" "Yellow"
}
# Optional service installation
if ($InstallService) {
$serviceInstalled = Install-WindowsService
if ($serviceInstalled -and $StartService) {
Start-WindowsService
}
}
# Show results
Test-Installation
Show-NextSteps
Write-ColorOutput "`n============================================================" "Cyan"
Write-ColorOutput " Installation Completed Successfully!" "Cyan"
Write-ColorOutput "============================================================`n" "Cyan"
exit 0
}
catch {
Write-ColorOutput "`n============================================================" "Red"
Write-ColorOutput " Installation Failed!" "Red"
Write-ColorOutput "============================================================`n" "Red"
Write-ColorOutput "Error: $_" "Red"
Write-ColorOutput "`nStack Trace:" "DarkGray"
Write-ColorOutput $_.ScriptStackTrace "DarkGray"
exit 1
}

Binary file not shown.

View File

@@ -0,0 +1,834 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"RoslynBridge.WebApi/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Hosting.WindowsServices": "9.0.10",
"Microsoft.Extensions.Http": "8.0.0",
"Swashbuckle.AspNetCore": "6.6.2"
},
"runtime": {
"RoslynBridge.WebApi.dll": {}
}
},
"Microsoft.Extensions.ApiDescription.Server/6.0.5": {},
"Microsoft.Extensions.Configuration/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.Binder/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Binder.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.CommandLine/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.CommandLine.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.EnvironmentVariables/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.FileExtensions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.10",
"Microsoft.Extensions.FileProviders.Physical": "9.0.10",
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.Json/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.10",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.10",
"System.Text.Json": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Json.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Configuration.UserSecrets/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.Configuration.Json": "9.0.10",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.10",
"Microsoft.Extensions.FileProviders.Physical": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.UserSecrets.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.DependencyInjection/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.10": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Diagnostics/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.10",
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Diagnostics.Abstractions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10",
"System.Diagnostics.DiagnosticSource": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.FileProviders.Abstractions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.FileProviders.Physical/9.0.10": {
"dependencies": {
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.10",
"Microsoft.Extensions.FileSystemGlobbing": "9.0.10",
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileProviders.Physical.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.FileSystemGlobbing/9.0.10": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileSystemGlobbing.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Hosting/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.Configuration.Binder": "9.0.10",
"Microsoft.Extensions.Configuration.CommandLine": "9.0.10",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.10",
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.10",
"Microsoft.Extensions.Configuration.Json": "9.0.10",
"Microsoft.Extensions.Configuration.UserSecrets": "9.0.10",
"Microsoft.Extensions.DependencyInjection": "9.0.10",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Diagnostics": "9.0.10",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.10",
"Microsoft.Extensions.FileProviders.Physical": "9.0.10",
"Microsoft.Extensions.Hosting.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging.Configuration": "9.0.10",
"Microsoft.Extensions.Logging.Console": "9.0.10",
"Microsoft.Extensions.Logging.Debug": "9.0.10",
"Microsoft.Extensions.Logging.EventLog": "9.0.10",
"Microsoft.Extensions.Logging.EventSource": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Hosting.Abstractions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.10",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Hosting.WindowsServices/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Hosting": "9.0.10",
"Microsoft.Extensions.Logging.EventLog": "9.0.10",
"System.ServiceProcess.ServiceController": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.WindowsServices.dll": {
"assemblyVersion": "9.0.0.10",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Http/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Diagnostics": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10"
}
},
"Microsoft.Extensions.Logging/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"System.Diagnostics.DiagnosticSource": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Logging.Configuration/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.10",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.Configuration.Binder": "9.0.10",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10",
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Configuration.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Logging.Console/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging.Configuration": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10",
"System.Text.Json": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Console.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Logging.Debug/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Debug.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Logging.EventLog/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10",
"System.Diagnostics.EventLog": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.EventLog.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Logging.EventSource/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Logging": "9.0.10",
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10",
"Microsoft.Extensions.Primitives": "9.0.10",
"System.Text.Json": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.EventSource.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Options/9.0.10": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Options.ConfigurationExtensions/9.0.10": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.10",
"Microsoft.Extensions.Configuration.Binder": "9.0.10",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
"Microsoft.Extensions.Options": "9.0.10",
"Microsoft.Extensions.Primitives": "9.0.10"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.Extensions.Primitives/9.0.10": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"Microsoft.OpenApi/1.6.14": {
"runtime": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
"assemblyVersion": "1.6.14.0",
"fileVersion": "1.6.14.0"
}
}
},
"Swashbuckle.AspNetCore/6.6.2": {
"dependencies": {
"Microsoft.Extensions.ApiDescription.Server": "6.0.5",
"Swashbuckle.AspNetCore.Swagger": "6.6.2",
"Swashbuckle.AspNetCore.SwaggerGen": "6.6.2",
"Swashbuckle.AspNetCore.SwaggerUI": "6.6.2"
}
},
"Swashbuckle.AspNetCore.Swagger/6.6.2": {
"dependencies": {
"Microsoft.OpenApi": "1.6.14"
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll": {
"assemblyVersion": "6.6.2.0",
"fileVersion": "6.6.2.401"
}
}
},
"Swashbuckle.AspNetCore.SwaggerGen/6.6.2": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "6.6.2"
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
"assemblyVersion": "6.6.2.0",
"fileVersion": "6.6.2.401"
}
}
},
"Swashbuckle.AspNetCore.SwaggerUI/6.6.2": {
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
"assemblyVersion": "6.6.2.0",
"fileVersion": "6.6.2.401"
}
}
},
"System.Diagnostics.DiagnosticSource/9.0.10": {
"runtime": {
"lib/net8.0/System.Diagnostics.DiagnosticSource.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"System.Diagnostics.EventLog/9.0.10": {
"runtime": {
"lib/net8.0/System.Diagnostics.EventLog.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
},
"runtimeTargets": {
"runtimes/win/lib/net8.0/System.Diagnostics.EventLog.Messages.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "9.0.0.0",
"fileVersion": "0.0.0.0"
},
"runtimes/win/lib/net8.0/System.Diagnostics.EventLog.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"System.IO.Pipelines/9.0.10": {
"runtime": {
"lib/net8.0/System.IO.Pipelines.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"System.ServiceProcess.ServiceController/9.0.10": {
"dependencies": {
"System.Diagnostics.EventLog": "9.0.10"
},
"runtime": {
"lib/net8.0/System.ServiceProcess.ServiceController.dll": {
"assemblyVersion": "9.0.0.10",
"fileVersion": "9.0.1025.47515"
}
},
"runtimeTargets": {
"runtimes/win/lib/net8.0/System.ServiceProcess.ServiceController.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "9.0.0.10",
"fileVersion": "9.0.1025.47515"
}
}
},
"System.Text.Encodings.Web/9.0.10": {
"runtime": {
"lib/net8.0/System.Text.Encodings.Web.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
},
"runtimeTargets": {
"runtimes/browser/lib/net8.0/System.Text.Encodings.Web.dll": {
"rid": "browser",
"assetType": "runtime",
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
},
"System.Text.Json/9.0.10": {
"dependencies": {
"System.IO.Pipelines": "9.0.10",
"System.Text.Encodings.Web": "9.0.10"
},
"runtime": {
"lib/net8.0/System.Text.Json.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
}
}
}
}
},
"libraries": {
"RoslynBridge.WebApi/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.Extensions.ApiDescription.Server/6.0.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==",
"path": "microsoft.extensions.apidescription.server/6.0.5",
"hashPath": "microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512"
},
"Microsoft.Extensions.Configuration/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UAm3SLGAMlJdowbN+/xnh2UGJkdJoXVm4MsdhZ60dAMS8jteoyCx5WfIab5DKv0TCYpdhVecLJVUjEO3abs9UQ==",
"path": "microsoft.extensions.configuration/9.0.10",
"hashPath": "microsoft.extensions.configuration.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ad3JxmFj0uxuFa1CT6oxTCC1lQ0xeRuOvzBRFT/I/ofIXVOnNsH/v2GZkAJWhlpZqKUvSexQZzp3EEAB2CdtJg==",
"path": "microsoft.extensions.configuration.abstractions/9.0.10",
"hashPath": "microsoft.extensions.configuration.abstractions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Binder/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-D6Kng+9I+w1SQPxJybc6wzw9nnnyUQPutycjtI0svv1RHaWOpUk9PPlwIRfhhoQZ3yihejkEI2wNv/7VnVtkGA==",
"path": "microsoft.extensions.configuration.binder/9.0.10",
"hashPath": "microsoft.extensions.configuration.binder.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.CommandLine/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Sg400UyKl33kOpqklEg1MIM3lpY/aWi7QZTB2JfFpKgxnSRQl9J6tHiKYll+Rd603P+71YsDy/zqBYUE/3Xeag==",
"path": "microsoft.extensions.configuration.commandline/9.0.10",
"hashPath": "microsoft.extensions.configuration.commandline.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.EnvironmentVariables/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Nje8x5JDRi7uzf2q3NpXiBleRRJAxJMnHcJTi0tLyqd6eGIICRuF6qxgZssMS1r8xXDoaUr/2ZLQ6Cui1Io+Qw==",
"path": "microsoft.extensions.configuration.environmentvariables/9.0.10",
"hashPath": "microsoft.extensions.configuration.environmentvariables.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.FileExtensions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kYWY9VRoCKQJCLKAA4Wqn74FVnytqosF7vFq1chJ8st9mGZS6SQrkoZg7GmcpqrRRUWmWDOZI4nFdoFnxsI/Ug==",
"path": "microsoft.extensions.configuration.fileextensions/9.0.10",
"hashPath": "microsoft.extensions.configuration.fileextensions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Json/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bn+qnwuOaDelax8PUw30UTjLOuEd0lGWqUG4Z+oVr4D/gEWouCWOyvCVkyn+PWbftPlnmAmWxd4J+7ljwE8wVw==",
"path": "microsoft.extensions.configuration.json/9.0.10",
"hashPath": "microsoft.extensions.configuration.json.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.UserSecrets/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-l7em+qNfEdGlwRm8Qk2rkzqjT8xWb/EosoQeTvJ3kZYiRo5inMj0nNcZw51dUKwGO/LW7uNMdqNNU3P0pB5JqA==",
"path": "microsoft.extensions.configuration.usersecrets/9.0.10",
"hashPath": "microsoft.extensions.configuration.usersecrets.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iEtXCkNd5XhjNJAOb/wO4IhDRdLIE2CsPxZggZQWJ/q2+sa8dmEPC393nnsiqdH8/4KV8Xn25IzgKPR1UEQ0og==",
"path": "microsoft.extensions.dependencyinjection/9.0.10",
"hashPath": "microsoft.extensions.dependencyinjection.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-r9waLiOPe9ZF1PvzUT+RDoHvpMmY8MW+lb4lqjYGObwKpnyPMLI3odVvlmshwuZcdoHynsGWOrCPA0hxZ63lIA==",
"path": "microsoft.extensions.dependencyinjection.abstractions/9.0.10",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-01x2vz0AbIdfNUzEVYFq2HSeq1BmrSDpiG7nTmwjfd0d39sahQ8T7dhSXhH+YnZyaLWyMBudOq0vVa/voyNWjg==",
"path": "microsoft.extensions.diagnostics/9.0.10",
"hashPath": "microsoft.extensions.diagnostics.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics.Abstractions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iwVnYi+gNKrr5riw8YFCoLCN4s0dmHtzfUmV99RIhrz8R4d6C/bsKzXhIhZWDIxJOhVzB+idSOQeRGj1/oMF+Q==",
"path": "microsoft.extensions.diagnostics.abstractions/9.0.10",
"hashPath": "microsoft.extensions.diagnostics.abstractions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.FileProviders.Abstractions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3+cLxZKUWBbpfIXLLuKcEok9C91PsV1h5xxfUsEnLSXXLNMiPDfrhpb1xajNFcejFPs9Ck/Fi3z71hYDqFBwYg==",
"path": "microsoft.extensions.fileproviders.abstractions/9.0.10",
"hashPath": "microsoft.extensions.fileproviders.abstractions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.FileProviders.Physical/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Eg3YOEMpHWZzAgPD9YvGkQSv97AtG3II6maRQV/voDRORh4bRiyl0mVtT2PKnu1JoD9rJeYgjGCwRvVWMBaqgQ==",
"path": "microsoft.extensions.fileproviders.physical/9.0.10",
"hashPath": "microsoft.extensions.fileproviders.physical.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.FileSystemGlobbing/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-KdZAM2YMYBipVp/4tSEWPLnrocd17SL4iaXdgXjR5/nheBXbfR5QfPWYoTyh6C6IW3uKR7TRMwQr2qCvtaCTiA==",
"path": "microsoft.extensions.filesystemglobbing/9.0.10",
"hashPath": "microsoft.extensions.filesystemglobbing.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Hosting/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-63yDlitelCBNl1unJsnEWVUCZHOtxbVTbTODi7cszQJBG9bIfdPYIpB9w0UIcoqVSP1C9P6THXgukx8APWRzMw==",
"path": "microsoft.extensions.hosting/9.0.10",
"hashPath": "microsoft.extensions.hosting.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Hosting.Abstractions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-spfXydiEQENFwxdgr3Y57wwys/FRjfmq5VjHGPh6ct1FJK7X+qNEWYbnZJCMqq0B0oJTMvnItAReOv4mi2Idog==",
"path": "microsoft.extensions.hosting.abstractions/9.0.10",
"hashPath": "microsoft.extensions.hosting.abstractions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Hosting.WindowsServices/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6J44fDEcblCFoc7vNwuxLQrfY9ldToc2pkfzuraCOCsRAVFg4Fajm+PdpDYQ3VzG7eh1Iqc1JMPs9LMpaIeSKA==",
"path": "microsoft.extensions.hosting.windowsservices/9.0.10",
"hashPath": "microsoft.extensions.hosting.windowsservices.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Http/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==",
"path": "microsoft.extensions.http/8.0.0",
"hashPath": "microsoft.extensions.http.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UBXHqE9vyptVhaFnT1R7YJKCve7TqVI10yjjUZBNGMlW2lZ4c031Slt9hxsOzWCzlpPxxIFyf1Yk4a6Iubxx7w==",
"path": "microsoft.extensions.logging/9.0.10",
"hashPath": "microsoft.extensions.logging.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-MFUPv/nN1rAQ19w43smm6bbf0JDYN/1HEPHoiMYY50pvDMFpglzWAuoTavByDmZq7UuhjaxwrET3joU69ZHoHQ==",
"path": "microsoft.extensions.logging.abstractions/9.0.10",
"hashPath": "microsoft.extensions.logging.abstractions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Configuration/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-qwTRpxrmLOXZrbgQHRZ9wS2AtVa/61DFIYk8k1rBCCgA5qW0MBxxQC4BjkaI0wSoHHOv/IUXBeFNK+Y59qe/Ug==",
"path": "microsoft.extensions.logging.configuration/9.0.10",
"hashPath": "microsoft.extensions.logging.configuration.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Console/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ponA8k4E4S0LlQ8J4ce4Yp1NND8rxww0lbADK9yL3omRpnnawiENb7W/CTgZUIZVJxKcmIwhm1IbUCRk6RLocQ==",
"path": "microsoft.extensions.logging.console/9.0.10",
"hashPath": "microsoft.extensions.logging.console.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Debug/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Uj4YMaMMLawIkpHYnDWsR2/pufV/8X3dDT1/RNhkmt8RRf6/SriyA2gxH6I6bj4gFx6yMuFWZhCgFLy3wcSGTw==",
"path": "microsoft.extensions.logging.debug/9.0.10",
"hashPath": "microsoft.extensions.logging.debug.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Logging.EventLog/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Son+9zr7gnuYv1CcuZ8b6XhZK/UQRG88Ku1iSUvAQSZ1cFjYC+lDYRD6nBVXF2QIQyv0jhjt/MPKD7sA+323TQ==",
"path": "microsoft.extensions.logging.eventlog/9.0.10",
"hashPath": "microsoft.extensions.logging.eventlog.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Logging.EventSource/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-qhqkUWsf/CVyQ9V98n5uWSQcvy7HbyRkhyhpK75OKojWuaNKoEIfBmrHRiahmdGJDuh2Qz/nDpFOjQOi/ERtZQ==",
"path": "microsoft.extensions.logging.eventsource/9.0.10",
"hashPath": "microsoft.extensions.logging.eventsource.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Options/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zMNABt8eBv0B0XrWjFy9nZNgddavaOeq3ZdaD5IlHhRH65MrU7HM+Hd8GjWE3e2VDGFPZFfSAc6XVXC17f9fOA==",
"path": "microsoft.extensions.options/9.0.10",
"hashPath": "microsoft.extensions.options.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Options.ConfigurationExtensions/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-wLsf2TyVFFxWQPv0PRJj365it1ngIt8utlHJWSZ9OJ2k+NDa/PtBIRsGlF/NkoLwm1m+1vOePNl2MiKfk6lYfQ==",
"path": "microsoft.extensions.options.configurationextensions/9.0.10",
"hashPath": "microsoft.extensions.options.configurationextensions.9.0.10.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3pl8D1O5ZwMpDkZAT2uXrhQ6NipkwEgDLMFuURiHTf72TvkoMP61QYH3Vk1yrzVHnHBdNZk3cQACz8Zc7YGNhQ==",
"path": "microsoft.extensions.primitives/9.0.10",
"hashPath": "microsoft.extensions.primitives.9.0.10.nupkg.sha512"
},
"Microsoft.OpenApi/1.6.14": {
"type": "package",
"serviceable": true,
"sha512": "sha512-tTaBT8qjk3xINfESyOPE2rIellPvB7qpVqiWiyA/lACVvz+xOGiXhFUfohcx82NLbi5avzLW0lx+s6oAqQijfw==",
"path": "microsoft.openapi/1.6.14",
"hashPath": "microsoft.openapi.1.6.14.nupkg.sha512"
},
"Swashbuckle.AspNetCore/6.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-+NB4UYVYN6AhDSjW0IJAd1AGD8V33gemFNLPaxKTtPkHB+HaKAKf9MGAEUPivEWvqeQfcKIw8lJaHq6LHljRuw==",
"path": "swashbuckle.aspnetcore/6.6.2",
"hashPath": "swashbuckle.aspnetcore.6.6.2.nupkg.sha512"
},
"Swashbuckle.AspNetCore.Swagger/6.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ovgPTSYX83UrQUWiS5vzDcJ8TEX1MAxBgDFMK45rC24MorHEPQlZAHlaXj/yth4Zf6xcktpUgTEBvffRQVwDKA==",
"path": "swashbuckle.aspnetcore.swagger/6.6.2",
"hashPath": "swashbuckle.aspnetcore.swagger.6.6.2.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerGen/6.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zv4ikn4AT1VYuOsDCpktLq4QDq08e7Utzbir86M5/ZkRaLXbCPF11E1/vTmOiDzRTl0zTZINQU2qLKwTcHgfrA==",
"path": "swashbuckle.aspnetcore.swaggergen/6.6.2",
"hashPath": "swashbuckle.aspnetcore.swaggergen.6.6.2.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerUI/6.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-mBBb+/8Hm2Q3Wygag+hu2jj69tZW5psuv0vMRXY07Wy+Rrj40vRP8ZTbKBhs91r45/HXT4aY4z0iSBYx1h6JvA==",
"path": "swashbuckle.aspnetcore.swaggerui/6.6.2",
"hashPath": "swashbuckle.aspnetcore.swaggerui.6.6.2.nupkg.sha512"
},
"System.Diagnostics.DiagnosticSource/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uIpKiKp7EWlYZBK71jYP+maGYjDY9YTi/FxBlZoqDzM1ZHZB7gLqUm4jHvRFwaKfR1/Lrt2rQih9LGPIKyNEow==",
"path": "system.diagnostics.diagnosticsource/9.0.10",
"hashPath": "system.diagnostics.diagnosticsource.9.0.10.nupkg.sha512"
},
"System.Diagnostics.EventLog/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Jc+az1pTMujPLDn2j5eqSfzlO7j/T1K/LB7THxdfRWOxujE4zaitUqBs7sv1t6/xmmvpU6Xx3IofCs4owYH0yQ==",
"path": "system.diagnostics.eventlog/9.0.10",
"hashPath": "system.diagnostics.eventlog.9.0.10.nupkg.sha512"
},
"System.IO.Pipelines/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lwI0mhHcCxMtNSxB5ate9Gc9petWovRBUprtjz2yiIDDZPGBIaUiqNzQHJzjPuzTnvNbEMilpAXjDguKsU/2Fg==",
"path": "system.io.pipelines/9.0.10",
"hashPath": "system.io.pipelines.9.0.10.nupkg.sha512"
},
"System.ServiceProcess.ServiceController/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dmH+qHQ5wMjvEI0M2s6J+vmaU9L9ID2D9DWMFa7FiTfINfo3e3zeL4ljX7Dg5gCnFIULPFip2ej2iIAC3X6MFw==",
"path": "system.serviceprocess.servicecontroller/9.0.10",
"hashPath": "system.serviceprocess.servicecontroller.9.0.10.nupkg.sha512"
},
"System.Text.Encodings.Web/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-znmiJFUa0GGwq7t6ShUKBDRlPsNJaudNFI7rVeyGnRBhiRMegBvu2GRcadThP/QX/a5UpGgZbe6tolDooobj/Q==",
"path": "system.text.encodings.web/9.0.10",
"hashPath": "system.text.encodings.web.9.0.10.nupkg.sha512"
},
"System.Text.Json/9.0.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-XM02ZBnzxk7Ti6l9YRy8Bp639wANqJzJzw4W4VYiCh+HXY9hBOWkGB4k89OLP/s/RxgM02P4a/mWcJTgFiLf1Q==",
"path": "system.text.json/9.0.10",
"hashPath": "system.text.json.9.0.10.nupkg.sha512"
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

View File

@@ -0,0 +1,5 @@
{
"Version": 1,
"ManifestType": "Publish",
"Endpoints": []
}

View File

@@ -0,0 +1,576 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>RoslynBridge.WebApi</name>
</assembly>
<members>
<member name="T:RoslynBridge.WebApi.Controllers.HealthController">
<summary>
Health check and status controller
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HealthController.GetHealth(System.Threading.CancellationToken)">
<summary>
Get the health status of the middleware and Visual Studio plugin
</summary>
<param name="cancellationToken">Cancellation token</param>
<returns>Health status information</returns>
<response code="200">Service is healthy</response>
<response code="503">Service is unhealthy</response>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HealthController.Ping">
<summary>
Simple ping endpoint
</summary>
<returns>Pong response</returns>
</member>
<member name="T:RoslynBridge.WebApi.Controllers.HistoryController">
<summary>
Controller for accessing query history
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HistoryController.GetAll">
<summary>
Get all history entries
</summary>
<returns>List of all history entries</returns>
<response code="200">Returns the list of history entries</response>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HistoryController.GetById(System.String)">
<summary>
Get a specific history entry by ID
</summary>
<param name="id">The history entry ID</param>
<returns>The history entry</returns>
<response code="200">Returns the history entry</response>
<response code="404">Entry not found</response>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HistoryController.GetRecent(System.Int32)">
<summary>
Get recent history entries
</summary>
<param name="count">Number of entries to return (default: 50, max: 500)</param>
<returns>List of recent history entries</returns>
<response code="200">Returns the list of recent entries</response>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HistoryController.GetStats">
<summary>
Get history statistics
</summary>
<returns>Statistics about history entries</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.HistoryController.Clear">
<summary>
Clear all history entries
</summary>
<returns>Confirmation message</returns>
<response code="200">History cleared successfully</response>
</member>
<member name="T:RoslynBridge.WebApi.Controllers.InstancesController">
<summary>
Controller for managing Visual Studio instance registrations
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.Register(RoslynBridge.WebApi.Models.VSInstanceInfo)">
<summary>
Register a new Visual Studio instance
</summary>
<param name="instance">Instance information</param>
<returns>Registration result</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.Unregister(System.Int32)">
<summary>
Unregister a Visual Studio instance
</summary>
<param name="processId">Process ID of the instance to unregister</param>
<returns>Unregistration result</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.Heartbeat(System.Int32)">
<summary>
Update heartbeat for a Visual Studio instance
</summary>
<param name="processId">Process ID of the instance</param>
<returns>Heartbeat update result</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.GetAll">
<summary>
Get all registered Visual Studio instances
</summary>
<returns>List of registered instances</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.GetByProcessId(System.Int32)">
<summary>
Get instance by process ID
</summary>
<param name="processId">Process ID</param>
<returns>Instance information</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.GetBySolutionPath(System.String)">
<summary>
Get instance by solution path
</summary>
<param name="solutionPath">Solution file path</param>
<returns>Instance information</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.InstancesController.GetByPort(System.Int32)">
<summary>
Get instance by port
</summary>
<param name="port">Port number</param>
<returns>Instance information</returns>
</member>
<member name="T:RoslynBridge.WebApi.Controllers.RoslynController">
<summary>
Controller for Roslyn code analysis operations
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.ExecuteQuery(RoslynBridge.WebApi.Models.RoslynQueryRequest,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Execute a Roslyn query
</summary>
<param name="request">The query request</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>The query result</returns>
<response code="200">Query executed successfully</response>
<response code="400">Invalid request</response>
<response code="500">Internal server error</response>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.GetProjects(System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Get all projects in the solution
</summary>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>List of projects</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.GetSolutionOverview(System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Get solution overview
</summary>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>Solution statistics and overview</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.GetDiagnostics(System.String,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Get diagnostics (errors and warnings)
</summary>
<param name="filePath">Optional file path to filter diagnostics</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>List of diagnostics</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.GetSymbol(System.String,System.Int32,System.Int32,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Get symbol information at a specific position
</summary>
<param name="filePath">File path</param>
<param name="line">Line number (1-based)</param>
<param name="column">Column number (0-based)</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>Symbol information</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.FindReferences(System.String,System.Int32,System.Int32,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Find all references to a symbol
</summary>
<param name="filePath">File path</param>
<param name="line">Line number (1-based)</param>
<param name="column">Column number (0-based)</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>List of references</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.FindSymbol(System.String,System.String,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Search for symbols by name
</summary>
<param name="symbolName">Symbol name or pattern</param>
<param name="kind">Optional symbol kind filter</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>List of matching symbols</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.FormatDocument(System.String,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Format a document
</summary>
<param name="filePath">File path to format</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>Format operation result</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.AddNuGetPackage(System.String,System.String,System.String,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Add a NuGet package to a project
</summary>
<param name="projectName">Project name</param>
<param name="packageName">NuGet package name</param>
<param name="version">Optional package version</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>Operation result</returns>
</member>
<member name="M:RoslynBridge.WebApi.Controllers.RoslynController.BuildProject(System.String,System.String,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Build a project
</summary>
<param name="projectName">Project name</param>
<param name="configuration">Build configuration (Debug/Release)</param>
<param name="instancePort">Optional: specific VS instance port to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>Build result</returns>
</member>
<member name="T:RoslynBridge.WebApi.Middleware.HistoryMiddleware">
<summary>
Middleware to capture and log all Roslyn API requests and responses
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Middleware.HistoryMiddlewareExtensions">
<summary>
Extension methods for adding history middleware
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Models.HealthCheckResponse">
<summary>
Health check response for service status
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.HealthCheckResponse.Status">
<summary>
Overall health status
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.HealthCheckResponse.WebApiStatus">
<summary>
Web API service status
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.HealthCheckResponse.VsPluginStatus">
<summary>
Visual Studio plugin connection status
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.HealthCheckResponse.Timestamp">
<summary>
Timestamp of the health check
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.HealthCheckResponse.Version">
<summary>
API version
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Models.QueryHistoryEntry">
<summary>
Represents a single query history entry with request and response information
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Id">
<summary>
Unique identifier for this history entry
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Timestamp">
<summary>
Timestamp when the request was received
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Path">
<summary>
The endpoint path that was called
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Method">
<summary>
HTTP method used
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Request">
<summary>
The Roslyn query request
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Response">
<summary>
The Roslyn query response
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.DurationMs">
<summary>
Request duration in milliseconds
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.Success">
<summary>
Whether the request was successful
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.QueryHistoryEntry.ClientIp">
<summary>
Client IP address
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Models.RoslynQueryRequest">
<summary>
Request model for Roslyn query operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.QueryType">
<summary>
Type of query to execute (e.g., "getsymbol", "getdocument", "findreferences")
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.FilePath">
<summary>
File path for file-based operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.SymbolName">
<summary>
Symbol name for symbol-based operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.Line">
<summary>
Line number (1-based) for position-based operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.Column">
<summary>
Column number (0-based) for position-based operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.Parameters">
<summary>
Additional parameters for the query
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.ProjectName">
<summary>
Project name for project operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.PackageName">
<summary>
Package name for NuGet operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.Version">
<summary>
Version for NuGet package operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.Configuration">
<summary>
Build configuration (Debug/Release)
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryRequest.DirectoryPath">
<summary>
Directory path for directory operations
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Models.RoslynQueryResponse">
<summary>
Response model for Roslyn query operations
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryResponse.Success">
<summary>
Indicates whether the operation was successful
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryResponse.Message">
<summary>
Optional message providing additional context
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryResponse.Data">
<summary>
The response data (structure varies by query type)
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.RoslynQueryResponse.Error">
<summary>
Error message if the operation failed
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Models.VSInstanceInfo">
<summary>
Information about a registered Visual Studio instance
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.Port">
<summary>
The port number where this VS instance is listening
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.ProcessId">
<summary>
The process ID of the Visual Studio instance
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.SolutionPath">
<summary>
The solution file path (if any solution is open)
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.SolutionName">
<summary>
The solution name (if any solution is open)
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.RegisteredAt">
<summary>
When this instance was registered
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.LastHeartbeat">
<summary>
Last heartbeat time
</summary>
</member>
<member name="P:RoslynBridge.WebApi.Models.VSInstanceInfo.Projects">
<summary>
List of project names in the solution
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Services.HistoryService">
<summary>
In-memory implementation of history service
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Services.IHistoryService">
<summary>
Service for managing query history
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IHistoryService.Add(RoslynBridge.WebApi.Models.QueryHistoryEntry)">
<summary>
Add a new history entry
</summary>
<param name="entry">The history entry to add</param>
</member>
<member name="M:RoslynBridge.WebApi.Services.IHistoryService.GetAll">
<summary>
Get all history entries
</summary>
<returns>List of all history entries</returns>
</member>
<member name="M:RoslynBridge.WebApi.Services.IHistoryService.GetById(System.String)">
<summary>
Get a specific history entry by ID
</summary>
<param name="id">The entry ID</param>
<returns>The history entry, or null if not found</returns>
</member>
<member name="M:RoslynBridge.WebApi.Services.IHistoryService.GetRecent(System.Int32)">
<summary>
Get recent history entries
</summary>
<param name="count">Number of entries to return</param>
<returns>List of recent history entries</returns>
</member>
<member name="M:RoslynBridge.WebApi.Services.IHistoryService.Clear">
<summary>
Clear all history entries
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IHistoryService.GetCount">
<summary>
Get total count of history entries
</summary>
<returns>Total number of entries</returns>
</member>
<member name="T:RoslynBridge.WebApi.Services.IInstanceRegistryService">
<summary>
Service for managing registered Visual Studio instances
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.Register(RoslynBridge.WebApi.Models.VSInstanceInfo)">
<summary>
Register a new Visual Studio instance
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.Unregister(System.Int32)">
<summary>
Unregister a Visual Studio instance by process ID
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.UpdateHeartbeat(System.Int32)">
<summary>
Update heartbeat for an instance
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.GetAllInstances">
<summary>
Get all registered instances
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.GetByProcessId(System.Int32)">
<summary>
Get instance by process ID
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.GetBySolutionPath(System.String)">
<summary>
Get instance by solution path
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.GetByPort(System.Int32)">
<summary>
Get instance by port
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IInstanceRegistryService.RemoveStaleInstances(System.TimeSpan)">
<summary>
Remove stale instances (no heartbeat for specified timeout)
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Services.InstanceCleanupService">
<summary>
Background service that periodically removes stale VS instances
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Services.InstanceRegistryService">
<summary>
Thread-safe in-memory registry for Visual Studio instances
</summary>
</member>
<member name="T:RoslynBridge.WebApi.Services.IRoslynBridgeClient">
<summary>
Interface for communicating with the Roslyn Bridge Visual Studio plugin
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.IRoslynBridgeClient.ExecuteQueryAsync(RoslynBridge.WebApi.Models.RoslynQueryRequest,System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Execute a query against the Roslyn Bridge server
</summary>
<param name="request">The query request</param>
<param name="instancePort">Optional port of specific VS instance to target</param>
<param name="cancellationToken">Cancellation token</param>
<returns>The query response</returns>
</member>
<member name="M:RoslynBridge.WebApi.Services.IRoslynBridgeClient.IsHealthyAsync(System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>
Check if the Roslyn Bridge server is healthy
</summary>
<param name="instancePort">Optional port of specific VS instance to check</param>
<param name="cancellationToken">Cancellation token</param>
<returns>True if healthy, false otherwise</returns>
</member>
<member name="T:RoslynBridge.WebApi.Services.RoslynBridgeClient">
<summary>
HTTP client for communicating with the Roslyn Bridge Visual Studio plugin
</summary>
</member>
<member name="M:RoslynBridge.WebApi.Services.RoslynBridgeClient.ResolveInstancePortAsync(System.Nullable{System.Int32},RoslynBridge.WebApi.Models.RoslynQueryRequest)">
<summary>
Resolves which VS instance port to use based on provided hints
</summary>
</member>
</members>
</doc>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"RoslynBridge.WebApi": "Debug"
}
},
"RoslynBridge": {
"BaseUrl": "http://localhost:59123"
}
}

View File

@@ -0,0 +1,33 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"RoslynBridge.WebApi": "Information"
},
"EventLog": {
"SourceName": "Roslyn Bridge Web API",
"LogName": "Application",
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
},
"AllowedHosts": "*",
"RoslynBridge": {
"BaseUrl": "http://localhost:59123",
"TimeoutSeconds": 30
},
"History": {
"MaxEntries": 1000,
"Enabled": true
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5001"
}
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\RoslynBridge.WebApi.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
<!--ProjectGuid: F257A158-E5B4-569A-2B64-6EA9157F9C52-->

View File

@@ -4,6 +4,8 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoslynBridge", "RoslynBridge\RoslynBridge.csproj", "{B2C3D4E5-F6A7-4B5C-9D8E-0F1A2B3C4D5E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoslynBridge.WebApi", "RoslynBridge.WebApi\RoslynBridge.WebApi.csproj", "{F257A158-E5B4-569A-2B64-6EA9157F9C52}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -14,6 +16,10 @@ Global
{B2C3D4E5-F6A7-4B5C-9D8E-0F1A2B3C4D5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2C3D4E5-F6A7-4B5C-9D8E-0F1A2B3C4D5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2C3D4E5-F6A7-4B5C-9D8E-0F1A2B3C4D5E}.Release|Any CPU.Build.0 = Release|Any CPU
{F257A158-E5B4-569A-2B64-6EA9157F9C52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F257A158-E5B4-569A-2B64-6EA9157F9C52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F257A158-E5B4-569A-2B64-6EA9157F9C52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F257A158-E5B4-569A-2B64-6EA9157F9C52}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,291 @@
# RoslynBridge Extension Management Guide
Quick reference for managing the RoslynBridge Visual Studio extension.
## Common Issues
### "Extension Already Installed" but Not Visible
This happens when Visual Studio's extension cache is out of sync. Use the cleanup script:
```powershell
cd C:\Users\AJ\Desktop\RoslynBridge\RoslynBridge
.\cleanup-and-reinstall.ps1
```
## Scripts
### cleanup-and-reinstall.ps1
Automates extension cleanup and reinstallation.
**Basic Usage:**
```powershell
# Clean and reinstall everything (default)
.\cleanup-and-reinstall.ps1
# Only clean (remove extension and cache)
.\cleanup-and-reinstall.ps1 -Action Clean
# Only reinstall (skip cleanup)
.\cleanup-and-reinstall.ps1 -Action Reinstall
# Reinstall without rebuilding (use existing VSIX)
.\cleanup-and-reinstall.ps1 -Action Reinstall -SkipBuild
```
**What it does:**
1. **Closes Visual Studio** - Ensures no file locks
2. **Cleans Cache** - Removes extension metadata cache
3. **Removes Extension** - Deletes installed extension files
4. **Rebuilds** - Compiles the extension in Release mode
5. **Reinstalls** - Installs the new VSIX
6. **Verifies** - Checks installation succeeded
## Manual Management
### Check if Extension is Installed
```powershell
# Check extension folder
Test-Path "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ulxnn4r3.rql"
# View extension files
ls "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ulxnn4r3.rql"
```
### Manual Cleanup
```powershell
# Close Visual Studio first!
# Remove cache files
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ExtensionMetadataCache.sqlite" -Force
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ExtensionMetadata.mpack" -Force
# Remove extension
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ulxnn4r3.rql" -Recurse -Force
# Remove component cache (optional, full reset)
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\ComponentModelCache" -Recurse -Force
```
### Manual Build
```powershell
cd C:\Users\AJ\Desktop\RoslynBridge\RoslynBridge
# Build using MSBuild
& "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" `
RoslynBridge.csproj `
/t:Rebuild `
/p:Configuration=Release `
/v:minimal
```
### Manual Install
```powershell
# Find the VSIX
$vsixPath = (Get-ChildItem -Path . -Filter "*.vsix" -Recurse | Where-Object {
$_.FullName -like "*\bin\Release\*"
} | Sort-Object LastWriteTime -Descending | Select-Object -First 1).FullName
# Install using VSIXInstaller
& "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\VSIXInstaller.exe" `
/quiet `
/admin `
$vsixPath
```
## Testing the Extension
### 1. Check Visual Studio Extensions UI
1. Open Visual Studio
2. Go to **Extensions → Manage Extensions**
3. Click **Installed** tab
4. Look for "Roslyn Bridge"
### 2. Test HTTP Endpoint
```powershell
# Health check
curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"
# Get projects
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d '{"queryType":"getprojects"}'
```
### 3. Check Output Window
In Visual Studio:
1. Open **View → Output**
2. Select "RoslynBridge" from the dropdown
3. Should see startup messages when opening a solution
### 4. Check Event Viewer (if issues)
1. Open Event Viewer
2. Navigate to: **Windows Logs → Application**
3. Filter by Source: "VSPackage"
4. Look for RoslynBridge-related messages
## Debugging
### Extension Won't Load
**Symptoms:**
- Extension shows in "Installed" but HTTP endpoint doesn't work
- No output in RoslynBridge Output window
**Solutions:**
1. **Check Extension Loading:**
```powershell
# In Visual Studio, open Developer PowerShell
Get-VSPackage | Where-Object { $_.Name -like "*Roslyn*" }
```
2. **Enable Diagnostic Logging:**
- Run Visual Studio with: `devenv.exe /log`
- Check log at: `%APPDATA%\Microsoft\VisualStudio\17.0_XXXXX\ActivityLog.xml`
3. **Reset Visual Studio:**
```cmd
devenv.exe /ResetSettings
```
### Port 59123 Already in Use
```powershell
# Check what's using the port
netstat -ano | findstr :59123
# Kill the process if needed
taskkill /PID <PID> /F
```
### Extension Loads But Crashes
Check the ActivityLog:
```powershell
# Open ActivityLog with formatting
code "$env:APPDATA\Microsoft\VisualStudio\17.0_875bdf7a\ActivityLog.xml"
```
Look for entries with:
- `Type="Error"`
- `Source="RoslynBridge"`
## Development Workflow
### Quick Iteration During Development
```powershell
# 1. Make code changes
# 2. Quick reinstall (no need to close VS manually)
.\cleanup-and-reinstall.ps1
# 3. Open Visual Studio and test
```
### Debugging the Extension
1. Open the RoslynBridge solution in Visual Studio
2. Set breakpoints in your code
3. Press **F5** (or Debug → Start Debugging)
4. This launches a new "Experimental Instance" of VS
5. Open a solution in the experimental instance
6. Breakpoints will hit in the original VS instance
### Testing in Production VS (Not Experimental)
```powershell
# Install in production VS
.\cleanup-and-reinstall.ps1
# Attach debugger from another VS instance
# 1. Open a second Visual Studio
# 2. Debug → Attach to Process
# 3. Select "devenv.exe" (the first VS)
# 4. Set breakpoints in the RoslynBridge code
```
## Uninstalling
### Complete Removal
```powershell
# Use the cleanup script
.\cleanup-and-reinstall.ps1 -Action Clean
# OR manually remove via Extensions Manager
# 1. Open Visual Studio
# 2. Extensions → Manage Extensions
# 3. Find "Roslyn Bridge"
# 4. Click "Uninstall"
# 5. Restart Visual Studio
```
### Remove All Traces
```powershell
# Remove extension
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ulxnn4r3.rql" -Recurse -Force
# Remove all VS caches (nuclear option)
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\ComponentModelCache" -Recurse -Force
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ExtensionMetadataCache.sqlite" -Force
Remove-Item "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ExtensionMetadata.mpack" -Force
# Reset Visual Studio
devenv.exe /ResetSettings
```
## Troubleshooting Quick Reference
| Issue | Solution |
|-------|----------|
| Extension not visible in UI | `.\cleanup-and-reinstall.ps1` |
| "Already installed" error | `.\cleanup-and-reinstall.ps1 -Action Clean` then reinstall |
| Port 59123 not responding | Check extension loaded in Output window |
| VS won't close | Use Task Manager to end `devenv.exe` |
| Build fails | Check MSBuild output, ensure .NET SDK installed |
| Install fails | Run as Administrator, check VSIXInstaller.exe exists |
| Extension crashes on load | Check ActivityLog.xml for errors |
## File Locations Reference
```
Extension Installation:
C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ulxnn4r3.rql\
Cache Files:
C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ExtensionMetadataCache.sqlite
C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\Extensions\ExtensionMetadata.mpack
C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a\ComponentModelCache\
Activity Log:
C:\Users\AJ\AppData\Roaming\Microsoft\VisualStudio\17.0_875bdf7a\ActivityLog.xml
Build Output:
C:\Users\AJ\Desktop\RoslynBridge\RoslynBridge\bin\Release\
VSIX File:
C:\Users\AJ\Desktop\RoslynBridge\RoslynBridge\bin\Release\RoslynBridge.vsix
```
## Getting Help
If issues persist:
1. Check the ActivityLog.xml for detailed error messages
2. Run Visual Studio with logging: `devenv.exe /log`
3. Check Windows Event Viewer for crashes
4. Try the "nuclear option": Complete removal + reinstall
5. Check that .NET Framework 4.8 is installed
6. Ensure Visual Studio 2022 Community/Pro/Enterprise (17.0+)

View File

@@ -74,12 +74,14 @@
<!-- Services -->
<Compile Include="Services\BaseRoslynService.cs" />
<Compile Include="Services\ConfigurationService.cs" />
<Compile Include="Services\DiagnosticsService.cs" />
<Compile Include="Services\DocumentQueryService.cs" />
<Compile Include="Services\IRoslynQueryService.cs" />
<Compile Include="Services\IWorkspaceProvider.cs" />
<Compile Include="Services\ProjectOperationsService.cs" />
<Compile Include="Services\RefactoringService.cs" />
<Compile Include="Services\RegistrationService.cs" />
<Compile Include="Services\RoslynQueryService.cs" />
<Compile Include="Services\SymbolQueryService.cs" />
<Compile Include="Services\WorkspaceProvider.cs" />
@@ -87,6 +89,10 @@
<None Include="source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="README.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
@@ -95,6 +101,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
</ItemGroup>

View File

@@ -18,6 +18,7 @@ namespace RoslynBridge
{
public const string PackageGuidString = "b2c3d4e5-f6a7-4b5c-9d8e-0f1a2b3c4d5e";
private BridgeServer? _bridgeServer;
private Services.RegistrationService? _registrationService;
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
@@ -29,6 +30,10 @@ namespace RoslynBridge
_bridgeServer = new BridgeServer(this);
await _bridgeServer.StartAsync();
// Register with WebAPI
_registrationService = new Services.RegistrationService(this, _bridgeServer.Port);
await _registrationService.RegisterAsync();
await base.InitializeAsync(cancellationToken, progress);
}
catch (Exception ex)
@@ -43,6 +48,8 @@ namespace RoslynBridge
{
if (disposing)
{
_registrationService?.UnregisterAsync().Wait(TimeSpan.FromSeconds(5));
_registrationService?.Dispose();
_bridgeServer?.Dispose();
}
base.Dispose(disposing);

View File

@@ -18,12 +18,14 @@ namespace RoslynBridge.Server
private readonly AsyncPackage _package;
private readonly IRoslynQueryService _queryService;
private bool _isRunning;
private readonly int _port;
private int _port;
public BridgeServer(AsyncPackage package, int port = ServerConstants.DefaultPort)
public int Port => _port;
public BridgeServer(AsyncPackage package, int? startPort = null)
{
_package = package;
_port = port;
_port = startPort ?? ConfigurationService.Instance.DefaultPort;
_queryService = new RoslynQueryService(package);
}
@@ -38,12 +40,36 @@ namespace RoslynBridge.Server
{
await _queryService.InitializeAsync();
_listener = new HttpListener();
_listener.Prefixes.Add(ServerConstants.GetServerUrl(_port));
_listener.Start();
_isRunning = true;
// Try to find an available port
int maxPort = _port + ConfigurationService.Instance.MaxPortRange;
bool started = false;
System.Diagnostics.Debug.WriteLine($"Roslyn Bridge HTTP server started on port {_port}");
for (int port = _port; port < maxPort; port++)
{
try
{
_listener = new HttpListener();
_listener.Prefixes.Add(ServerConstants.GetServerUrl(port));
_listener.Start();
_port = port; // Update to the port that worked
started = true;
System.Diagnostics.Debug.WriteLine($"Roslyn Bridge HTTP server started on port {_port}");
break;
}
catch (HttpListenerException)
{
// Port is in use, try next one
_listener?.Close();
_listener = null;
}
}
if (!started)
{
throw new Exception($"Could not find available port in range {_port}-{maxPort}");
}
_isRunning = true;
// Start listening for requests in the background
#pragma warning disable CS4014
@@ -95,6 +121,8 @@ namespace RoslynBridge.Server
return;
}
var path = context.Request.Url?.AbsolutePath ?? "/";
if (context.Request.HttpMethod != "POST")
{
await RespondWithError(response, 405, "Only POST requests are supported");
@@ -110,7 +138,7 @@ namespace RoslynBridge.Server
}
// Route request
var queryResponse = await RouteRequestAsync(context.Request.Url?.AbsolutePath ?? "/", request);
var queryResponse = await RouteRequestAsync(path, request);
response.StatusCode = queryResponse.Success ? 200 : 400;
await WriteResponseAsync(response, queryResponse);
}
@@ -196,6 +224,22 @@ namespace RoslynBridge.Server
}
}
private static async Task WriteRawResponseAsync(HttpListenerResponse response, string contentType, string content)
{
try
{
response.ContentType = contentType;
var buffer = Encoding.UTF8.GetBytes(content);
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
response.Close();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error writing raw response: {ex}");
}
}
public void Dispose()
{
_isRunning = false;

View File

@@ -0,0 +1,85 @@
using System;
using System.IO;
using System.Reflection;
using System.Text.Json;
namespace RoslynBridge.Services
{
/// <summary>
/// Service for reading configuration from appsettings.json
/// </summary>
public class ConfigurationService
{
private static ConfigurationService? _instance;
private readonly RoslynBridgeConfig _config;
private ConfigurationService()
{
_config = LoadConfiguration();
}
public static ConfigurationService Instance => _instance ??= new ConfigurationService();
public string WebApiUrl => _config.RoslynBridge.WebApiUrl;
public int DefaultPort => _config.RoslynBridge.DefaultPort;
public int MaxPortRange => _config.RoslynBridge.MaxPortRange;
public int HeartbeatIntervalSeconds => _config.RoslynBridge.HeartbeatIntervalSeconds;
private RoslynBridgeConfig LoadConfiguration()
{
try
{
// Get the directory where the extension is installed
var assemblyPath = Assembly.GetExecutingAssembly().Location;
var assemblyDir = Path.GetDirectoryName(assemblyPath);
var configPath = Path.Combine(assemblyDir ?? "", "appsettings.json");
if (File.Exists(configPath))
{
var json = File.ReadAllText(configPath);
var config = JsonSerializer.Deserialize<RoslynBridgeConfig>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (config != null)
{
System.Diagnostics.Debug.WriteLine($"Loaded configuration from {configPath}");
return config;
}
}
System.Diagnostics.Debug.WriteLine($"Configuration file not found at {configPath}, using defaults");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading configuration: {ex.Message}, using defaults");
}
// Return default configuration
return new RoslynBridgeConfig
{
RoslynBridge = new RoslynBridgeSettings
{
WebApiUrl = "http://localhost:5001",
DefaultPort = 59123,
MaxPortRange = 10,
HeartbeatIntervalSeconds = 60
}
};
}
}
public class RoslynBridgeConfig
{
public RoslynBridgeSettings RoslynBridge { get; set; } = new();
}
public class RoslynBridgeSettings
{
public string WebApiUrl { get; set; } = "http://localhost:5001";
public int DefaultPort { get; set; } = 59123;
public int MaxPortRange { get; set; } = 10;
public int HeartbeatIntervalSeconds { get; set; } = 60;
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
namespace RoslynBridge.Services
{
/// <summary>
/// Service for registering this VS instance with the WebAPI
/// </summary>
public class RegistrationService : IDisposable
{
private readonly string _webApiUrl;
private readonly HttpClient _httpClient;
private readonly int _port;
private readonly AsyncPackage _package;
private System.Threading.Timer? _heartbeatTimer;
private bool _isRegistered;
public RegistrationService(AsyncPackage package, int port)
{
_package = package;
_port = port;
_webApiUrl = ConfigurationService.Instance.WebApiUrl;
_httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
}
public async Task RegisterAsync()
{
try
{
var dte = await GetDTEAsync();
var solutionPath = dte?.Solution?.FullName;
var solutionName = string.IsNullOrEmpty(solutionPath)
? null
: System.IO.Path.GetFileNameWithoutExtension(solutionPath);
var registrationData = new
{
port = _port,
processId = Process.GetCurrentProcess().Id,
solutionPath = string.IsNullOrEmpty(solutionPath) ? null : solutionPath,
solutionName = solutionName,
projects = new string[] { } // TODO: Get project names
};
var json = JsonSerializer.Serialize(registrationData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{_webApiUrl}/api/instances/register", content);
if (response.IsSuccessStatusCode)
{
_isRegistered = true;
Debug.WriteLine($"Successfully registered with WebAPI at {_webApiUrl}");
// Start heartbeat timer (configurable interval)
var heartbeatInterval = TimeSpan.FromSeconds(ConfigurationService.Instance.HeartbeatIntervalSeconds);
_heartbeatTimer = new System.Threading.Timer(
async _ => await SendHeartbeatAsync(),
null,
heartbeatInterval,
heartbeatInterval);
}
else
{
Debug.WriteLine($"Failed to register with WebAPI: {response.StatusCode}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error registering with WebAPI: {ex.Message}");
// Don't throw - registration is optional, VS extension should work standalone
}
}
private async Task SendHeartbeatAsync()
{
if (!_isRegistered)
return;
try
{
var processId = Process.GetCurrentProcess().Id;
var response = await _httpClient.PostAsync(
$"{_webApiUrl}/api/instances/heartbeat/{processId}",
null);
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine($"Heartbeat failed: {response.StatusCode}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error sending heartbeat: {ex.Message}");
}
}
public async Task UnregisterAsync()
{
if (!_isRegistered)
return;
try
{
var processId = Process.GetCurrentProcess().Id;
var response = await _httpClient.PostAsync(
$"{_webApiUrl}/api/instances/unregister/{processId}",
null);
if (response.IsSuccessStatusCode)
{
Debug.WriteLine("Successfully unregistered from WebAPI");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error unregistering from WebAPI: {ex.Message}");
}
}
private async Task<EnvDTE.DTE?> GetDTEAsync()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
return await _package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
}
public void Dispose()
{
_heartbeatTimer?.Dispose();
_httpClient?.Dispose();
}
}
}

View File

@@ -0,0 +1,8 @@
{
"RoslynBridge": {
"WebApiUrl": "http://localhost:5001",
"DefaultPort": 59123,
"MaxPortRange": 10,
"HeartbeatIntervalSeconds": 60
}
}

View File

@@ -0,0 +1,337 @@
#Requires -Version 5.0
<#
.SYNOPSIS
Cleanup and reinstall the RoslynBridge Visual Studio extension
.DESCRIPTION
This script automates the process of:
1. Closing Visual Studio
2. Removing the extension cache
3. Deleting the installed extension
4. Rebuilding and reinstalling the extension
.PARAMETER Action
The action to perform: Clean, Reinstall, or Both (default)
.PARAMETER SkipBuild
Skip the build step when reinstalling
.EXAMPLE
.\cleanup-and-reinstall.ps1
Cleans and reinstalls the extension
.EXAMPLE
.\cleanup-and-reinstall.ps1 -Action Clean
Only removes the extension and cache
.EXAMPLE
.\cleanup-and-reinstall.ps1 -Action Reinstall -SkipBuild
Reinstalls without rebuilding (uses existing VSIX)
#>
param(
[Parameter(Mandatory=$false)]
[ValidateSet("Clean", "Reinstall", "Both")]
[string]$Action = "Both",
[Parameter(Mandatory=$false)]
[switch]$SkipBuild
)
$ErrorActionPreference = "Stop"
# Colors for output
function Write-ColorOutput {
param(
[string]$Message,
[string]$Color = "White"
)
Write-Host $Message -ForegroundColor $Color
}
function Write-Step {
param([string]$Message)
Write-ColorOutput "`n>>> $Message" "Cyan"
}
function Write-Success {
param([string]$Message)
Write-ColorOutput "$Message" "Green"
}
function Write-Warning {
param([string]$Message)
Write-ColorOutput "$Message" "Yellow"
}
function Write-ErrorMsg {
param([string]$Message)
Write-ColorOutput "$Message" "Red"
}
# Configuration
$VSInstancePath = "C:\Users\AJ\AppData\Local\Microsoft\VisualStudio\17.0_875bdf7a"
$ExtensionsPath = Join-Path $VSInstancePath "Extensions"
$ExtensionFolder = "ulxnn4r3.rql"
$ExtensionFullPath = Join-Path $ExtensionsPath $ExtensionFolder
$ProjectPath = $PSScriptRoot
$SolutionFile = Join-Path $ProjectPath "RoslynBridge.csproj"
Write-ColorOutput "`n========================================" "Cyan"
Write-ColorOutput " RoslynBridge Extension Manager" "Cyan"
Write-ColorOutput "========================================`n" "Cyan"
# Step 1: Close Visual Studio
function Stop-VisualStudio {
Write-Step "Closing Visual Studio..."
$vsProcesses = Get-Process | Where-Object { $_.Name -like "devenv*" }
if ($vsProcesses) {
Write-ColorOutput " Found $($vsProcesses.Count) Visual Studio instance(s) running" "Gray"
foreach ($proc in $vsProcesses) {
try {
Write-ColorOutput " Closing Visual Studio (PID: $($proc.Id))..." "Gray"
$proc.CloseMainWindow() | Out-Null
Start-Sleep -Seconds 2
if (!$proc.HasExited) {
Write-ColorOutput " Force closing..." "Gray"
$proc.Kill()
Start-Sleep -Seconds 1
}
Write-Success "Visual Studio closed"
}
catch {
Write-Warning "Failed to close Visual Studio: $_"
}
}
# Wait for processes to fully terminate
Start-Sleep -Seconds 3
}
else {
Write-Success "Visual Studio is not running"
}
}
# Step 2: Clean extension and cache
function Clear-ExtensionCache {
Write-Step "Cleaning extension cache..."
$filesToDelete = @(
(Join-Path $ExtensionsPath "ExtensionMetadataCache.sqlite"),
(Join-Path $ExtensionsPath "ExtensionMetadata.mpack"),
(Join-Path $VSInstancePath "ComponentModelCache")
)
foreach ($file in $filesToDelete) {
if (Test-Path $file) {
try {
Remove-Item $file -Recurse -Force -ErrorAction Stop
Write-Success "Deleted: $(Split-Path $file -Leaf)"
}
catch {
Write-Warning "Could not delete $file: $_"
}
}
else {
Write-ColorOutput " Skipped (not found): $(Split-Path $file -Leaf)" "Gray"
}
}
}
function Remove-InstalledExtension {
Write-Step "Removing installed extension..."
if (Test-Path $ExtensionFullPath) {
try {
Remove-Item $ExtensionFullPath -Recurse -Force -ErrorAction Stop
Write-Success "Extension removed: $ExtensionFolder"
}
catch {
Write-ErrorMsg "Failed to remove extension: $_"
return $false
}
}
else {
Write-Success "Extension not installed"
}
return $true
}
# Step 3: Build the extension
function Build-Extension {
Write-Step "Building RoslynBridge extension..."
if (!(Test-Path $SolutionFile)) {
Write-ErrorMsg "Solution file not found: $SolutionFile"
return $false
}
Write-ColorOutput " Building in Release mode..." "Gray"
try {
# Use MSBuild to build the project
$msbuildPath = "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"
if (!(Test-Path $msbuildPath)) {
Write-ErrorMsg "MSBuild not found at: $msbuildPath"
return $false
}
$buildOutput = & $msbuildPath $SolutionFile /t:Rebuild /p:Configuration=Release /v:minimal 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Success "Build completed successfully"
return $true
}
else {
Write-ErrorMsg "Build failed with exit code: $LASTEXITCODE"
Write-ColorOutput "Build output:" "Gray"
$buildOutput | ForEach-Object { Write-ColorOutput " $_" "Gray" }
return $false
}
}
catch {
Write-ErrorMsg "Build error: $_"
return $false
}
}
# Step 4: Install the extension
function Install-Extension {
Write-Step "Installing extension..."
# Find the VSIX file
$vsixFiles = Get-ChildItem -Path $ProjectPath -Filter "*.vsix" -Recurse | Where-Object {
$_.FullName -like "*\bin\Release\*" -or $_.FullName -like "*\bin\Debug\*"
} | Sort-Object LastWriteTime -Descending
if (!$vsixFiles) {
Write-ErrorMsg "No VSIX file found. Please build the project first."
return $false
}
$vsixFile = $vsixFiles[0].FullName
Write-ColorOutput " Found VSIX: $vsixFile" "Gray"
# Use VSIXInstaller to install
$vsixInstallerPath = "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\VSIXInstaller.exe"
if (!(Test-Path $vsixInstallerPath)) {
Write-ErrorMsg "VSIXInstaller not found at: $vsixInstallerPath"
return $false
}
Write-ColorOutput " Installing extension..." "Gray"
try {
$installArgs = @("/quiet", "/admin", $vsixFile)
$installOutput = & $vsixInstallerPath $installArgs 2>&1
if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq 1001) {
# 1001 = already installed (which is fine, we're reinstalling)
Write-Success "Extension installed successfully"
return $true
}
else {
Write-ErrorMsg "Installation failed with exit code: $LASTEXITCODE"
$installOutput | ForEach-Object { Write-ColorOutput " $_" "Gray" }
return $false
}
}
catch {
Write-ErrorMsg "Installation error: $_"
return $false
}
}
# Step 5: Verify installation
function Test-ExtensionInstalled {
Write-Step "Verifying installation..."
if (Test-Path $ExtensionFullPath) {
$dllPath = Join-Path $ExtensionFullPath "RoslynBridge.dll"
if (Test-Path $dllPath) {
Write-Success "Extension files found at: $ExtensionFolder"
# Check file version
try {
$dllInfo = Get-Item $dllPath
$version = $dllInfo.VersionInfo.FileVersion
Write-ColorOutput " Version: $version" "Gray"
}
catch {
Write-ColorOutput " Version: Unable to determine" "Gray"
}
return $true
}
}
Write-Warning "Extension files not found"
return $false
}
# Main execution
try {
$startTime = Get-Date
if ($Action -eq "Clean" -or $Action -eq "Both") {
Stop-VisualStudio
Clear-ExtensionCache
$cleanSuccess = Remove-InstalledExtension
if (!$cleanSuccess) {
Write-ErrorMsg "`nCleanup failed!"
exit 1
}
}
if ($Action -eq "Reinstall" -or $Action -eq "Both") {
if (!$SkipBuild) {
$buildSuccess = Build-Extension
if (!$buildSuccess) {
Write-ErrorMsg "`nBuild failed! Cannot reinstall."
exit 1
}
}
else {
Write-Warning "Skipping build as requested"
}
$installSuccess = Install-Extension
if (!$installSuccess) {
Write-ErrorMsg "`nInstallation failed!"
exit 1
}
Test-ExtensionInstalled | Out-Null
}
$elapsed = (Get-Date) - $startTime
Write-ColorOutput "`n========================================" "Cyan"
Write-ColorOutput " ✓ Completed in $($elapsed.TotalSeconds.ToString('F1')) seconds" "Green"
Write-ColorOutput "========================================`n" "Cyan"
if ($Action -eq "Reinstall" -or $Action -eq "Both") {
Write-ColorOutput "Next steps:" "Yellow"
Write-ColorOutput " 1. Open Visual Studio" "White"
Write-ColorOutput " 2. Go to Extensions → Manage Extensions" "White"
Write-ColorOutput " 3. Check 'Installed' tab for 'Roslyn Bridge'" "White"
Write-ColorOutput " 4. Open a solution and verify the extension loads" "White"
Write-ColorOutput " 5. Test: curl -X POST http://localhost:59123/health -H 'Content-Type: application/json' -d '{}'" "White"
Write-ColorOutput ""
}
}
catch {
Write-ErrorMsg "`nUnexpected error: $_"
Write-ColorOutput $_.ScriptStackTrace "Gray"
exit 1
}

267
SKILL.md Normal file
View File

@@ -0,0 +1,267 @@
---
name: roslyn-bridge
description: Use this for C# code analysis, querying .NET projects, finding symbols, getting diagnostics, or any Roslyn/semantic analysis tasks using the bridge server
---
# Roslyn Bridge API Guide
Use this guide when accessing the Roslyn Bridge for C# code analysis.
## Architecture
```
┌─────────────┐ REST API ┌────────────────────┐ HTTP ┌─────────────────────┐
│ Claude │ ◄─────────────────► │ Web API (:5001) │ ◄────────────► │ VS Plugin (:59123) │
│ AI │ │ Middleware │ │ Roslyn Bridge │
└─────────────┘ └────────────────────┘ └─────────────────────┘
```
## Recommended: Web API (Port 5001)
**Base URL**: `http://localhost:5001`
The Web API provides a modern RESTful interface with:
- Clean REST endpoints with query parameters
- Full Swagger documentation at `/`
- Request/response history tracking at `/api/history`
- CORS support for web applications
- Better error handling and logging
### Web API Endpoints
**Health & Info:**
- `GET /api/health` - Check health of both Web API and VS plugin
- `GET /api/health/ping` - Simple ping
**Roslyn Operations:**
- `GET /api/roslyn/projects` - Get all projects
- `GET /api/roslyn/solution/overview` - Solution statistics
- `GET /api/roslyn/diagnostics?filePath={path}` - Get errors/warnings
- `GET /api/roslyn/symbol?filePath={path}&line={line}&column={col}` - Get symbol info
- `GET /api/roslyn/references?filePath={path}&line={line}&column={col}` - Find references
- `GET /api/roslyn/symbol/search?symbolName={name}&kind={kind}` - Search symbols
- `POST /api/roslyn/query` - Execute any query (fallback to raw queries)
- `POST /api/roslyn/format` - Format document
- `POST /api/roslyn/project/package/add` - Add NuGet package
- `POST /api/roslyn/project/build` - Build project
**History:**
- `GET /api/history` - All history entries
- `GET /api/history/{id}` - Specific entry
- `GET /api/history/recent?count=50` - Recent entries
- `GET /api/history/stats` - Statistics
- `DELETE /api/history` - Clear history
## Alternative: Direct VS Plugin Access (Port 59123)
**Base URL**: `http://localhost:59123`
Direct access to the Visual Studio plugin (use only if Web API is unavailable):
- **Endpoints**: `/query`, `/health`
- **Method**: POST only
- **Content-Type**: application/json
## ⚠️ CRITICAL: Command Syntax Rules
**ALWAYS use curl with the Bash tool. NEVER pipe curl output to PowerShell.**
### ✅ CORRECT: Using Web API (Recommended)
```bash
# Health check
curl http://localhost:5001/api/health
# Get all projects (returns full file paths)
curl http://localhost:5001/api/roslyn/projects
# Get solution overview
curl http://localhost:5001/api/roslyn/solution/overview
# Get diagnostics for entire solution
curl http://localhost:5001/api/roslyn/diagnostics
# Get diagnostics for specific file
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=C:/path/to/file.cs"
# Get symbol at position (use paths from /projects response)
curl "http://localhost:5001/api/roslyn/symbol?filePath=C:/path/to/file.cs&line=10&column=5"
# Find all references
curl "http://localhost:5001/api/roslyn/references?filePath=C:/path/to/file.cs&line=18&column=30"
# Search for symbols
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=MyClass&kind=class"
# Get recent history
curl http://localhost:5001/api/history/recent?count=10
# Get history statistics
curl http://localhost:5001/api/history/stats
```
**IMPORTANT curl syntax rules:**
- For GET requests, no `-X GET` needed
- Use quotes around URLs with query parameters
- Forward slashes `/` work in file paths (Windows accepts both `/` and `\`)
- **DO NOT pipe to PowerShell** - the JSON output is already formatted
### Using Direct VS Plugin Access (Fallback)
```bash
# Health check - verify VS plugin is running
curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"
# Get projects via POST
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}"
# Get symbol at position
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsymbol\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\",\"line\":10,\"column\":5}"
```
**Note:** In POST requests to VS plugin, escape backslashes: `\\\\` becomes `\\` in JSON
## Quick Reference: All Endpoints
### Query Endpoints
| Endpoint | Required Fields | Optional Fields | Description |
|----------|----------------|-----------------|-------------|
| `getprojects` | - | - | Get all projects in the solution |
| `getdocument` | `filePath` | - | Get document information for a specific file |
| `getsymbol` | `filePath`, `line`, `column` | - | Get symbol information at a specific position |
| `getsemanticmodel` | `filePath` | - | Verify semantic model availability (not serializable) |
| `getsyntaxtree` | `filePath` | - | Get the syntax tree (source code) for a file |
| `getdiagnostics` | - | `filePath` | Get compilation errors and warnings |
| `findreferences` | `filePath`, `line`, `column` | - | Find all references to a symbol |
| `findsymbol` | `symbolName` | `parameters.kind` | Find symbols by name (supports filtering by kind) |
| `gettypemembers` | `symbolName` | `parameters.includeInherited` | Get all members of a type |
| `gettypehierarchy` | `symbolName` | `parameters.direction` | Get base types or derived types |
| `findimplementations` | `symbolName` OR `filePath`+`line`+`column` | - | Find implementations of an interface/abstract member |
| `getnamespacetypes` | `symbolName` | - | Get all types in a namespace |
| `getcallhierarchy` | `filePath`, `line`, `column` | `parameters.direction` | Get callers or callees of a method |
| `getsolutionoverview` | - | - | Get high-level solution statistics |
| `getsymbolcontext` | `filePath`, `line`, `column` | - | Get contextual information about a symbol's location |
| `searchcode` | `symbolName` (regex) | `parameters.scope` | Search for code patterns using regex |
### Editing Endpoints
| Endpoint | Required Fields | Optional Fields | Description |
|----------|----------------|-----------------|-------------|
| `formatdocument` | `filePath` | - | Format a document according to coding style |
| `organizeusings` | `filePath` | - | Sort and remove unused using statements |
| `renamesymbol` | `filePath`, `line`, `column`, `parameters.newName` | - | Rename a symbol across the solution |
| `addmissingusing` | `filePath`, `line`, `column` | - | Add missing using statement for a symbol |
| `applycodefix` | `filePath`, `line`, `column` | - | Apply available code fixes at a position |
### Project Operation Endpoints
| Endpoint | Required Fields | Optional Fields | Description |
|----------|----------------|-----------------|-------------|
| `addnugetpackage` | `projectName`, `packageName` | `version` | Add a NuGet package to a project |
| `removenugetpackage` | `projectName`, `packageName` | - | Remove a NuGet package from a project |
| `buildproject` | `projectName` | `configuration` | Build a project or solution |
| `cleanproject` | `projectName` | - | Clean build output |
| `restorepackages` | `projectName` | - | Restore NuGet packages |
| `createdirectory` | `directoryPath` | - | Create a new directory |
## Common Workflow Examples
### Typical Code Analysis Workflow
```bash
# Step 1: Check if services are healthy
curl http://localhost:5001/api/health
# Step 2: Get all projects and their files
curl http://localhost:5001/api/roslyn/projects
# Response includes: {"data": [{"documents": ["C:/Full/Path/To/File.cs", ...]}]}
# Step 3: Use the full paths from Step 2 in subsequent queries
FILE="C:/Users/AJ/Desktop/MyProject/Program.cs"
# Get diagnostics (errors/warnings) for a file
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=$FILE"
# Get symbol information at a specific location
curl "http://localhost:5001/api/roslyn/symbol?filePath=$FILE&line=15&column=10"
# Find all references to that symbol
curl "http://localhost:5001/api/roslyn/references?filePath=$FILE&line=15&column=10"
# Step 4: View your query history
curl http://localhost:5001/api/history/recent?count=5
# Step 5: Get statistics about your queries
curl http://localhost:5001/api/history/stats
```
### Advanced Query Examples
```bash
# Search for all classes with "Service" in the name
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=Service&kind=class"
# Get solution-wide statistics
curl http://localhost:5001/api/roslyn/solution/overview
# For complex queries not available as REST endpoints, use POST /query
curl -X POST http://localhost:5001/api/roslyn/query \
-H "Content-Type: application/json" \
-d '{"queryType":"searchcode","symbolName":".*Controller","parameters":{"scope":"classes"}}'
# Build a project
curl -X POST "http://localhost:5001/api/roslyn/project/build?projectName=MyProject"
# Add NuGet package
curl -X POST "http://localhost:5001/api/roslyn/project/package/add?projectName=MyProject&packageName=Newtonsoft.Json&version=13.0.3"
```
## Response Format
All endpoints return JSON in this format:
**Success:**
```json
{
"success": true,
"message": "Optional message",
"data": { /* Response data varies by endpoint */ },
"error": null
}
```
**Error:**
```json
{
"success": false,
"message": null,
"data": null,
"error": "Error description"
}
```
## Important Notes
- Line numbers are **1-based** (first line is 1)
- Column numbers are **0-based** (first column is 0)
- **Use full paths from `/api/roslyn/projects` response** in other endpoints
- Forward slashes `/` work in file paths (Windows accepts both `/` and `\`)
- Both the Web API (port 5000) and VS Plugin (port 59123) must be running
- All workspace modifications use VS threading model
- Request history is automatically tracked in the Web API
## Troubleshooting
**"Failed to connect" error:**
1. Check if Web API is running: `curl http://localhost:5001/api/health/ping`
2. Check if VS Plugin is running: `curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"`
3. Ensure Visual Studio is open with a solution loaded
**"vsPluginStatus": "Disconnected":**
- The VS Plugin (port 59123) is not running
- Open Visual Studio and ensure the RoslynBridge extension is loaded
**Empty or null data:**
- Ensure you're using the correct file paths from `/api/roslyn/projects`
- Verify line/column numbers are within the file bounds
- Check that the file is part of the currently open VS solution

372
rb.ps1 Normal file
View File

@@ -0,0 +1,372 @@
# Roslyn Bridge PowerShell helper
#
# Purpose: Fast, lowtoken wrapper around the Roslyn Bridge Web API (port 5001)
# and the legacy VS plugin (port 59123). Auto-detects which is running
# and provides concise commands for common operations.
#
# Usage examples:
# .\rb.ps1 health
# .\rb.ps1 projects
# .\rb.ps1 overview
# .\rb.ps1 diagnostics -FilePath C:/path/to/File.cs
# .\rb.ps1 symbol -FilePath C:/path/to/File.cs -Line 12 -Column 4
# .\rb.ps1 references -FilePath C:/path/to/File.cs -Line 12 -Column 4
# .\rb.ps1 search -SymbolName MyService -Kind class
# .\rb.ps1 build -ProjectName MyProject
# .\rb.ps1 package-add -ProjectName MyProject -PackageName Newtonsoft.Json -Version 13.0.3
# .\rb.ps1 history-recent -Count 10
# .\rb.ps1 query -BodyJson '{"queryType":"searchcode","symbolName":".*Controller"}'
#
# Output:
# - Compressed JSON by default (minimal tokens)
# - Add -Pretty for indented JSON
# - Add -Raw to return the raw response string
[CmdletBinding(PositionalBinding=$true)]
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateSet(
'health','projects','overview','diagnostics','symbol','references','search',
'build','package-add','history','history-recent','history-stats',
'plugin-health','solution-overview'
)]
[string]$Command,
# Common params
[string]$BaseUrl, # Overrides auto-detected base URL
[switch]$NoDetect, # Skip auto-detection
[int]$TimeoutSec = 6, # Short default to stay snappy
[switch]$Raw, # Emit raw response string
[switch]$Pretty, # Indented JSON output
# Query params
[string]$FilePath,
[int]$Line,
[int]$Column,
[string]$SymbolName,
[ValidateSet('class','method','property','field','interface','enum','struct','namespace','event','any')]
[string]$Kind = 'any',
# Project ops
[string]$ProjectName,
[string]$Configuration,
[string]$PackageName,
[string]$Version,
# History
[int]$Count = 25,
# Solution overview summary options
[ValidateSet('json','text','yaml')]
[string]$Format = 'json',
[int]$Top = 5,
[bool]$IncludeNamespaces = $true
)
set-strictmode -version latest
$ErrorActionPreference = 'Stop'
function Write-Err {
param([string]$Msg)
Write-Error -Message $Msg
}
function Get-JsonOutput {
param(
[Parameter(Mandatory=$true)] $Data
)
if ($Raw) {
# If we were given an object, compress to a compact string; if it's already a string, emit it.
if ($Data -is [string]) { return $Data }
return ($Data | ConvertTo-Json -Depth 50 -Compress)
}
$json = $Data | ConvertTo-Json -Depth 50 -Compress
if ($Pretty) { $json = ($Data | ConvertTo-Json -Depth 50) }
return $json
}
function Test-Url {
param(
[string]$Url,
[ValidateSet('GET','POST')][string]$Method = 'GET'
)
try {
if ($Method -eq 'GET') {
$resp = Invoke-WebRequest -Method GET -Uri $Url -TimeoutSec $TimeoutSec
} else {
$resp = Invoke-WebRequest -Method POST -Uri $Url -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body '{}'
}
return $true
} catch {
return $false
}
}
function Get-BridgeEndpoints {
# Decide which base URL to use.
# Priority: explicit -BaseUrl -> Web API (5001) if healthy -> Plugin (59123) if healthy
$web = 'http://localhost:5001'
$plugin = 'http://localhost:59123'
if ($BaseUrl) {
return @{ BaseUrl = $BaseUrl; Mode = 'explicit' }
}
if ($NoDetect) {
return @{ BaseUrl = $web; Mode = 'web' }
}
if (Test-Url -Url "$web/api/health/ping" -Method GET) {
return @{ BaseUrl = $web; Mode = 'web' }
}
if (Test-Url -Url "$plugin/health" -Method POST) {
return @{ BaseUrl = $plugin; Mode = 'plugin' }
}
# Fallback to web
return @{ BaseUrl = $web; Mode = 'web' }
}
function Invoke-WebApi {
param(
[string]$BaseUrl,
[ValidateSet('GET','POST')][string]$Method,
[string]$Path,
[hashtable]$Query,
$Body
)
$uri = [System.UriBuilder]::new($BaseUrl)
if ($Path.StartsWith('/')) { $uri.Path = $Path } else { $uri.Path = "$($uri.Path.TrimEnd('/'))/$Path" }
if ($Query) {
$qs = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
foreach ($k in $Query.Keys) {
if ($null -ne $Query[$k] -and $Query[$k] -ne '') { $qs[$k] = [string]$Query[$k] }
}
$uri.Query = $qs.ToString()
}
if ($Raw) {
if ($Method -eq 'GET') {
return (Invoke-WebRequest -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec).Content
} else {
$bodyStr = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 50 -Compress) }
return (Invoke-WebRequest -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $bodyStr).Content
}
}
if ($Method -eq 'GET') {
return Invoke-RestMethod -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec
} else {
if ($Body -is [string]) {
return Invoke-RestMethod -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $Body
} else {
return Invoke-RestMethod -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 50 -Compress)
}
}
}
function Invoke-PluginApi {
param(
[string]$BaseUrl,
[string]$Path, # '/query' or '/health'
$Body # hashtable or string
)
$uri = "$BaseUrl$Path"
if ($Raw) {
$bodyStr = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 50 -Compress) }
return (Invoke-WebRequest -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $bodyStr).Content
}
if ($Body -is [string]) {
return Invoke-RestMethod -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $Body
} else {
return Invoke-RestMethod -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 50 -Compress)
}
}
#
# Main dispatch
#
$endpoint = Get-BridgeEndpoints
$mode = $endpoint.Mode
$base = $endpoint.BaseUrl
try {
switch ($Command) {
'health' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/health'
$out = Get-JsonOutput -Data $resp
Write-Output $out
} else {
$resp = Invoke-PluginApi -BaseUrl $base -Path '/health' -Body '{}'
$out = Get-JsonOutput -Data $resp
Write-Output $out
}
}
'projects' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/projects'
} else {
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body @{ queryType = 'getprojects' }
}
Write-Output (Get-JsonOutput -Data $resp)
}
'overview' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/solution/overview'
} else {
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body @{ queryType = 'getsolutionoverview' }
}
Write-Output (Get-JsonOutput -Data $resp)
}
'diagnostics' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{}
if ($FilePath) { $q.filePath = $FilePath }
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/diagnostics' -Query $q
} else {
$body = @{ queryType = 'getdiagnostics' }
if ($FilePath) { $body.filePath = $FilePath }
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
}
Write-Output (Get-JsonOutput -Data $resp)
}
'symbol' {
if (-not $FilePath -or -not $PSBoundParameters.ContainsKey('Line') -or -not $PSBoundParameters.ContainsKey('Column')) {
Write-Err 'symbol requires -FilePath, -Line, -Column'; exit 2
}
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{ filePath=$FilePath; line=$Line; column=$Column }
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/symbol' -Query $q
} else {
$body = @{ queryType='getsymbol'; filePath=$FilePath; line=$Line; column=$Column }
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
}
Write-Output (Get-JsonOutput -Data $resp)
}
'references' {
if (-not $FilePath -or -not $PSBoundParameters.ContainsKey('Line') -or -not $PSBoundParameters.ContainsKey('Column')) {
Write-Err 'references requires -FilePath, -Line, -Column'; exit 2
}
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{ filePath=$FilePath; line=$Line; column=$Column }
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/references' -Query $q
} else {
$body = @{ queryType='findreferences'; filePath=$FilePath; line=$Line; column=$Column }
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
}
Write-Output (Get-JsonOutput -Data $resp)
}
'search' {
if (-not $SymbolName) { Write-Err 'search requires -SymbolName'; exit 2 }
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{ symbolName=$SymbolName }
if ($Kind -and $Kind -ne 'any') { $q.kind = $Kind }
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/symbol/search' -Query $q
} else {
$parameters = @{}
if ($Kind -and $Kind -ne 'any') { $parameters.kind = $Kind }
$body = @{ queryType='findsymbol'; symbolName=$SymbolName }
if ($parameters.Count -gt 0) { $body.parameters = $parameters }
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
}
Write-Output (Get-JsonOutput -Data $resp)
}
'build' {
if (-not $ProjectName) { Write-Err 'build requires -ProjectName'; exit 2 }
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{ projectName=$ProjectName }
if ($Configuration) { $q.configuration = $Configuration }
$resp = Invoke-WebApi -BaseUrl $base -Method POST -Path '/api/roslyn/project/build' -Query $q
} else {
$body = @{ queryType='buildproject'; projectName=$ProjectName }
if ($Configuration) { $body.configuration = $Configuration }
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
}
Write-Output (Get-JsonOutput -Data $resp)
}
'package-add' {
if (-not $ProjectName -or -not $PackageName) { Write-Err 'package-add requires -ProjectName and -PackageName'; exit 2 }
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{ projectName=$ProjectName; packageName=$PackageName }
if ($Version) { $q.version = $Version }
$resp = Invoke-WebApi -BaseUrl $base -Method POST -Path '/api/roslyn/project/package/add' -Query $q
} else {
$body = @{ queryType='addnugetpackage'; projectName=$ProjectName; packageName=$PackageName }
if ($Version) { $body.version = $Version }
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
}
Write-Output (Get-JsonOutput -Data $resp)
}
'history' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history'
} else {
Write-Err 'history is only available via Web API (port 5001).'; exit 2
}
Write-Output (Get-JsonOutput -Data $resp)
}
'history-recent' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$q = @{ count = $Count }
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history/recent' -Query $q
} else {
Write-Err 'history-recent is only available via Web API (port 5001).'; exit 2
}
Write-Output (Get-JsonOutput -Data $resp)
}
'history-stats' {
if ($mode -eq 'web' -or $mode -eq 'explicit') {
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history/stats'
} else {
Write-Err 'history-stats is only available via Web API (port 5001).'; exit 2
}
Write-Output (Get-JsonOutput -Data $resp)
}
'plugin-health' {
$pluginBase = if ($BaseUrl) { $BaseUrl } else { 'http://localhost:59123' }
$resp = Invoke-PluginApi -BaseUrl $pluginBase -Path '/health' -Body '{}'
Write-Output (Get-JsonOutput -Data $resp)
}
'solution-overview' {
if ($mode -ne 'web' -and $mode -ne 'explicit') {
Write-Err 'solution-overview requires the Web API (port 5001).'; exit 2
}
$q = @{ topNProjects=$Top; includeNamespaces=$IncludeNamespaces; format=$Format }
# For text/yaml formats, we want the raw response as-is
if ($Format -eq 'json') {
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/solution/overview/summary' -Query $q
Write-Output (Get-JsonOutput -Data $resp)
} else {
# Build query string properly
$qs = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
$qs['topNProjects'] = [string]$Top
$qs['includeNamespaces'] = [string]$IncludeNamespaces
$qs['format'] = $Format
$uri = [System.UriBuilder]::new("$base/api/roslyn/solution/overview/summary")
$uri.Query = $qs.ToString()
$content = (Invoke-WebRequest -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec).Content
Write-Output $content
}
}
default {
Write-Err "Unknown command: $Command"; exit 2
}
}
} catch {
$msg = $_.Exception.Message
$respProp = $_.Exception.PSObject.Properties['Response']
if ($respProp -and $respProp.Value) {
try {
$resp = $respProp.Value
$stream = $resp.GetResponseStream()
if ($stream) {
$reader = New-Object System.IO.StreamReader($stream)
$content = $reader.ReadToEnd()
if ($content) { $msg = "$msg`n$content" }
}
} catch {}
}
Write-Err $msg
exit 1
}