From c98392aea0a4f5ae6e614a1dfef2c94c1b3680f2 Mon Sep 17 00:00:00 2001 From: Dmitriy Fofanov Date: Mon, 23 Feb 2026 22:25:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=BE=D1=81=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D0=BA=D0=B8=20=D1=80=D0=B5=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=20=D0=B2=20scripts/release.ps1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 111 ++------------------ scripts/release.ps1 | 239 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 103 deletions(-) create mode 100644 scripts/release.ps1 diff --git a/Makefile b/Makefile index 91a453c..c113a33 100644 --- a/Makefile +++ b/Makefile @@ -476,109 +476,14 @@ endif ## Если GIT_TOKEN не указан — соберёт локально и создаст тег, но не загрузит в Gitea ifeq ($(OS),Windows_NT) release: - @$$ErrorActionPreference = "Stop"; \ - $$v='$(VERSION)'; $$tag="v$$v"; $$token='$(GIT_TOKEN)'; if (-not $$token) { $$token='$(GITEA_TOKEN)' }; \ - if ($$v -eq 'dev' -or $$v -eq '') { Write-Host '!!! VERSION не указана. Использование: make release VERSION=X.Y.Z' -ForegroundColor Red; exit 1 }; \ - $$dirty = git status --porcelain; \ - if ($$dirty) { Write-Host '!!! Есть незакоммиченные изменения. Сначала сделайте git commit.' -ForegroundColor Red; git status --short; exit 1 }; \ - Write-Host ""; \ - Write-Host "══════════════════════════════════════════════" -ForegroundColor Cyan; \ - Write-Host " Создание релиза $$tag" -ForegroundColor Cyan; \ - Write-Host "══════════════════════════════════════════════" -ForegroundColor Cyan; \ - Write-Host ""; \ - Write-Host ">>> [1/6] Кросс-компиляция для всех платформ..." -ForegroundColor Yellow; \ - $$env:GOOS=''; $$env:GOARCH=''; Remove-Item Env:GOARM,Env:GOMIPS -ErrorAction SilentlyContinue; \ - make build-all VERSION=$$v; \ - Write-Host ""; \ - Write-Host ">>> [2/6] Создание архивов..." -ForegroundColor Yellow; \ - $$archDir = '$(BUILD_DIR)\archives'; \ - New-Item -ItemType Directory -Force $$archDir | Out-Null; \ - Get-ChildItem '$(BUILD_DIR)' -File | Where-Object { $$_.Name -ne '.env' } | ForEach-Object { \ - if ($$_.Name -match '\.exe$$') { \ - $$arcName = $$_.BaseName + '.zip'; \ - Write-Host " $$($$.Name) → $$arcName"; \ - Compress-Archive -Path $$_.FullName -DestinationPath "$$archDir\$$arcName" -Force; \ - } elseif ($$_.Name -match '^$(APP_NAME)-') { \ - $$arcName = $$_.Name + '.tar.gz'; \ - Write-Host " $$($$.Name) → $$arcName"; \ - tar -czf "$$archDir\$$arcName" -C '$(BUILD_DIR)' $$_.Name; \ - } \ - }; \ - Write-Host ""; \ - Write-Host ">>> [3/6] Генерация SHA256..." -ForegroundColor Yellow; \ - $$checksumFile = "$$archDir\checksums-sha256.txt"; \ - Get-ChildItem $$archDir -File | Where-Object { $$_.Name -ne 'checksums-sha256.txt' } | Get-FileHash -Algorithm SHA256 | ForEach-Object { "$$($$_.Hash.ToLower()) $$(Split-Path $$_.Path -Leaf)" } | Out-File -Encoding UTF8 $$checksumFile; \ - Write-Host " checksums-sha256.txt создан"; \ - Write-Host ""; \ - Write-Host ">>> [4/6] Создание тега $$tag..." -ForegroundColor Yellow; \ - $$existingTag = git tag -l $$tag; \ - if ($$existingTag) { Write-Host " Тег $$tag уже существует, пропускаю создание" -ForegroundColor Gray } \ - else { git tag -a $$tag -m "Release $$tag" }; \ - git push origin $$tag 2>$$null; \ - git push origin master 2>$$null; \ - Write-Host ""; \ - if (-not $$token) { \ - Write-Host "══════════════════════════════════════════════" -ForegroundColor Yellow; \ - Write-Host " Локальная сборка завершена." -ForegroundColor Yellow; \ - Write-Host " Для загрузки в Gitea укажите GIT_TOKEN:" -ForegroundColor Yellow; \ - Write-Host " make release VERSION=$$v GIT_TOKEN=<токен>" -ForegroundColor Yellow; \ - Write-Host " Архивы: $(BUILD_DIR)\archives\" -ForegroundColor Yellow; \ - Write-Host "══════════════════════════════════════════════" -ForegroundColor Yellow; \ - exit 0 \ - }; \ - Write-Host ">>> [5/6] Создание релиза в Gitea..." -ForegroundColor Yellow; \ - $$giteaUrl = '$(GITEA_URL)'; \ - $$repo = '$(GITEA_REPO)'; \ - $$commit = git rev-parse HEAD; \ - $$date = Get-Date -Format 'dd.MM.yyyy HH:mm UTC'; \ - $$prevTag = ''; try { $$prevTag = git describe --tags --abbrev=0 "$$tag^" 2>$$null } catch {}; \ - $$rawLog = ''; \ - if ($$prevTag) { $$rawLog = git log --pretty=format:"%s" "$$prevTag..$$tag" --no-merges 2>$$null } \ - else { $$rawLog = git log --pretty=format:"%s" --no-merges 2>$$null | Select-Object -First 50 }; \ - $$features = @(); $$fixes = @(); $$refactor = @(); $$other = @(); \ - ($$rawLog -split "`n") | Where-Object { $$_ } | ForEach-Object { \ - $$low = $$_.ToLower(); \ - if ($$low -match '^(функция|feat|feature|добавлен|реализован|новое)') { $$features += "- $$_" } \ - elseif ($$low -match '^(исправлен|fix|bugfix|баг|ошибка)') { $$fixes += "- $$_" } \ - elseif ($$low -match '^(рефакторинг|refactor|оптимизац|улучшен)') { $$refactor += "- $$_" } \ - else { $$other += "- $$_" } \ - }; \ - $$body = "## GenAudioBookInfo $$tag`n`n"; \ - if (Test-Path 'RELEASE_NOTES.md') { $$body += (Get-Content 'RELEASE_NOTES.md' -Raw) + "`n`n" }; \ - if ($$features.Count -gt 0) { $$body += "### Новые возможности`n" + ($$features -join "`n") + "`n`n" }; \ - if ($$fixes.Count -gt 0) { $$body += "### Исправления`n" + ($$fixes -join "`n") + "`n`n" }; \ - if ($$refactor.Count -gt 0) { $$body += "### Рефакторинг`n" + ($$refactor -join "`n") + "`n`n" }; \ - if ($$other.Count -gt 0) { $$body += "### Прочие изменения`n" + ($$other -join "`n") + "`n`n" }; \ - $$body += "---`n`n### Информация о сборке`n`n| Параметр | Значение |`n|---|---|`n| Коммит | ``$$commit`` |`n| Дата | $$date |`n| Go | $(GO_VERSION) |`n"; \ - $$bodyJson = $$body | ConvertTo-Json; \ - $$releaseData = @{ tag_name = $$tag; target_commitish = $$commit; name = "$(APP_NAME) $$tag"; body = $$body; draft = $$false; prerelease = $$false } | ConvertTo-Json -Compress; \ - $$headers = @{ Authorization = "token $$token"; 'Content-Type' = 'application/json' }; \ - $$releaseResp = Invoke-RestMethod -Method Post -Uri "$$giteaUrl/api/v1/repos/$$repo/releases" -Headers $$headers -Body $$releaseData -ErrorAction Stop; \ - $$releaseId = $$releaseResp.id; \ - Write-Host " Релиз создан: ID=$$releaseId"; \ - Write-Host ""; \ - Write-Host ">>> [6/6] Загрузка файлов..." -ForegroundColor Yellow; \ - $$ok = 0; $$fail = 0; \ - Get-ChildItem $$archDir -File | ForEach-Object { \ - $$fname = $$_.Name; $$fsize = '{0:N1} MB' -f ($$_.Length / 1MB); \ - Write-Host -NoNewline (" {0,-55} [{1,6}] " -f $$fname, $$fsize); \ - try { \ - $$bytes = [System.IO.File]::ReadAllBytes($$_.FullName); \ - $$uploadHeaders = @{ Authorization = "token $$token"; 'Content-Type' = 'application/octet-stream' }; \ - Invoke-RestMethod -Method Post -Uri "$$giteaUrl/api/v1/repos/$$repo/releases/$$releaseId/assets?name=$$fname" -Headers $$uploadHeaders -Body $$bytes -ErrorAction Stop | Out-Null; \ - Write-Host "OK" -ForegroundColor Green; \ - $$ok++ \ - } catch { \ - Write-Host "ОШИБКА" -ForegroundColor Red; \ - $$fail++ \ - } \ - }; \ - Write-Host ""; \ - Write-Host "══════════════════════════════════════════════" -ForegroundColor Green; \ - Write-Host " Загружено: $$ok | Ошибок: $$fail" -ForegroundColor Green; \ - Write-Host " Релиз: $$giteaUrl/$$repo/releases/tag/$$tag" -ForegroundColor Green; \ - Write-Host "══════════════════════════════════════════════" -ForegroundColor Green; \ - if ($$fail -gt 0) { exit 1 } + @powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\release.ps1 \ + -Version '$(VERSION)' \ + -Token '$(or $(GIT_TOKEN),$(GITEA_TOKEN))' \ + -AppName '$(APP_NAME)' \ + -BuildDir '$(BUILD_DIR)' \ + -GiteaUrl '$(GITEA_URL)' \ + -GiteaRepo '$(GITEA_REPO)' \ + -GoVersion '$(GO_VERSION)' else release: @v="$(VERSION)"; tag="v$$v"; token="$(GIT_TOKEN)"; [ -z "$$token" ] && token="$(GITEA_TOKEN)"; \ diff --git a/scripts/release.ps1 b/scripts/release.ps1 new file mode 100644 index 0000000..1d4b433 --- /dev/null +++ b/scripts/release.ps1 @@ -0,0 +1,239 @@ +<# +.SYNOPSIS + Полный локальный релиз: сборка + архивы + тег + публикация в Gitea. +.DESCRIPTION + Вызывается из Makefile: make release VERSION=X.Y.Z GIT_TOKEN=xxx +.PARAMETER Version + Версия релиза (например, 2.0.0). Обязательный. +.PARAMETER Token + Gitea API-токен. Если не указан — только локальная сборка. +.PARAMETER AppName + Имя приложения (передаётся из Makefile). +.PARAMETER BuildDir + Директория сборки (передаётся из Makefile). +.PARAMETER GiteaUrl + URL Gitea-сервера. +.PARAMETER GiteaRepo + Репозиторий в формате owner/repo. +.PARAMETER GoVersion + Версия Go (для описания релиза). +#> +param( + [Parameter(Mandatory)][string]$Version, + [string]$Token, + [string]$AppName = "genaudiobookinfo", + [string]$BuildDir = "build", + [string]$GiteaUrl = "https://github.dfv24.com", + [string]$GiteaRepo = "fofanov.dmitry/GenAudioBookInfo", + [string]$GoVersion = "unknown" +) + +$ErrorActionPreference = "Stop" +$tag = "v$Version" + +# ── Валидация ───────────────────────────────────────────────────────────────── + +if ($Version -eq 'dev' -or $Version -eq '') { + Write-Host '!!! VERSION не указана. Использование: make release VERSION=X.Y.Z' -ForegroundColor Red + exit 1 +} + +$dirty = git status --porcelain +if ($dirty) { + Write-Host '!!! Есть незакоммиченные изменения. Сначала сделайте git commit.' -ForegroundColor Red + git status --short + exit 1 +} + +# ── Начало ──────────────────────────────────────────────────────────────────── + +Write-Host "" +Write-Host "══════════════════════════════════════════════" -ForegroundColor Cyan +Write-Host " Создание релиза $tag" -ForegroundColor Cyan +Write-Host "══════════════════════════════════════════════" -ForegroundColor Cyan +Write-Host "" + +# ── [1/6] Кросс-компиляция ─────────────────────────────────────────────────── + +Write-Host ">>> [1/6] Кросс-компиляция для всех платформ..." -ForegroundColor Yellow + +$env:GOOS = '' +$env:GOARCH = '' +Remove-Item Env:GOARM, Env:GOMIPS -ErrorAction SilentlyContinue + +& make build-all VERSION=$Version +if ($LASTEXITCODE -ne 0) { + Write-Host "!!! Ошибка компиляции." -ForegroundColor Red + exit 1 +} + +# ── [2/6] Создание архивов ──────────────────────────────────────────────────── + +Write-Host "" +Write-Host ">>> [2/6] Создание архивов..." -ForegroundColor Yellow + +$archDir = Join-Path $BuildDir "archives" +New-Item -ItemType Directory -Force $archDir | Out-Null + +Get-ChildItem $BuildDir -File | Where-Object { $_.Name -ne '.env' } | ForEach-Object { + if ($_.Name -match '\.exe$') { + $arcName = $_.BaseName + '.zip' + Write-Host " $($_.Name) -> $arcName" + Compress-Archive -Path $_.FullName -DestinationPath (Join-Path $archDir $arcName) -Force + } + elseif ($_.Name -match "^$([regex]::Escape($AppName))-") { + $arcName = $_.Name + '.tar.gz' + Write-Host " $($_.Name) -> $arcName" + tar -czf (Join-Path $archDir $arcName) -C $BuildDir $_.Name + } +} + +# ── [3/6] SHA256 ────────────────────────────────────────────────────────────── + +Write-Host "" +Write-Host ">>> [3/6] Генерация SHA256..." -ForegroundColor Yellow + +$checksumFile = Join-Path $archDir "checksums-sha256.txt" +$lines = Get-ChildItem $archDir -File | + Where-Object { $_.Name -ne 'checksums-sha256.txt' } | + Get-FileHash -Algorithm SHA256 | + ForEach-Object { "$($_.Hash.ToLower()) $(Split-Path $_.Path -Leaf)" } +$lines | Out-File -Encoding UTF8 $checksumFile +Write-Host " checksums-sha256.txt создан" + +# ── [4/6] Тег ───────────────────────────────────────────────────────────────── + +Write-Host "" +Write-Host ">>> [4/6] Создание тега $tag..." -ForegroundColor Yellow + +$existingTag = git tag -l $tag +if ($existingTag) { + Write-Host " Тег $tag уже существует, пропускаю создание" -ForegroundColor Gray +} else { + git tag -a $tag -m "Release $tag" +} +git push origin $tag 2>$null +git push origin master 2>$null + +# ── Проверка токена ─────────────────────────────────────────────────────────── + +if (-not $Token) { + Write-Host "" + Write-Host "══════════════════════════════════════════════" -ForegroundColor Yellow + Write-Host " Локальная сборка завершена." -ForegroundColor Yellow + Write-Host " Для загрузки в Gitea укажите GIT_TOKEN:" -ForegroundColor Yellow + Write-Host " make release VERSION=$Version GIT_TOKEN=<токен>" -ForegroundColor Yellow + Write-Host " Архивы: $archDir\" -ForegroundColor Yellow + Write-Host "══════════════════════════════════════════════" -ForegroundColor Yellow + exit 0 +} + +# ── [5/6] Создание релиза в Gitea ──────────────────────────────────────────── + +Write-Host "" +Write-Host ">>> [5/6] Создание релиза в Gitea..." -ForegroundColor Yellow + +$commit = git rev-parse HEAD +$date = Get-Date -Format 'dd.MM.yyyy HH:mm UTC' +$prevTag = '' +try { $prevTag = git describe --tags --abbrev=0 "$tag^" 2>$null } catch {} + +$rawLog = @() +if ($prevTag) { + $rawLog = git log --pretty=format:"%s" "$prevTag..$tag" --no-merges 2>$null +} else { + $rawLog = git log --pretty=format:"%s" --no-merges 2>$null | Select-Object -First 50 +} + +$features = @() +$fixes = @() +$refactor = @() +$other = @() + +($rawLog -split "`n") | Where-Object { $_ } | ForEach-Object { + $low = $_.ToLower() + if ($low -match '^(функция|feat|feature|добавлен|реализован|новое)') { $features += "- $_" } + elseif ($low -match '^(исправлен|fix|bugfix|баг|ошибка)') { $fixes += "- $_" } + elseif ($low -match '^(рефакторинг|refactor|оптимизац|улучшен)') { $refactor += "- $_" } + else { $other += "- $_" } +} + +# Формируем тело релиза +$body = "## GenAudioBookInfo $tag`n`n" + +if (Test-Path 'RELEASE_NOTES.md') { + $body += (Get-Content 'RELEASE_NOTES.md' -Raw) + "`n`n" +} +if ($features.Count -gt 0) { $body += "### Новые возможности`n" + ($features -join "`n") + "`n`n" } +if ($fixes.Count -gt 0) { $body += "### Исправления`n" + ($fixes -join "`n") + "`n`n" } +if ($refactor.Count -gt 0) { $body += "### Рефакторинг`n" + ($refactor -join "`n") + "`n`n" } +if ($other.Count -gt 0) { $body += "### Прочие изменения`n" + ($other -join "`n") + "`n`n" } + +$body += "---`n`n### Информация о сборке`n`n" +$body += "| Параметр | Значение |`n|---|---|`n" +$body += "| Коммит | ``$commit`` |`n" +$body += "| Дата | $date |`n" +$body += "| Go | $GoVersion |`n" + +# API: создание релиза +$releaseData = @{ + tag_name = $tag + target_commitish = $commit + name = "$AppName $tag" + body = $body + draft = $false + prerelease = $false +} | ConvertTo-Json -Compress -Depth 5 + +$headers = @{ + Authorization = "token $Token" + 'Content-Type' = 'application/json' +} + +$releaseResp = Invoke-RestMethod -Method Post ` + -Uri "$GiteaUrl/api/v1/repos/$GiteaRepo/releases" ` + -Headers $headers -Body ([System.Text.Encoding]::UTF8.GetBytes($releaseData)) ` + -ErrorAction Stop + +$releaseId = $releaseResp.id +Write-Host " Релиз создан: ID=$releaseId" + +# ── [6/6] Загрузка файлов ──────────────────────────────────────────────────── + +Write-Host "" +Write-Host ">>> [6/6] Загрузка файлов..." -ForegroundColor Yellow + +$ok = 0 +$fail = 0 + +Get-ChildItem $archDir -File | ForEach-Object { + $fname = $_.Name + $fsize = '{0:N1} MB' -f ($_.Length / 1MB) + Write-Host -NoNewline (" {0,-55} [{1,6}] " -f $fname, $fsize) + + try { + $bytes = [System.IO.File]::ReadAllBytes($_.FullName) + $uploadHeaders = @{ + Authorization = "token $Token" + 'Content-Type' = 'application/octet-stream' + } + Invoke-RestMethod -Method Post ` + -Uri "$GiteaUrl/api/v1/repos/$GiteaRepo/releases/$releaseId/assets?name=$fname" ` + -Headers $uploadHeaders -Body $bytes -ErrorAction Stop | Out-Null + Write-Host "OK" -ForegroundColor Green + $ok++ + } catch { + Write-Host "ОШИБКА: $_" -ForegroundColor Red + $fail++ + } +} + +# ── Итог ────────────────────────────────────────────────────────────────────── + +Write-Host "" +Write-Host "══════════════════════════════════════════════" -ForegroundColor Green +Write-Host " Загружено: $ok | Ошибок: $fail" -ForegroundColor Green +Write-Host " Релиз: $GiteaUrl/$GiteaRepo/releases/tag/$tag" -ForegroundColor Green +Write-Host "══════════════════════════════════════════════" -ForegroundColor Green + +if ($fail -gt 0) { exit 1 }