From c4521dc4b2f38c05136a323a6ff01eae45d5534b Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Mon, 30 Mar 2026 19:23:10 +0800 Subject: [PATCH] feat: centralize release script and simplify workflow input Move release.ps1 into the platform kit as the shared source of truth and remove include_readme_md input so all skills always publish readme_md by default. --- .github/workflows/reusable-release-skill.yaml | 9 +- tools/release.ps1 | 216 ++++++++++++++++++ 2 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 tools/release.ps1 diff --git a/.github/workflows/reusable-release-skill.yaml b/.github/workflows/reusable-release-skill.yaml index a1c634b..0e55b3f 100644 --- a/.github/workflows/reusable-release-skill.yaml +++ b/.github/workflows/reusable-release-skill.yaml @@ -11,10 +11,6 @@ on: required: false type: string default: windows.x86_64 - include_readme_md: - required: false - type: boolean - default: false upload_url: required: false type: string @@ -49,15 +45,12 @@ jobs: - name: Parse Metadata and Pack id: build_task - env: - INCLUDE_README_MD: ${{ inputs.include_readme_md }} run: | python -c " import frontmatter, os, json, shutil post = frontmatter.load('SKILL.md') metadata = dict(post.metadata or {}) - if os.environ.get('INCLUDE_README_MD', 'false').lower() == 'true': - metadata['readme_md'] = (post.content or '').strip() + metadata['readme_md'] = (post.content or '').strip() openclaw_meta = metadata.get('metadata', {}).get('openclaw', {}) slug = (openclaw_meta.get('slug') or metadata.get('slug') or metadata.get('name') or '').strip() if not slug: diff --git a/tools/release.ps1 b/tools/release.ps1 new file mode 100644 index 0000000..9a3aa41 --- /dev/null +++ b/tools/release.ps1 @@ -0,0 +1,216 @@ +<# +.SYNOPSIS + One-command release script for skill repos. + +.DESCRIPTION + - Optional auto-commit + - Push current branch + - Auto-increment semantic tag (vX.Y.Z) + - Create & push tag + - Fail fast on unsafe states + +.EXAMPLES + # Safe mode (recommended): requires clean working tree + .\release.ps1 + + # Auto commit tracked/untracked changes then release + .\release.ps1 -AutoCommit -CommitMessage "chore: update skill config" + + # Dry run (show what would happen) + .\release.ps1 -DryRun + + # Custom tag prefix + .\release.ps1 -Prefix "v" -Message "正式发布" + +.NOTES + Requires: git, PowerShell 5+ +#> + +[CmdletBinding()] +param( + [string]$Prefix = "v", + [string]$Message = "正式发布", + [switch]$AutoCommit, + [switch]$RequireClean, + [string]$CommitMessage, + [switch]$DryRun +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Invoke-Git { + param([Parameter(Mandatory = $true)][string]$Args) + Write-Host ">> git $Args" -ForegroundColor DarkGray + & cmd /c "git $Args" + if ($LASTEXITCODE -ne 0) { + throw "git command failed: git $Args" + } +} + + +function Get-GitOutput { + param([Parameter(Mandatory = $true)][string]$Args) + $output = & cmd /c "git $Args" 2>$null + if ($LASTEXITCODE -ne 0) { + throw "git command failed: git $Args" + } + return @($output) +} + +function Test-Repo { + & git rev-parse --is-inside-work-tree *> $null + return ($LASTEXITCODE -eq 0) +} + +function Get-CurrentBranch { + $b = (Get-GitOutput "branch --show-current" | Select-Object -First 1).Trim() + return $b +} + + +function Get-StatusPorcelain { + $lines = @(Get-GitOutput "status --porcelain") + return $lines +} + +function Parse-SemVerTag { + param( + [string]$Tag, + [string]$TagPrefix + ) + $escaped = [regex]::Escape($TagPrefix) + $m = [regex]::Match($Tag, "^${escaped}(\d+)\.(\d+)\.(\d+)$") + if (-not $m.Success) { return $null } + + return [pscustomobject]@{ + Raw = $Tag + Major = [int]$m.Groups[1].Value + Minor = [int]$m.Groups[2].Value + Patch = [int]$m.Groups[3].Value + } +} + +function Get-NextTag { + param([string]$TagPrefix) + + $tags = Get-GitOutput "tag --list" + $parsed = @() + + foreach ($t in $tags) { + $t = $t.Trim() + if (-not $t) { continue } + $obj = Parse-SemVerTag -Tag $t -TagPrefix $TagPrefix + if ($null -ne $obj) { $parsed += $obj } + } + + if ($parsed.Count -eq 0) { + return "${TagPrefix}1.0.1" + } + + $latest = $parsed | Sort-Object Major, Minor, Patch | Select-Object -Last 1 + return "$TagPrefix$($latest.Major).$($latest.Minor).$([int]$latest.Patch + 1)" +} + + +function Ensure-CleanOrAutoCommit { + param( + [switch]$DoAutoCommit, + [switch]$NeedClean, + [switch]$IsDryRun, + [string]$Msg + ) + + $status = @(Get-StatusPorcelain) + if ($status.Length -eq 0) { return } + + if ($NeedClean) { + Write-Host "Working tree is not clean and -RequireClean is enabled." -ForegroundColor Yellow + & git status --short + throw "Abort: dirty working tree." + } + + # 默认一键发布:有改动就自动提交;也可用 -AutoCommit 显式开启 + $commitMsg = $Msg + if ([string]::IsNullOrWhiteSpace($commitMsg)) { + $commitMsg = "chore: auto release commit ($(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))" + } + + if (-not $DoAutoCommit) { + Write-Host "Detected uncommitted changes, auto-committing before release..." -ForegroundColor Yellow + } + + if ($IsDryRun) { + Write-Host "[DryRun] Would run: git add -A" -ForegroundColor Yellow + Write-Host "[DryRun] Would run: git commit -m `"$commitMsg`"" -ForegroundColor Yellow + return + } + + Invoke-Git "add -A" + Invoke-Git "commit -m `"$commitMsg`"" +} + + +try { + Write-Host "=== Release Script Start ===" -ForegroundColor Cyan + + if (-not (Test-Repo)) { + throw "Current directory is not a git repository." + } + + $branch = Get-CurrentBranch + if ([string]::IsNullOrWhiteSpace($branch)) { + throw "Unable to determine current branch." + } + + if ($branch -notin @("main", "master")) { + throw "Current branch is '$branch'. Release is only allowed from main/master." + } + + Invoke-Git "fetch --tags --prune origin" + + Ensure-CleanOrAutoCommit -DoAutoCommit:$AutoCommit -NeedClean:$RequireClean -IsDryRun:$DryRun -Msg $CommitMessage + + $upstream = (& git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>$null) + $hasUpstream = ($LASTEXITCODE -eq 0) + + if ($DryRun) { + if ($hasUpstream) { + Write-Host "[DryRun] Would run: git push" -ForegroundColor Yellow + } else { + Write-Host "[DryRun] Would run: git push -u origin $branch" -ForegroundColor Yellow + } + } else { + if ($hasUpstream) { + Invoke-Git "push" + } else { + Invoke-Git "push -u origin $branch" + } + } + + $nextTag = Get-NextTag -TagPrefix $Prefix + Write-Host "Next tag: $nextTag" -ForegroundColor Green + + $existing = @(Get-GitOutput "tag --list `"$nextTag`"") + if ($existing.Length -gt 0) { + throw "Tag already exists: $nextTag" + } + + if ($DryRun) { + Write-Host "[DryRun] Would run: git tag -a $nextTag -m `"$Message`"" -ForegroundColor Yellow + Write-Host "[DryRun] Would run: git push origin $nextTag" -ForegroundColor Yellow + Write-Host "=== DryRun Complete ===" -ForegroundColor Cyan + exit 0 + } + + Invoke-Git "tag -a $nextTag -m `"$Message`"" + Invoke-Git "push origin $nextTag" + + Write-Host "Release success: $nextTag" -ForegroundColor Green + Write-Host "=== Release Script Done ===" -ForegroundColor Cyan + exit 0 +} +catch { + Write-Host "Release failed: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +}