Refactor run-aspire scripts for improved cross-platform support. Replace run-aspire.cmd with run-aspire.ps1 for Windows and add run-aspire.sh for Ubuntu. Update documentation to reflect these changes and ensure synchronization between build rules across scripts. Enhance Tailscale integration and clarify usage instructions.
This commit is contained in:
@@ -53,11 +53,13 @@ npm --prefix src/HSchool.Client run build
|
||||
dotnet run --project src/HSchool.AppHost
|
||||
```
|
||||
|
||||
`run-aspire.cmd` is the Windows entry point: it skips MSBuild when AppHost and Server dlls are
|
||||
newer than C# / csproj / props. Pass `--rebuild` to force a build. When `tailscale` is on PATH
|
||||
it also runs `tailscale serve --bg 5173` when `tailscale` is on PATH and connected; otherwise
|
||||
it warns and continues locally only. Pass `--no-tailscale` to skip Serve. Keep the script in sync
|
||||
with `tools/apphost-uptodate.ps1` if the AppHost path ever moves.
|
||||
`run-aspire.ps1` is the Windows entry point (`run-aspire.cmd` just forwards to it);
|
||||
`run-aspire.sh` is the same on Ubuntu. They skip MSBuild when AppHost and Server dlls are
|
||||
newer than C# / csproj / props. Pass `--rebuild` to force a build. When `tailscale` is on
|
||||
PATH and connected they run `tailscale serve --bg 5173` and print the HTTPS client URL;
|
||||
otherwise they warn and continue locally only. Pass `--no-tailscale` to skip Serve. Keep
|
||||
`tools/apphost-uptodate.ps1`, `LaunchBuildStamp`, and `needs_build` in `run-aspire.sh` in
|
||||
sync if the AppHost path ever moves.
|
||||
|
||||
`dotnet run --project src/HSchool.AppHost` still compiles every time — that is for a dirty tree,
|
||||
not a fast relaunch. It starts the server *and* the Vite dev server and opens the Aspire
|
||||
@@ -201,6 +203,6 @@ only resolved a status line. Solution-wide is CI.
|
||||
keep going out. Opening sends one extra snapshot. A test that waits for *people* on pause
|
||||
will hang; a test that waits for a lesson label will not.
|
||||
- **`dotnet run` on the AppHost always evaluates MSBuild**, even when nothing changed.
|
||||
`run-aspire.cmd` skips that with `--no-build` when AppHost and Server dlls are newer than
|
||||
C# / csproj / props. `--rebuild` forces a build. Bare `dotnet run --project src/HSchool.AppHost`
|
||||
still compiles.
|
||||
`run-aspire.ps1` / `run-aspire.sh` skip that with `--no-build` when AppHost and Server dlls
|
||||
are newer than C# / csproj / props. `--rebuild` forces a build. Bare
|
||||
`dotnet run --project src/HSchool.AppHost` still compiles.
|
||||
|
||||
@@ -30,10 +30,10 @@ school screen with a running game clock. The school itself is still empty.
|
||||
dotnet run --project src/HSchool.AppHost
|
||||
```
|
||||
|
||||
On Windows `run-aspire.cmd` does the same and can be double-clicked; it checks that the .NET SDK
|
||||
and Node are on PATH first and passes any arguments through
|
||||
(`run-aspire.cmd --launch-profile http`). A second launch with no C# changes skips MSBuild;
|
||||
`run-aspire.cmd --rebuild` forces a build.
|
||||
On Windows `run-aspire.ps1` does the same (`run-aspire.cmd` forwards to it and can be
|
||||
double-clicked). On Ubuntu `./run-aspire.sh`. Both check that the .NET SDK and Node are on
|
||||
PATH first and pass any arguments through (`--launch-profile http`). A second launch with no
|
||||
C# changes skips MSBuild; `--rebuild` forces a build.
|
||||
|
||||
The Aspire dashboard opens with two resources: `server` (ASP.NET Core) and `client` (Vite dev
|
||||
server on port **5173**).
|
||||
@@ -43,11 +43,12 @@ server on port **5173**).
|
||||
For a dev session over your tailnet, expose only the Vite client — not the game server (5180),
|
||||
Aspire dashboard (15180), or SwarmUI.
|
||||
|
||||
`run-aspire.cmd` runs `tailscale serve --bg 5173` before the AppHost when `tailscale` is on
|
||||
PATH and connected. If Tailscale is missing, offline, or `serve` fails, the script prints a
|
||||
warning and continues with local access only (`http://localhost:5173`). Pass `--no-tailscale`
|
||||
to skip Serve entirely. You can also run the command yourself once per boot if you start with
|
||||
`dotnet run --project src/HSchool.AppHost` instead.
|
||||
`run-aspire.ps1` / `run-aspire.sh` run `tailscale serve --bg 5173` before the AppHost when
|
||||
`tailscale` is on PATH and connected. If Tailscale is missing, offline, or `serve` fails, the script prints a
|
||||
warning and continues with local access only (`http://localhost:5173`). When Serve is up, it
|
||||
prints the HTTPS client URL next to the local one; the Aspire dashboard stays on localhost.
|
||||
Pass `--no-tailscale` to skip Serve entirely. You can also run the command yourself once per
|
||||
boot if you start with `dotnet run --project src/HSchool.AppHost` instead.
|
||||
|
||||
Friends on the same tailnet open `https://<your-machine>.<tailnet>.ts.net`. The page, REST API
|
||||
and game WebSocket all go through Vite’s proxy on one origin; Kestrel stays on localhost.
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
Клиентский TypeScript и JSONC модов не входы: Vite и каталог читают диск (`ContentRoot`), а не
|
||||
`bin`. Тестовые проекты AppHost не ссылается — их правки запуск не пересобирают.
|
||||
|
||||
Правила «устарел ли билд» живут в `tools/apphost-uptodate.ps1` (то, что вызывает `.cmd`) и в
|
||||
Правила «устарел ли билд» живут в `tools/apphost-uptodate.ps1` (то, что вызывает `run-aspire.ps1`) и в
|
||||
`LaunchBuildStamp` (то, что проверяют тесты). Менять надо оба.
|
||||
|
||||
### Что не входит
|
||||
|
||||
+4
-123
@@ -1,127 +1,8 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
rem Starts the whole app: game server, Vite client and the Aspire dashboard.
|
||||
rem Skips MSBuild when AppHost and Server dlls are newer than C# / csproj / props.
|
||||
rem run-aspire.cmd --launch-profile http
|
||||
rem run-aspire.cmd --rebuild
|
||||
rem run-aspire.cmd --no-tailscale skip `tailscale serve --bg 5173`
|
||||
|
||||
rem Trampoline for Explorer double-click and muscle memory. The launcher is run-aspire.ps1.
|
||||
cd /d "%~dp0"
|
||||
|
||||
where dotnet >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] dotnet SDK not found in PATH.
|
||||
echo Install .NET 10: https://dotnet.microsoft.com/download
|
||||
call :maybe_pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
where node >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] Node.js not found in PATH - the client resource will fail to start.
|
||||
echo Install Node 22.12 or newer: https://nodejs.org
|
||||
echo.
|
||||
)
|
||||
|
||||
set "CONFIG=Debug"
|
||||
set "REBUILD=0"
|
||||
set "TAILSCALE_SERVE=1"
|
||||
set "ARGS="
|
||||
|
||||
:parse
|
||||
if "%~1"=="" goto parsed
|
||||
if /I "%~1"=="--rebuild" (
|
||||
set "REBUILD=1"
|
||||
shift
|
||||
goto parse
|
||||
)
|
||||
if /I "%~1"=="--no-tailscale" (
|
||||
set "TAILSCALE_SERVE=0"
|
||||
shift
|
||||
goto parse
|
||||
)
|
||||
if /I "%~1"=="-c" if not "%~2"=="" set "CONFIG=%~2"
|
||||
if /I "%~1"=="--configuration" if not "%~2"=="" set "CONFIG=%~2"
|
||||
set "ARGS=!ARGS! %1"
|
||||
shift
|
||||
goto parse
|
||||
|
||||
:parsed
|
||||
|
||||
if "%REBUILD%"=="1" (
|
||||
echo [run-aspire] --rebuild: building.
|
||||
goto build
|
||||
)
|
||||
|
||||
where powershell >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] PowerShell not found, building.
|
||||
goto build
|
||||
)
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0tools\apphost-uptodate.ps1" -RepoRoot "%CD%" -Configuration "%CONFIG%"
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] sources changed or binaries missing, building.
|
||||
goto build
|
||||
)
|
||||
|
||||
echo [run-aspire] binaries are up to date, skipping build.
|
||||
goto run
|
||||
|
||||
:build
|
||||
echo.
|
||||
dotnet build "src\HSchool.AppHost\HSchool.AppHost.csproj" -c "%CONFIG%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo [run-aspire] build failed.
|
||||
call :maybe_pause
|
||||
exit /b 1
|
||||
)
|
||||
echo.
|
||||
|
||||
:run
|
||||
if "%TAILSCALE_SERVE%"=="1" (
|
||||
where tailscale >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] WARNING: tailscale not found in PATH.
|
||||
echo Friends cannot reach the client over the tailnet.
|
||||
echo Continuing with local access only ^(http://localhost:5173^).
|
||||
) else (
|
||||
tailscale status >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] WARNING: tailscale is not connected or not running.
|
||||
echo Friends cannot reach the client over the tailnet.
|
||||
echo Continuing with local access only ^(http://localhost:5173^).
|
||||
) else (
|
||||
echo [run-aspire] tailscale serve --bg 5173
|
||||
tailscale serve --bg 5173
|
||||
if errorlevel 1 (
|
||||
echo [run-aspire] WARNING: tailscale serve failed.
|
||||
echo Friends cannot reach the client over the tailnet.
|
||||
echo Continuing with local access only ^(http://localhost:5173^).
|
||||
)
|
||||
)
|
||||
)
|
||||
echo.
|
||||
)
|
||||
|
||||
echo [run-aspire] Starting the Aspire AppHost. Press Ctrl+C to shut everything down.
|
||||
echo.
|
||||
|
||||
dotnet run --project "src\HSchool.AppHost\HSchool.AppHost.csproj" --no-build --no-restore !ARGS!
|
||||
set "EXITCODE=!ERRORLEVEL!"
|
||||
|
||||
if not "!EXITCODE!"=="0" (
|
||||
echo.
|
||||
echo [run-aspire] AppHost exited with code !EXITCODE!.
|
||||
)
|
||||
|
||||
call :maybe_pause
|
||||
exit /b !EXITCODE!
|
||||
|
||||
rem Keeps the window open when the file was double-clicked from Explorer.
|
||||
:maybe_pause
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0run-aspire.ps1" %*
|
||||
set "EXITCODE=%ERRORLEVEL%"
|
||||
echo %cmdcmdline% | find /i "%~nx0" >nul
|
||||
if not errorlevel 1 pause
|
||||
exit /b 0
|
||||
exit /b %EXITCODE%
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
# Starts the whole app: game server, Vite client and the Aspire dashboard.
|
||||
# Skips MSBuild when AppHost and Server dlls are newer than C# / csproj / props.
|
||||
# .\run-aspire.ps1
|
||||
# .\run-aspire.ps1 --launch-profile http
|
||||
# .\run-aspire.ps1 --rebuild
|
||||
# .\run-aspire.ps1 --no-tailscale
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $false
|
||||
|
||||
Set-Location -LiteralPath $PSScriptRoot
|
||||
|
||||
function Write-Log([string] $Message) {
|
||||
Write-Host "[run-aspire] $Message"
|
||||
}
|
||||
|
||||
function Write-TailscaleSkipped([string] $Why) {
|
||||
Write-Log "WARNING: $Why"
|
||||
Write-Host ' Friends cannot reach the client over the tailnet.'
|
||||
Write-Host ' Continuing with local access only (http://localhost:5173).'
|
||||
}
|
||||
|
||||
$configuration = 'Debug'
|
||||
$rebuild = $false
|
||||
$tailscaleServe = $true
|
||||
$dotnetArgs = @()
|
||||
$expectConfiguration = $false
|
||||
|
||||
foreach ($arg in $args) {
|
||||
if ($expectConfiguration) {
|
||||
$configuration = $arg
|
||||
$expectConfiguration = $false
|
||||
continue
|
||||
}
|
||||
|
||||
switch ($arg) {
|
||||
'--rebuild' { $rebuild = $true }
|
||||
'--no-tailscale' { $tailscaleServe = $false }
|
||||
{ $_ -in '-c', '--configuration' } { $expectConfiguration = $true }
|
||||
default { $dotnetArgs += $arg }
|
||||
}
|
||||
}
|
||||
|
||||
if ($expectConfiguration) {
|
||||
Write-Log 'missing value for -c / --configuration.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) {
|
||||
Write-Log 'dotnet SDK not found in PATH.'
|
||||
Write-Host ' Install .NET 10: https://dotnet.microsoft.com/download'
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
||||
Write-Log 'Node.js not found in PATH - the client resource will fail to start.'
|
||||
Write-Host ' Install Node 22.12 or newer: https://nodejs.org'
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
if ($rebuild) {
|
||||
Write-Log '--rebuild: building.'
|
||||
$needBuild = $true
|
||||
} else {
|
||||
$stamp = Join-Path $PSScriptRoot 'tools\apphost-uptodate.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $stamp -RepoRoot $PSScriptRoot -Configuration $configuration
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log 'binaries are up to date, skipping build.'
|
||||
$needBuild = $false
|
||||
} else {
|
||||
Write-Log 'sources changed or binaries missing, building.'
|
||||
$needBuild = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($needBuild) {
|
||||
Write-Host ''
|
||||
& dotnet build (Join-Path $PSScriptRoot 'src\HSchool.AppHost\HSchool.AppHost.csproj') -c $configuration
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ''
|
||||
Write-Log 'build failed.'
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
$clientTailscaleUrl = $null
|
||||
if ($tailscaleServe) {
|
||||
if (-not (Get-Command tailscale -ErrorAction SilentlyContinue)) {
|
||||
Write-TailscaleSkipped 'tailscale not found in PATH.'
|
||||
} else {
|
||||
& tailscale status 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-TailscaleSkipped 'tailscale is not connected or not running.'
|
||||
} else {
|
||||
Write-Log 'tailscale serve --bg 5173'
|
||||
& tailscale serve --bg 5173
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-TailscaleSkipped 'tailscale serve failed.'
|
||||
} else {
|
||||
$line = & tailscale serve status 2>$null | Select-Object -First 1
|
||||
$token = if ($line) { ($line -split '\s+', 2)[0] } else { $null }
|
||||
if ($token -like 'https://*') {
|
||||
$clientTailscaleUrl = $token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
Write-Log 'Starting the Aspire AppHost. Press Ctrl+C to shut everything down.'
|
||||
Write-Log 'Client: http://localhost:5173'
|
||||
if ($clientTailscaleUrl) {
|
||||
Write-Log "Client (Tailscale): $clientTailscaleUrl"
|
||||
}
|
||||
Write-Host ''
|
||||
|
||||
$run = @(
|
||||
'--project', (Join-Path $PSScriptRoot 'src\HSchool.AppHost\HSchool.AppHost.csproj')
|
||||
'--no-build'
|
||||
'--no-restore'
|
||||
) + $dotnetArgs
|
||||
& dotnet run @run
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0) {
|
||||
Write-Host ''
|
||||
Write-Log "AppHost exited with code $exitCode."
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# Starts the whole app: game server, Vite client and the Aspire dashboard.
|
||||
# Skips MSBuild when AppHost and Server dlls are newer than C# / csproj / props.
|
||||
# ./run-aspire.sh
|
||||
# ./run-aspire.sh --launch-profile http
|
||||
# ./run-aspire.sh --rebuild
|
||||
# ./run-aspire.sh --no-tailscale
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
log() {
|
||||
echo "[run-aspire] $*"
|
||||
}
|
||||
|
||||
skip_tailscale() {
|
||||
log "WARNING: $*"
|
||||
echo " Friends cannot reach the client over the tailnet."
|
||||
echo " Continuing with local access only (http://localhost:5173)."
|
||||
}
|
||||
|
||||
# Same rules as tools/apphost-uptodate.ps1 and LaunchBuildStamp.cs.
|
||||
needs_build() {
|
||||
local tfm=net10.0
|
||||
local apphost="src/HSchool.AppHost/bin/${configuration}/${tfm}/HSchool.AppHost.dll"
|
||||
local server="src/HSchool.Server/bin/${configuration}/${tfm}/HSchool.Server.dll"
|
||||
[[ -f "$apphost" && -f "$server" ]] || return 0
|
||||
|
||||
local oldest=$apphost
|
||||
[[ "$server" -ot "$apphost" ]] && oldest=$server
|
||||
|
||||
local name
|
||||
for name in global.json Directory.Build.props Directory.Packages.props; do
|
||||
[[ -f "$name" && "$name" -nt "$oldest" ]] && return 0
|
||||
done
|
||||
|
||||
[[ -d src ]] || return 0
|
||||
|
||||
local found
|
||||
found=$(find src \( -name bin -o -name obj -o -name node_modules \) -prune -o \
|
||||
-type f \( -name '*.cs' -o -name '*.csproj' -o -name '*.props' \) -newer "$oldest" -print -quit)
|
||||
[[ -n "$found" ]]
|
||||
}
|
||||
|
||||
configuration=Debug
|
||||
rebuild=0
|
||||
tailscale_serve=1
|
||||
dotnet_args=()
|
||||
expect_configuration=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if [[ $expect_configuration -eq 1 ]]; then
|
||||
configuration=$1
|
||||
expect_configuration=0
|
||||
shift
|
||||
continue
|
||||
fi
|
||||
case $1 in
|
||||
--rebuild) rebuild=1 ;;
|
||||
--no-tailscale) tailscale_serve=0 ;;
|
||||
-c|--configuration) expect_configuration=1 ;;
|
||||
*) dotnet_args+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $expect_configuration -eq 1 ]]; then
|
||||
log "missing value for -c / --configuration."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v dotnet >/dev/null; then
|
||||
log "dotnet SDK not found in PATH."
|
||||
echo " Install .NET 10: https://dotnet.microsoft.com/download"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null; then
|
||||
log "Node.js not found in PATH - the client resource will fail to start."
|
||||
echo " Install Node 22.12 or newer: https://nodejs.org"
|
||||
echo
|
||||
fi
|
||||
|
||||
need_build=0
|
||||
if [[ $rebuild -eq 1 ]]; then
|
||||
log "--rebuild: building."
|
||||
need_build=1
|
||||
elif needs_build; then
|
||||
log "sources changed or binaries missing, building."
|
||||
need_build=1
|
||||
else
|
||||
log "binaries are up to date, skipping build."
|
||||
fi
|
||||
|
||||
if [[ $need_build -eq 1 ]]; then
|
||||
echo
|
||||
if ! dotnet build src/HSchool.AppHost/HSchool.AppHost.csproj -c "$configuration"; then
|
||||
echo
|
||||
log "build failed."
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
|
||||
client_tailscale_url=
|
||||
if [[ $tailscale_serve -eq 1 ]]; then
|
||||
if ! command -v tailscale >/dev/null; then
|
||||
skip_tailscale "tailscale not found in PATH."
|
||||
elif ! tailscale status >/dev/null 2>&1; then
|
||||
skip_tailscale "tailscale is not connected or not running."
|
||||
else
|
||||
log "tailscale serve --bg 5173"
|
||||
if ! tailscale serve --bg 5173; then
|
||||
skip_tailscale "tailscale serve failed."
|
||||
else
|
||||
line=$(tailscale serve status 2>/dev/null | head -n 1 || true)
|
||||
token=${line%% *}
|
||||
case $token in
|
||||
https://*) client_tailscale_url=$token ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
|
||||
log "Starting the Aspire AppHost. Press Ctrl+C to shut everything down."
|
||||
log "Client: http://localhost:5173"
|
||||
if [[ -n "$client_tailscale_url" ]]; then
|
||||
log "Client (Tailscale): $client_tailscale_url"
|
||||
fi
|
||||
echo
|
||||
|
||||
set +e
|
||||
dotnet run --project src/HSchool.AppHost/HSchool.AppHost.csproj --no-build --no-restore "${dotnet_args[@]}"
|
||||
exit_code=$?
|
||||
set -e
|
||||
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
echo
|
||||
log "AppHost exited with code ${exit_code}."
|
||||
fi
|
||||
|
||||
exit "$exit_code"
|
||||
@@ -1,7 +1,8 @@
|
||||
namespace HSchool.AppHost.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Same rules as <c>tools/apphost-uptodate.ps1</c>. Keep the two in sync.
|
||||
/// Same rules as <c>tools/apphost-uptodate.ps1</c> and <c>needs_build</c> in
|
||||
/// <c>run-aspire.sh</c>. Keep the three in sync.
|
||||
/// </summary>
|
||||
internal static class LaunchBuildStamp
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Same rules as tests/HSchool.AppHost.Tests/LaunchBuildStamp.cs. Keep the two in sync.
|
||||
# Exit 0: AppHost and Server dlls are newer than inputs. Exit 1: need a build.
|
||||
# Same rules as tests/HSchool.AppHost.Tests/LaunchBuildStamp.cs and needs_build in run-aspire.sh.
|
||||
# Keep the three in sync. Exit 0: AppHost and Server dlls are newer than inputs. Exit 1: need a build.
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $RepoRoot,
|
||||
|
||||
Reference in New Issue
Block a user