Add MCP Apps UI support to Repl.Mcp #117
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main, 'release/**'] | |
| permissions: | |
| contents: write | |
| checks: write | |
| env: | |
| DOTNET_NOLOGO: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | |
| jobs: | |
| lint-docs: | |
| name: Lint Documentation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Run markdownlint | |
| uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v19.1.0 | |
| with: | |
| globs: '**/*.md' | |
| build-test-cross-platform: | |
| name: Build and Test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: ga | |
| - name: Restore | |
| run: dotnet restore src/Repl.slnx --force | |
| - name: Build | |
| run: dotnet build src/Repl.slnx -c Release -warnaserror --no-restore | |
| - name: Test | |
| run: >- | |
| dotnet test --solution src/Repl.slnx | |
| -c Release | |
| --no-build | |
| --no-restore | |
| shell-completion-real-shells: | |
| name: Shell Completion Smoke (Real Shells) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: ga | |
| - name: Install shell dependencies | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y fish zsh jq | |
| if ! command -v pwsh >/dev/null 2>&1; then | |
| sudo apt-get install -y powershell | |
| fi | |
| if ! command -v nu >/dev/null 2>&1; then | |
| release_json="$(curl -fsSL https://api.github.com/repos/nushell/nushell/releases/latest)" | |
| nu_url="$(printf '%s' "$release_json" | jq -r '.assets[] | select(.name | endswith("x86_64-unknown-linux-gnu.tar.gz")) | .browser_download_url' | head -n 1)" | |
| if [[ -z "$nu_url" || "$nu_url" == "null" ]]; then | |
| echo "Failed to resolve a Nushell Linux release asset URL." >&2 | |
| exit 1 | |
| fi | |
| tmp_dir="$(mktemp -d)" | |
| trap 'rm -rf "$tmp_dir"' EXIT | |
| curl -fsSL "$nu_url" -o "$tmp_dir/nu.tar.gz" | |
| tar -xzf "$tmp_dir/nu.tar.gz" -C "$tmp_dir" | |
| nu_binary="$(find "$tmp_dir" -type f -name nu | head -n 1)" | |
| if [[ -z "$nu_binary" ]]; then | |
| echo "Failed to locate extracted nushell binary." >&2 | |
| exit 1 | |
| fi | |
| sudo install -m 0755 "$nu_binary" /usr/local/bin/nu | |
| fi | |
| bash --version | head -n 1 | |
| zsh --version | |
| fish --version | |
| pwsh -NoLogo -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' | |
| nu --version | |
| - name: Restore | |
| run: dotnet restore src/Repl.slnx --force | |
| - name: Build shell completion test host | |
| run: >- | |
| dotnet build src/Repl.ShellCompletionTestHost/Repl.ShellCompletionTestHost.csproj | |
| -c Release | |
| -warnaserror | |
| --no-restore | |
| - name: Shell completion smoke checks | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bash ./eng/ci/shell-completion-real-shell-smoke.sh ./src/Repl.ShellCompletionTestHost/bin/Release/net10.0/Repl.ShellCompletionTestHost | |
| build-test-pack: | |
| name: Build, Test, Pack | |
| runs-on: ubuntu-latest | |
| env: | |
| PUBLIC_RELEASE: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) }} | |
| outputs: | |
| version: ${{ steps.resolve-version.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| fetch-depth: 0 # Nerdbank.GitVersioning needs full history | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: ga | |
| - name: Restore | |
| run: dotnet restore src/Repl.slnx --force | |
| - name: Build | |
| shell: bash | |
| run: | | |
| if [[ "${PUBLIC_RELEASE}" == "true" ]]; then | |
| dotnet build src/Repl.slnx -c Release -warnaserror --no-restore -p:PublicRelease=true | |
| else | |
| dotnet build src/Repl.slnx -c Release -warnaserror --no-restore | |
| fi | |
| - name: Test | |
| shell: bash | |
| run: | | |
| dotnet test --solution src/Repl.slnx \ | |
| -c Release \ | |
| --no-build \ | |
| --no-restore \ | |
| --results-directory TestResults \ | |
| -- \ | |
| --report-trx \ | |
| --coverage \ | |
| --coverage-output-format cobertura | |
| - name: Test report | |
| if: always() | |
| uses: dorny/test-reporter@v1 | |
| with: | |
| name: Test Results | |
| path: 'TestResults/**/*.trx' | |
| reporter: dotnet-trx | |
| - name: Coverage summary | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $coberturaFiles = Get-ChildItem -Path TestResults -Filter '*.cobertura.xml' -Recurse -ErrorAction SilentlyContinue | |
| if (-not $coberturaFiles -or $coberturaFiles.Count -eq 0) { | |
| Write-Host 'No Cobertura coverage files found — skipping summary.' | |
| exit 0 | |
| } | |
| foreach ($file in $coberturaFiles) { | |
| [xml]$xml = Get-Content $file.FullName | |
| $lineRate = [math]::Round([double]$xml.coverage.'line-rate' * 100, 1) | |
| $branchRate = [math]::Round([double]$xml.coverage.'branch-rate' * 100, 1) | |
| "## Code Coverage`n" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "| Metric | Value |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "|--------|-------|" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "| Line coverage | ${lineRate}% |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "| Branch coverage | ${branchRate}% |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| Write-Host "Line coverage: ${lineRate}%, Branch coverage: ${branchRate}%" | |
| } | |
| - name: Pack | |
| shell: pwsh | |
| run: | | |
| if ($env:PUBLIC_RELEASE -eq 'true') { | |
| dotnet pack src/Repl.slnx -c Release --no-restore -p:PublicRelease=true -p:WarnOnPackingNonPackableProject=false -o '${{ runner.temp }}/packages' | |
| } | |
| else { | |
| dotnet pack src/Repl.slnx -c Release --no-restore -p:WarnOnPackingNonPackableProject=false -o '${{ runner.temp }}/packages' | |
| } | |
| - name: Package readiness report (non-blocking) | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| Add-Type -AssemblyName System.IO.Compression | |
| Add-Type -AssemblyName System.IO.Compression.FileSystem | |
| $packagesDir = '${{ runner.temp }}/packages' | |
| $repoUrl = 'https://github.com/yllibed/repl' | |
| $issues = New-Object System.Collections.Generic.List[string] | |
| $symbolChecksRequired = New-Object System.Collections.Generic.HashSet[string]([System.StringComparer]::OrdinalIgnoreCase) | |
| function Get-ZipEntryText([System.IO.Compression.ZipArchive]$zip, [string]$entryName) { | |
| $entry = $zip.Entries | Where-Object { $_.FullName -eq $entryName } | Select-Object -First 1 | |
| if (-not $entry) { return $null } | |
| $reader = [System.IO.StreamReader]::new($entry.Open()) | |
| try { return $reader.ReadToEnd() } finally { $reader.Dispose() } | |
| } | |
| $nupkgs = @(Get-ChildItem -Path $packagesDir -Filter '*.nupkg' -File | | |
| Where-Object { $_.Name -notlike '*.symbols.nupkg' }) | |
| $snupkgs = @(Get-ChildItem -Path $packagesDir -Filter '*.snupkg' -File) | |
| if ($nupkgs.Count -eq 0) { | |
| $issues.Add('No .nupkg files produced by pack.') | |
| } | |
| foreach ($nupkg in $nupkgs) { | |
| $baseName = [System.IO.Path]::GetFileNameWithoutExtension($nupkg.Name) | |
| $zip = [System.IO.Compression.ZipFile]::OpenRead($nupkg.FullName) | |
| try { | |
| $containsBuildOutput = @($zip.Entries | Where-Object { $_.FullName -like 'lib/*.dll' -or $_.FullName -like 'lib/*/*.dll' }).Count -gt 0 | |
| if ($containsBuildOutput) { | |
| [void]$symbolChecksRequired.Add($baseName) | |
| $expectedSnupkg = Join-Path $packagesDir ($baseName + '.snupkg') | |
| if (-not (Test-Path $expectedSnupkg)) { | |
| $issues.Add("Missing .snupkg for package '$($nupkg.Name)'.") | |
| } | |
| } | |
| $nuspecEntry = $zip.Entries | Where-Object { $_.FullName -like '*.nuspec' } | Select-Object -First 1 | |
| if (-not $nuspecEntry) { | |
| $issues.Add("Missing .nuspec in '$($nupkg.Name)'.") | |
| continue | |
| } | |
| $nuspecText = Get-ZipEntryText -zip $zip -entryName $nuspecEntry.FullName | |
| [xml]$nuspec = $nuspecText | |
| $metadata = $nuspec.package.metadata | |
| $repository = $metadata.repository | |
| $readme = [string]$metadata.readme | |
| if (-not $repository) { | |
| $issues.Add("Missing <repository> metadata in '$($nupkg.Name)'.") | |
| } | |
| else { | |
| if ([string]$repository.type -ne 'git') { | |
| $issues.Add("Repository type is not 'git' in '$($nupkg.Name)'.") | |
| } | |
| if ([string]$repository.url -ne $repoUrl) { | |
| $issues.Add("Repository URL mismatch in '$($nupkg.Name)'. Expected '$repoUrl'.") | |
| } | |
| if ([string]::IsNullOrWhiteSpace([string]$repository.commit)) { | |
| $issues.Add("Missing repository commit metadata in '$($nupkg.Name)'.") | |
| } | |
| } | |
| if ([string]::IsNullOrWhiteSpace($readme)) { | |
| $issues.Add("Missing <readme> metadata in '$($nupkg.Name)'.") | |
| } | |
| elseif (-not ($zip.Entries | Where-Object { $_.FullName -eq $readme } | Select-Object -First 1)) { | |
| $issues.Add("Readme '$readme' not found in '$($nupkg.Name)'.") | |
| } | |
| } | |
| finally { | |
| $zip.Dispose() | |
| } | |
| } | |
| foreach ($snupkg in $snupkgs) { | |
| $snupkgBaseName = [System.IO.Path]::GetFileNameWithoutExtension($snupkg.Name) | |
| if (-not $symbolChecksRequired.Contains($snupkgBaseName)) { | |
| continue | |
| } | |
| $zip = [System.IO.Compression.ZipFile]::OpenRead($snupkg.FullName) | |
| try { | |
| $pdbEntries = @($zip.Entries | Where-Object { $_.FullName -like '*.pdb' }) | |
| if ($pdbEntries.Count -eq 0) { | |
| $issues.Add("No .pdb entries found in '$($snupkg.Name)'.") | |
| continue | |
| } | |
| $foundSourceLinkHint = $false | |
| foreach ($pdb in $pdbEntries) { | |
| $stream = $pdb.Open() | |
| try { | |
| $ms = New-Object System.IO.MemoryStream | |
| $stream.CopyTo($ms) | |
| $bytes = $ms.ToArray() | |
| $text = [System.Text.Encoding]::ASCII.GetString($bytes) | |
| if ($text.Contains('raw.githubusercontent.com/yllibed/repl') -or | |
| $text.Contains('github.com/yllibed/repl')) { | |
| $foundSourceLinkHint = $true | |
| break | |
| } | |
| } | |
| finally { | |
| $stream.Dispose() | |
| } | |
| } | |
| if (-not $foundSourceLinkHint) { | |
| $issues.Add("Could not find GitHub Source Link hint in '$($snupkg.Name)' PDB payload.") | |
| } | |
| } | |
| finally { | |
| $zip.Dispose() | |
| } | |
| } | |
| "## Package Readiness Report`n" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "| Check | Result |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "|---|---|" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "| Generated nupkg | $($nupkgs.Count) |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "| Generated snupkg | $($snupkgs.Count) |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| if ($issues.Count -eq 0) { | |
| "| Readiness status | PASS |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| Write-Host 'Package readiness checks passed.' | |
| } | |
| else { | |
| "| Readiness status | WARN ($($issues.Count) issue(s)) |" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| "`n### Warnings`n" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| foreach ($issue in $issues) { | |
| "- $issue" | Out-File -Append $env:GITHUB_STEP_SUMMARY | |
| Write-Warning $issue | |
| } | |
| } | |
| - name: Validate Repl meta-package | |
| shell: pwsh | |
| run: | | |
| Add-Type -AssemblyName System.IO.Compression | |
| Add-Type -AssemblyName System.IO.Compression.FileSystem | |
| $packagesDir = '${{ runner.temp }}/packages' | |
| $metaPackage = Get-ChildItem -Path $packagesDir -Filter 'Repl.*.nupkg' | | |
| Where-Object { $_.Name -match '^Repl\.\d' } | | |
| Select-Object -First 1 | |
| if (-not $metaPackage) { | |
| throw "Meta-package Repl.*.nupkg not found in $packagesDir" | |
| } | |
| $zip = [System.IO.Compression.ZipFile]::OpenRead($metaPackage.FullName) | |
| try { | |
| $entries = $zip.Entries | ForEach-Object { $_.FullName } | |
| if ($entries -match '^lib/') { | |
| throw "Meta-package '$($metaPackage.Name)' must not contain lib/ assets." | |
| } | |
| $nuspecEntry = $zip.Entries | Where-Object { $_.FullName -like '*.nuspec' } | Select-Object -First 1 | |
| if (-not $nuspecEntry) { | |
| throw "No nuspec found in '$($metaPackage.Name)'." | |
| } | |
| $reader = [System.IO.StreamReader]::new($nuspecEntry.Open()) | |
| try { | |
| $nuspecText = $reader.ReadToEnd() | |
| } | |
| finally { | |
| $reader.Dispose() | |
| } | |
| [xml]$nuspec = $nuspecText | |
| $dependencyNodes = @($nuspec.package.metadata.dependencies.group.dependency) | |
| $dependencyIds = $dependencyNodes | ForEach-Object { $_.id } | |
| foreach ($required in @('Repl.Core', 'Repl.Defaults', 'Repl.Protocol')) { | |
| if ($dependencyIds -notcontains $required) { | |
| throw "Meta-package '$($metaPackage.Name)' is missing dependency '$required'." | |
| } | |
| } | |
| } | |
| finally { | |
| $zip.Dispose() | |
| } | |
| - name: Resolve version | |
| id: resolve-version | |
| shell: pwsh | |
| run: | | |
| $toolPath = Join-Path $env:RUNNER_TEMP 'dotnet-tools' | |
| dotnet tool install nbgv --tool-path $toolPath | |
| $nbgvExe = if ($IsWindows) { 'nbgv.exe' } else { 'nbgv' } | |
| $nbgv = Join-Path $toolPath $nbgvExe | |
| if ($env:PUBLIC_RELEASE -eq 'true') { | |
| $version = & $nbgv get-version -v NuGetPackageVersion --public-release=true | |
| } | |
| else { | |
| $version = & $nbgv get-version -v NuGetPackageVersion | |
| } | |
| "version=$version" >> $env:GITHUB_OUTPUT | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: test-results | |
| path: TestResults/** | |
| retention-days: 14 | |
| - name: Upload packages | |
| if: success() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: packages | |
| path: | | |
| ${{ runner.temp }}/packages/*.nupkg | |
| ${{ runner.temp }}/packages/*.snupkg | |
| retention-days: 14 | |
| release: | |
| name: GitHub Release | |
| needs: [build-test-pack, lint-docs] | |
| if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Download packages | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| name: packages | |
| path: packages | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.build-test-pack.outputs.version }}" | |
| PRERELEASE="" | |
| [[ "$VERSION" == *-* ]] && PRERELEASE="--prerelease" | |
| gh release create "v${VERSION}" packages/* \ | |
| --title "v${VERSION}" \ | |
| --generate-notes \ | |
| $PRERELEASE | |
| - name: Publish to NuGet | |
| if: success() | |
| shell: pwsh | |
| env: | |
| NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} | |
| run: | | |
| $source = 'https://api.nuget.org/v3/index.json' | |
| $apiKey = $env:NUGET_API_KEY | |
| $failed = @() | |
| foreach ($pkg in Get-ChildItem packages/*.nupkg) { | |
| Write-Host "Pushing $($pkg.Name)..." | |
| dotnet nuget push $pkg.FullName --api-key $apiKey --source $source --skip-duplicate 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warning "Failed to push $($pkg.Name) (exit code $LASTEXITCODE) — continuing with remaining packages." | |
| $failed += $pkg.Name | |
| } | |
| } | |
| foreach ($pkg in Get-ChildItem packages/*.snupkg -ErrorAction SilentlyContinue) { | |
| Write-Host "Pushing $($pkg.Name)..." | |
| dotnet nuget push $pkg.FullName --api-key $apiKey --source $source --skip-duplicate 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warning "Failed to push $($pkg.Name) (exit code $LASTEXITCODE) — continuing with remaining packages." | |
| $failed += $pkg.Name | |
| } | |
| } | |
| $nupkgCount = @(Get-ChildItem packages/*.nupkg).Count | |
| $failedNupkgCount = @($failed | Where-Object { $_ -like '*.nupkg' }).Count | |
| $succeeded = $nupkgCount - $failedNupkgCount | |
| if ($failed.Count -gt 0) { | |
| Write-Warning "The following packages failed to publish: $($failed -join ', ')" | |
| Write-Warning "This may be expected for new packages awaiting NuGet validation." | |
| } | |
| if ($succeeded -eq 0 -and $nupkgCount -gt 0) { | |
| throw "All $nupkgCount packages failed to publish. Check API key and NuGet source." | |
| } |