diff --git a/AvParser.slnx b/AvParser.slnx index 9361aed..a7b276c 100644 --- a/AvParser.slnx +++ b/AvParser.slnx @@ -1,5 +1,9 @@ + + + + diff --git a/CLAUDE.md b/CLAUDE.md index 0114254..b5e095b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,14 @@ ## Команды +Полный локальный гейт (формат → сборка → тесты) — то, что нужно прогнать перед коммитом: + +```bash +./build.ps1 +``` + +Отдельные шаги, если нужен только один из них: + ```bash dotnet build AvParser.slnx -c Release ``` @@ -14,9 +22,11 @@ dotnet test AvParser.slnx -c Release dotnet csharpier check . ``` ```bash -dotnet run --project src/AvParser.Desktop +./run.ps1 ``` +На Linux/macOS — `./build.sh` и `./run.sh` с теми же шагами. + ## Слои `Core ← Infrastructure ← UI ← Desktop`, строго в одну сторону. @@ -95,6 +105,14 @@ dotnet run --project src/AvParser.Desktop открепленного контрола не строится визуальное дерево. - **csharpier — единственный владелец форматирования** (включая `.axaml` и `.csproj`). `IDE0055` понижен до suggestion: два форматтера с `TreatWarningsAsErrors` дерутся насмерть. +- **`Condition="'$(Configuration)' == 'Debug'"` на `PackageReference` — ловушка.** + `dotnet restore` вычисляется с конфигурацией по умолчанию, поэтому пакет обязан + резолвиться, даже если эту конфигурацию никто не собирает. Так тут проехал мёртвый + `Avalonia.Diagnostics` (его нет под Avalonia 12): `dotnet build -c Release` работал, + а голый `dotnet restore` падал. +- **Инспектора в Avalonia 12 нет из коробки.** `Avalonia.Diagnostics` закончился на 11.3.x; + DevTools живут отдельно (`AvaloniaUI.DiagnosticsSupport` + `.WithDeveloperTools()`), со своей + установкой. Зависимость намеренно не добавлена. ## Качество diff --git a/Directory.Packages.props b/Directory.Packages.props index 5fb1189..743dc8e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,6 @@ - diff --git a/README.md b/README.md index e4472ab..9f86207 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,31 @@ end-to-end до появления настоящей логики. Нужен .NET SDK **10.0.100** (закреплён в `global.json`). +Полный локальный гейт — восстановление, проверка форматирования, сборка, тесты: + ```bash -dotnet restore AvParser.slnx +./build.ps1 ``` + +Запуск приложения (по умолчанию Debug): + +```bash +./run.ps1 +``` + +На Linux и macOS — `./build.sh` и `./run.sh`, аргументы те же. + +Полезные флаги: + +```bash +./build.ps1 -Fix -Configuration Debug +``` + +`-Fix` переформатирует код вместо того, чтобы падать на непрошедшей проверке; `-SkipTests` +собирает без прогона тестов. То же в bash: `--fix`, `-c Debug`, `--skip-tests`. + +Если нужны отдельные шаги, скрипты ничего не прячут: + ```bash dotnet build AvParser.slnx -c Release ``` @@ -23,13 +45,7 @@ dotnet build AvParser.slnx -c Release dotnet test AvParser.slnx -c Release ``` ```bash -dotnet run --project src/AvParser.Desktop -``` - -Форматирование (csharpier — единственный владелец форматирования, включая `.axaml` и `.csproj`): - -```bash -dotnet tool restore && dotnet csharpier check . +dotnet csharpier check . ``` --- @@ -117,7 +133,9 @@ Semi.Avalonia даёт темы контролов; токены — это се 2. Переключить тему кнопкой в заголовке и в Settings; перезапустить — выбор сохранился. 3. На странице Parse нажать **50k rows**, затем **Parse** — виден прогресс; **Cancel** останавливает на середине и пишет, сколько успело разобраться. -4. `F12` в Debug-сборке открывает Avalonia DevTools — там видно, как переключаются - `:compact` / `:medium` / `:expanded`. +В Avalonia 12 инспектора «из коробки» больше нет: `Avalonia.Diagnostics` остановился на 11.3.x, +а DevTools вынесли в отдельный инструмент со своей установкой +(`AvaloniaUI.DiagnosticsSupport` + `.WithDeveloperTools()`). Поэтому `F12` здесь ничего не +открывает — зависимость намеренно не добавлена. Настройки и логи лежат в `%APPDATA%/AvParser` (Windows) или `~/.config/AvParser` (Linux/macOS). diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..7a40c5d --- /dev/null +++ b/build.ps1 @@ -0,0 +1,97 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Полный локальный гейт: формат, сборка, тесты. + +.DESCRIPTION + То, что имеет смысл прогнать перед коммитом. Обёртка вокруг `dotnet build` сама по себе + бесполезна — ценность здесь в том, что шаги идут в правильном порядке, падают быстро и + возвращают ненулевой код возврата. + +.PARAMETER Configuration + Debug или Release. По умолчанию Release — гейт должен проверять то, что уедет. + +.PARAMETER Fix + Переформатировать код вместо проверки. Без этого флага несформатированный код валит сборку. + +.PARAMETER SkipFormat + Пропустить проверку форматирования. + +.PARAMETER SkipTests + Только собрать, не запускать тесты. + +.EXAMPLE + ./build.ps1 + Формат, сборка и тесты в Release. + +.EXAMPLE + ./build.ps1 -Fix + Переформатировать код, затем собрать и прогнать тесты. +#> +[CmdletBinding()] +param( + [ValidateSet('Debug', 'Release')] + [string] $Configuration = 'Release', + + [switch] $Fix, + [switch] $SkipFormat, + [switch] $SkipTests +) + +$ErrorActionPreference = 'Stop' +Set-Location $PSScriptRoot + +$solution = 'AvParser.slnx' +$started = Get-Date + +function Invoke-Step { + param( + [Parameter(Mandatory)] [string] $Name, + [Parameter(Mandatory)] [scriptblock] $Action + ) + + Write-Host '' + Write-Host "==> $Name" -ForegroundColor Cyan + + & $Action + + # Native executables do not raise terminating errors, so the exit code is the only signal. + if ($LASTEXITCODE -ne 0) { + throw "$Name — код возврата $LASTEXITCODE" + } +} + +try { + Invoke-Step 'Локальные инструменты' { dotnet tool restore } + + Invoke-Step 'Восстановление пакетов' { dotnet restore $solution } + + if (-not $SkipFormat) { + if ($Fix) { + Invoke-Step 'Форматирование (csharpier format)' { dotnet csharpier format . } + } + else { + # Before the build: an unformatted file is the cheapest possible failure. + Invoke-Step 'Проверка форматирования (csharpier check)' { dotnet csharpier check . } + } + } + + Invoke-Step "Сборка ($Configuration)" { + dotnet build $solution -c $Configuration --no-restore --nologo + } + + if (-not $SkipTests) { + Invoke-Step 'Тесты' { + dotnet test $solution -c $Configuration --no-build --nologo + } + } + + $elapsed = (Get-Date) - $started + Write-Host '' + Write-Host ("OK — {0:mm\:ss}" -f $elapsed) -ForegroundColor Green +} +catch { + Write-Host '' + Write-Host "СБОЙ: $_" -ForegroundColor Red + exit 1 +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d6366f6 --- /dev/null +++ b/build.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Полный локальный гейт: формат, сборка, тесты. +# +# Обёртка вокруг `dotnet build` сама по себе бесполезна — ценность в том, что шаги идут в +# правильном порядке, падают быстро и возвращают ненулевой код возврата. +# +# ./build.sh формат, сборка и тесты в Release +# ./build.sh --fix переформатировать код, затем собрать и прогнать тесты +# ./build.sh -c Debug другая конфигурация +# ./build.sh --skip-tests только собрать + +set -euo pipefail +cd "$(dirname "$0")" + +SOLUTION=AvParser.slnx +CONFIGURATION=Release +FIX=0 +SKIP_FORMAT=0 +SKIP_TESTS=0 + +while [ $# -gt 0 ]; do + case "$1" in + -c|--configuration) CONFIGURATION="$2"; shift 2 ;; + --fix) FIX=1; shift ;; + --skip-format) SKIP_FORMAT=1; shift ;; + --skip-tests) SKIP_TESTS=1; shift ;; + -h|--help) sed -n '2,10p' "$0"; exit 0 ;; + *) echo "Неизвестный аргумент: $1" >&2; exit 2 ;; + esac +done + +case "$CONFIGURATION" in + Debug|Release) ;; + *) echo "Конфигурация должна быть Debug или Release, получено: $CONFIGURATION" >&2; exit 2 ;; +esac + +step() { printf '\n\033[36m==> %s\033[0m\n' "$1"; } + +trap 'printf "\n\033[31mСБОЙ на шаге: %s\033[0m\n" "${CURRENT_STEP:-?}" >&2' ERR + +CURRENT_STEP='Локальные инструменты'; step "$CURRENT_STEP" +dotnet tool restore + +CURRENT_STEP='Восстановление пакетов'; step "$CURRENT_STEP" +dotnet restore "$SOLUTION" + +if [ "$SKIP_FORMAT" -eq 0 ]; then + if [ "$FIX" -eq 1 ]; then + CURRENT_STEP='Форматирование (csharpier format)'; step "$CURRENT_STEP" + dotnet csharpier format . + else + # До сборки: несформатированный файл — самый дешёвый повод упасть. + CURRENT_STEP='Проверка форматирования (csharpier check)'; step "$CURRENT_STEP" + dotnet csharpier check . + fi +fi + +CURRENT_STEP="Сборка ($CONFIGURATION)"; step "$CURRENT_STEP" +dotnet build "$SOLUTION" -c "$CONFIGURATION" --no-restore --nologo + +if [ "$SKIP_TESTS" -eq 0 ]; then + CURRENT_STEP='Тесты'; step "$CURRENT_STEP" + dotnet test "$SOLUTION" -c "$CONFIGURATION" --no-build --nologo +fi + +printf '\n\033[32mOK\033[0m\n' diff --git a/run.ps1 b/run.ps1 new file mode 100644 index 0000000..37500d9 --- /dev/null +++ b/run.ps1 @@ -0,0 +1,50 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Запускает desktop-приложение. + +.DESCRIPTION + По умолчанию Debug: быстрее собирается и даёт осмысленные стеки. Инспектора в Avalonia 12 + из коробки нет — см. README. + +.PARAMETER Configuration + Debug или Release. По умолчанию Debug. + +.PARAMETER NoBuild + Не пересобирать — запустить то, что уже собрано. + +.PARAMETER AppArgs + Всё остальное уходит в приложение как аргументы командной строки. + +.EXAMPLE + ./run.ps1 + +.EXAMPLE + ./run.ps1 -Configuration Release -NoBuild +#> +[CmdletBinding()] +param( + [ValidateSet('Debug', 'Release')] + [string] $Configuration = 'Debug', + + [switch] $NoBuild, + + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AppArgs = @() +) + +$ErrorActionPreference = 'Stop' +Set-Location $PSScriptRoot + +$arguments = @('run', '--project', 'src/AvParser.Desktop', '-c', $Configuration) +if ($NoBuild) { + $arguments += '--no-build' +} +if ($AppArgs.Count -gt 0) { + $arguments += '--' + $arguments += $AppArgs +} + +Write-Host "==> dotnet $($arguments -join ' ')" -ForegroundColor Cyan +& dotnet @arguments +exit $LASTEXITCODE diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..c8c366f --- /dev/null +++ b/run.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Запускает desktop-приложение. +# +# По умолчанию Debug: быстрее собирается и даёт осмысленные стеки. Инспектора в Avalonia 12 +# из коробки нет — см. README. +# +# ./run.sh Debug +# ./run.sh -c Release --no-build запустить уже собранное +# ./run.sh -- --какой-то-аргумент передать аргументы приложению + +set -euo pipefail +cd "$(dirname "$0")" + +CONFIGURATION=Debug +NO_BUILD=0 +APP_ARGS=() + +while [ $# -gt 0 ]; do + case "$1" in + -c|--configuration) CONFIGURATION="$2"; shift 2 ;; + --no-build) NO_BUILD=1; shift ;; + -h|--help) sed -n '2,9p' "$0"; exit 0 ;; + --) shift; APP_ARGS=("$@"); break ;; + *) echo "Неизвестный аргумент: $1 (аргументы приложения — после --)" >&2; exit 2 ;; + esac +done + +ARGS=(run --project src/AvParser.Desktop -c "$CONFIGURATION") +[ "$NO_BUILD" -eq 1 ] && ARGS+=(--no-build) +[ ${#APP_ARGS[@]} -gt 0 ] && ARGS+=(-- "${APP_ARGS[@]}") + +printf '\033[36m==> dotnet %s\033[0m\n' "${ARGS[*]}" +exec dotnet "${ARGS[@]}" diff --git a/src/AvParser.Desktop/AvParser.Desktop.csproj b/src/AvParser.Desktop/AvParser.Desktop.csproj index 59424f6..70653d8 100644 --- a/src/AvParser.Desktop/AvParser.Desktop.csproj +++ b/src/AvParser.Desktop/AvParser.Desktop.csproj @@ -28,8 +28,13 @@ - - - - +