318 lines
9.3 KiB
PowerShell
318 lines
9.3 KiB
PowerShell
# Prepare TTRPG release: bump version, git push, build Win/Linux, copy to release folder.
|
|
# Run: prepare-release.cmd
|
|
# Options:
|
|
# -Version 1.0.17 explicit version (skip auto bump)
|
|
# -Patch bump patch (default)
|
|
# -Minor bump minor
|
|
# -SkipGit skip commit/push
|
|
# -SkipLinux skip Linux build (WSL)
|
|
# -NoBump do not change package.json version
|
|
|
|
param(
|
|
[string]$Version = '',
|
|
[switch]$Patch,
|
|
[switch]$Minor,
|
|
[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 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"
|
|
|
|
# Step 1 - version
|
|
Write-Step 1 'Bump version'
|
|
$currentVersion = Read-PackageVersion $packageJson
|
|
$newVersion = $currentVersion
|
|
|
|
if ($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
|
|
Write-Host ' 1) Copy Mac files (latest-mac.yml, TTRPGPlayer-*.zip, optional *.dmg) from Mac into release folder if needed'
|
|
Write-Host ' 2) Run publish.cmd to validate and upload to updates.mailib.ru'
|
|
Write-Host ''
|
|
|
|
exit 0
|