Files
DndGamePlayer/scripts/ttrpg-release/prepare-release.ps1
T
Ivan Fontosh cfa067519d фикс
2026-05-18 08:35:36 +08:00

394 lines
12 KiB
PowerShell

# Prepare TTRPG release: build Win/Linux, copy to release folder.
# Run: prepare-release.cmd
#
# Recommended (Mac first, same version on all platforms):
# 1) Bump version in package.json manually (npm version patch --no-git-tag-version)
# 2) pack:mac on Mac, copy latest-mac.yml + *.zip to release folder
# 3) prepare-release.cmd -AfterMac (or release-all.cmd)
#
# Options:
# -AfterMac do not bump; require Mac files in release folder (default in release-all)
# -Bump bump patch before build (Mac can be added later)
# -Version 1.0.17 set explicit version (with -Bump workflow)
# -Minor bump minor (with -Bump)
# -SkipGit skip commit/push
# -SkipLinux skip Linux build (WSL)
# -NoBump do not change package.json (same as -AfterMac without Mac check)
param(
[string]$Version = '',
[switch]$Patch,
[switch]$Minor,
[switch]$Bump,
[switch]$AfterMac,
[switch]$SkipGit,
[switch]$SkipLinux,
[switch]$NoBump
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ToolDir = $PSScriptRoot
$ConfigPath = Join-Path $ToolDir 'release-config.json'
function Write-Step([int]$n, [string]$text) {
Write-Host ''
Write-Host "----- Step $n : $text -----" -ForegroundColor Cyan
}
function Write-Ok([string]$text) {
Write-Host " [OK] $text" -ForegroundColor Green
}
function Write-Fail([string]$text) {
Write-Host " [!!] $text" -ForegroundColor Red
}
function Invoke-Npm {
param(
[string]$Label,
[string[]]$NpmArgs,
[string]$WorkingDirectory
)
Write-Host " > $Label"
Push-Location $WorkingDirectory
try {
& npm @NpmArgs
if ($LASTEXITCODE -ne 0) {
throw "$Label failed (exit $LASTEXITCODE)"
}
} finally {
Pop-Location
}
}
function Convert-ToWslPath([string]$winPath) {
$full = [System.IO.Path]::GetFullPath($winPath)
$p = $full -replace '\\', '/'
if ($p -match '^([A-Za-z]):(.*)$') {
$drive = $Matches[1].ToLower()
return "/mnt/$drive$($Matches[2])"
}
return $p
}
function Get-WslLinuxBuildCommand([string]$WslProjectPath) {
# WSL Debian often has node 18 on PATH; scripts/wsl-pack-linux.sh uses nvm + .nvmrc.
return "cd '$WslProjectPath' && bash scripts/wsl-pack-linux.sh"
}
function Read-PackageVersion([string]$packageJsonPath) {
$json = Get-Content -LiteralPath $packageJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
return [string]$json.version
}
function Invoke-NpmVersion {
param(
[string]$ProjectRoot,
[string]$Spec
)
Invoke-Npm "npm version $Spec" @('version', $Spec, '--no-git-tag-version', '--allow-same-version') $ProjectRoot
}
function Get-GiteaPushUrl([string]$mcpConfigPath) {
if (-not (Test-Path -LiteralPath $mcpConfigPath)) {
return $null
}
$raw = Get-Content -LiteralPath $mcpConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json
$token = $raw.mcpServers.'gitea-mailib'.env.GITEA_ACCESS_TOKEN
if (-not $token) {
return $null
}
return "https://ifontosh:${token}@git.mailib.ru/ifontosh/DndGamePlayer.git"
}
function Invoke-Git {
param(
[string]$ProjectRoot,
[string[]]$GitArgs
)
& git -C $ProjectRoot @GitArgs
if ($LASTEXITCODE -ne 0) {
throw "git $($GitArgs -join ' ') failed (exit $LASTEXITCODE)"
}
}
function Write-GitLines($lines) {
foreach ($line in $lines) {
if ($line -is [System.Management.Automation.ErrorRecord]) {
Write-Host $line.ToString()
} else {
Write-Host ([string]$line)
}
}
}
function Invoke-GitPush {
param(
[string]$ProjectRoot,
[string[]]$PushArgs
)
$prevEap = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$output = & git -C $ProjectRoot push @PushArgs 2>&1
Write-GitLines $output
return $LASTEXITCODE
} finally {
$ErrorActionPreference = $prevEap
}
}
function Push-Git([string]$ProjectRoot, [string]$Remote, [string]$McpConfigPath) {
$branch = (git -C $ProjectRoot rev-parse --abbrev-ref HEAD).Trim()
Write-Host " > git push $Remote $branch"
$exitCode = Invoke-GitPush $ProjectRoot @($Remote, $branch)
if ($exitCode -eq 0) {
return
}
$pushUrl = Get-GiteaPushUrl $McpConfigPath
if (-not $pushUrl) {
throw "git push failed (exit $exitCode) and no Gitea token in mcp config ($McpConfigPath)"
}
Write-Host " > git push via Gitea token URL"
$exitCode = Invoke-GitPush $ProjectRoot @($pushUrl, "HEAD:${branch}")
if ($exitCode -ne 0) {
throw "git push failed (exit $exitCode)"
}
}
function Get-YmlField([string]$ymlPath, [string]$fieldName) {
$content = Get-Content -LiteralPath $ymlPath -Raw -Encoding UTF8
$m = [regex]::Match($content, "(?m)^${fieldName}:\s*(\S+)\s*$")
if ($m.Success) {
return $m.Groups[1].Value.Trim()
}
return $null
}
function Test-MacReleaseReady {
param(
[string]$ReleaseDir,
[string]$ExpectedVersion
)
$macYml = Join-Path $ReleaseDir 'latest-mac.yml'
if (-not (Test-Path -LiteralPath $macYml)) {
throw @"
AfterMac: missing latest-mac.yml in $ReleaseDir
1) Bump version in package.json (now $ExpectedVersion)
2) npm run pack:mac on Mac
3) Copy latest-mac.yml + TTRPGPlayer-*.zip into release folder
4) Run prepare-release.cmd -AfterMac again
"@
}
$macVersion = Get-YmlField $macYml 'version'
if ($macVersion -and $macVersion -ne $ExpectedVersion) {
throw "AfterMac: latest-mac.yml is v$macVersion but package.json is v$ExpectedVersion. Align versions before building Win/Linux."
}
$primary = Get-YmlField $macYml 'path'
if (-not $primary) {
throw 'AfterMac: latest-mac.yml has no path: entry'
}
$primaryPath = Join-Path $ReleaseDir $primary
if (-not (Test-Path -LiteralPath $primaryPath)) {
throw "AfterMac: missing Mac update file $primary (path in latest-mac.yml). Copy zip from Mac build."
}
Write-Ok "Mac feed v$ExpectedVersion, primary: $primary"
}
function Copy-ReleaseArtifacts {
param(
[string]$BuildReleaseDir,
[string]$TargetDir
)
if (-not (Test-Path -LiteralPath $TargetDir)) {
New-Item -ItemType Directory -Path $TargetDir | Out-Null
}
$names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
$fixed = @(
'latest.yml',
'TTRPGPlayer-Setup.exe',
'TTRPGPlayer-Setup.exe.blockmap'
)
foreach ($n in $fixed) {
[void]$names.Add($n)
}
foreach ($yml in Get-ChildItem -LiteralPath $BuildReleaseDir -Filter 'latest-linux*.yml' -File -ErrorAction SilentlyContinue) {
[void]$names.Add($yml.Name)
$content = Get-Content -LiteralPath $yml.FullName -Raw -Encoding UTF8
foreach ($m in [regex]::Matches($content, '(?m)^(?:\s*-\s*)?(?:url|path):\s*(\S+)\s*$')) {
[void]$names.Add($m.Groups[1].Value.Trim())
}
}
foreach ($img in Get-ChildItem -LiteralPath $BuildReleaseDir -Filter 'TTRPGPlayer-*.AppImage' -File -ErrorAction SilentlyContinue) {
[void]$names.Add($img.Name)
}
$copied = 0
foreach ($name in ($names | Sort-Object)) {
$src = Join-Path $BuildReleaseDir $name
if (-not (Test-Path -LiteralPath $src)) {
if ($name -match 'x64' -and $name -notmatch 'x86_64') {
$alt = $name -replace 'x64', 'x86_64'
$src = Join-Path $BuildReleaseDir $alt
}
}
if (-not (Test-Path -LiteralPath $src)) {
Write-Host " [--] skip (not built): $name" -ForegroundColor Yellow
continue
}
$dest = Join-Path $TargetDir ([System.IO.Path]::GetFileName($src))
Copy-Item -LiteralPath $src -Destination $dest -Force
Write-Ok "copied $([System.IO.Path]::GetFileName($src))"
$copied += 1
}
if ($copied -eq 0) {
throw 'No release artifacts copied - check build output in project release folder'
}
}
if (-not (Test-Path -LiteralPath $ConfigPath)) {
throw "Missing release-config.json: $ConfigPath"
}
$config = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json
$projectRoot = [System.IO.Path]::GetFullPath([string]$config.projectRoot)
$releaseDir = [System.IO.Path]::GetFullPath([string]$config.releaseDir)
$gitRemote = [string]$config.gitRemote
$mcpConfig = [string]$config.giteaMcpConfig
$wslDistro = [string]$config.wslDistro
$packageJson = Join-Path $projectRoot 'package.json'
$buildReleaseDir = Join-Path $projectRoot 'release'
if (-not (Test-Path -LiteralPath $packageJson)) {
throw "package.json not found: $packageJson"
}
Write-Host '=== TTRPG Prepare Release ===' -ForegroundColor Cyan
Write-Host "Project: $projectRoot"
Write-Host "Release folder: $releaseDir"
if ($AfterMac) {
$NoBump = $true
}
if ($Bump -and $AfterMac) {
throw 'Use either -Bump or -AfterMac, not both'
}
if ($Bump) {
$Patch = $true
}
$currentVersion = Read-PackageVersion $packageJson
if ($AfterMac) {
Write-Step 0 'Mac artifacts check'
Test-MacReleaseReady $releaseDir $currentVersion
}
# Step 1 - version
if ($AfterMac) {
Write-Step 1 'Version (unchanged, Mac-first workflow)'
$newVersion = $currentVersion
Write-Ok "building Win/Linux at v$newVersion (same as Mac feed)"
} else {
Write-Step 1 'Bump version'
$newVersion = $currentVersion
}
if ($AfterMac) {
# version step done above
} elseif ($NoBump) {
Write-Ok "version unchanged: $currentVersion"
$newVersion = $currentVersion
} elseif ($Version) {
Invoke-NpmVersion $projectRoot $Version.Trim()
$newVersion = Read-PackageVersion $packageJson
Write-Ok "version set to $newVersion (was $currentVersion)"
} elseif ($Minor) {
Invoke-NpmVersion $projectRoot 'minor'
$newVersion = Read-PackageVersion $packageJson
Write-Ok "version $currentVersion -> $newVersion (minor)"
} else {
Invoke-NpmVersion $projectRoot 'patch'
$newVersion = Read-PackageVersion $packageJson
Write-Ok "version $currentVersion -> $newVersion (patch)"
}
# Step 2 - git
if ($SkipGit) {
Write-Step 2 'Git commit and push (skipped)'
} else {
Write-Step 2 'Git commit and push'
Invoke-Git $projectRoot @('add', 'package.json')
if (Test-Path -LiteralPath (Join-Path $projectRoot 'package-lock.json')) {
Invoke-Git $projectRoot @('add', 'package-lock.json')
}
$commitMsg = "chore: release v$newVersion"
$status = (git -C $projectRoot status --porcelain).Trim()
if ($status) {
Invoke-Git $projectRoot @('commit', '-m', $commitMsg)
Write-Ok "committed: $commitMsg"
} else {
Write-Ok 'nothing to commit (version may already be committed)'
}
Push-Git $projectRoot $gitRemote $mcpConfig
Write-Ok 'pushed to remote'
}
# Step 3 - Windows build
Write-Step 3 'Build Windows (npm ci + pack:win)'
Invoke-Npm 'npm ci' @('ci') $projectRoot
Invoke-Npm 'npm run pack:win' @('run', 'pack:win') $projectRoot
Write-Ok 'Windows build finished'
# Step 4 - Linux build
if ($SkipLinux) {
Write-Step 4 'Build Linux (skipped)'
} else {
Write-Step 4 'Build Linux via WSL (npm ci + pack:linux)'
$wslProject = Convert-ToWslPath $projectRoot
$wslCmd = Get-WslLinuxBuildCommand $wslProject
$wslArgs = @()
if ($wslDistro) {
$wslArgs += '-d', $wslDistro
}
$wslArgs += '-e', 'bash', '-lc', $wslCmd
Write-Host " > wsl $($wslArgs -join ' ')"
& wsl @wslArgs
if ($LASTEXITCODE -ne 0) {
throw "WSL Linux build failed (exit $LASTEXITCODE). Install WSL or use -SkipLinux"
}
Write-Ok 'Linux build finished'
}
# Step 5 - copy
Write-Step 5 'Copy artifacts to release folder'
if (-not (Test-Path -LiteralPath $buildReleaseDir)) {
throw "Build output missing: $buildReleaseDir"
}
Copy-ReleaseArtifacts $buildReleaseDir $releaseDir
Write-Ok "release folder updated: $releaseDir"
Write-Host ''
Write-Host '=== Prepare release done ===' -ForegroundColor Green
Write-Host "Version: $newVersion"
Write-Host ''
Write-Host 'Next steps:' -ForegroundColor Yellow
if ($AfterMac) {
Write-Host ' Run publish.cmd (release-all continues to publish automatically)'
} else {
Write-Host ' 1) Copy Mac files (latest-mac.yml, TTRPGPlayer-*.zip) from Mac into release folder'
Write-Host ' 2) Run release-all.cmd (default -AfterMac) or publish.cmd'
}
Write-Host ''
exit 0