From 30803a0834dd35a44ca1c29541df1b41c9853cfd Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Sun, 21 Jun 2026 10:51:05 +0800 Subject: [PATCH] chore: auto release commit (2026-06-21 10:51:04) --- .github/workflows/release_skill.yaml | 2 +- development/DEVELOPMENT.md | 2 +- release.ps1 | 243 ++++++++++++++++++++++++++- tests/test_docs_standards.py | 2 +- 4 files changed, 238 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release_skill.yaml b/.github/workflows/release_skill.yaml index 270c091..0b0c53c 100644 --- a/.github/workflows/release_skill.yaml +++ b/.github/workflows/release_skill.yaml @@ -5,7 +5,7 @@ on: jobs: release: - uses: admin/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml@main + uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml@main secrets: PYARMOR_REG_B64: ${{ secrets.PYARMOR_REG_B64 }} with: diff --git a/development/DEVELOPMENT.md b/development/DEVELOPMENT.md index 5ab345c..0198f0d 100644 --- a/development/DEVELOPMENT.md +++ b/development/DEVELOPMENT.md @@ -555,7 +555,7 @@ python scripts/main.py `.github/workflows/release_skill.yaml` 默认引用: ```yaml -uses: admin/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml@main +uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml@main ``` 约定如下: diff --git a/release.ps1 b/release.ps1 index fb87d92..1f1bdb8 100644 --- a/release.ps1 +++ b/release.ps1 @@ -1,3 +1,15 @@ +<# +.SYNOPSIS + Local git release for skill-template repo. + +.DESCRIPTION + Handles git commit / push / semver tag / push tag only. + Encryption, ZIP packaging, and marketplace sync are done by CI + (.github/workflows/release_skill.yaml → reusable-release-skill.yaml@main). + + Requires: git, PowerShell 5+ +#> + [CmdletBinding()] param( [string]$Prefix = "v", @@ -11,13 +23,228 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$sharedScript = Join-Path $scriptDir "..\..\jiangchang-platform-kit\tools\release.ps1" -$sharedScript = [System.IO.Path]::GetFullPath($sharedScript) - -if (-not (Test-Path $sharedScript)) { - throw "Shared release script not found: $sharedScript" +function Remove-GitNoise { + param([string[]]$Lines) + return @($Lines | Where-Object { + $_ -and ($_ -notmatch '^warning:\s+unable to access ') + }) } -& $sharedScript @PSBoundParameters -exit $LASTEXITCODE +function Invoke-Git { + param([Parameter(Mandatory = $true)][string]$Args) + Write-Host ">> git $Args" -ForegroundColor DarkGray + & cmd /c "git $Args 2>&1" | Out-Null + 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>&1" + if ($LASTEXITCODE -ne 0) { + throw "git command failed: git $Args" + } + return @(Remove-GitNoise -Lines @($output)) +} + +function Test-Repo { + $output = & cmd /c "git rev-parse --is-inside-work-tree 2>&1" + if ($LASTEXITCODE -ne 0) { + return $false + } + $clean = Remove-GitNoise -Lines @($output) + return (($clean | Select-Object -First 1) -eq "true") +} + +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 Assert-SkillReleasePackagingSources { + param([Parameter(Mandatory = $true)][string]$SkillRoot) + + $scriptsDir = Join-Path $SkillRoot "scripts" + $mainPy = Join-Path $scriptsDir "main.py" + if (-not (Test-Path -LiteralPath $mainPy)) { + return + } + + $text = Get-Content -LiteralPath $mainPy -Raw -Encoding UTF8 -ErrorAction SilentlyContinue + if ([string]::IsNullOrWhiteSpace($text)) { + return + } + + $need = @() + if ($text -match '(?m)^\s*from\s+cli\.') { $need += 'cli' } + if ($text -match '(?m)^\s*import\s+cli\b') { $need += 'cli' } + if ($text -match '(?m)^\s*from\s+service\.') { $need += 'service' } + if ($text -match '(?m)^\s*import\s+service\b') { $need += 'service' } + if ($text -match '(?m)^\s*from\s+db\.') { $need += 'db' } + if ($text -match '(?m)^\s*import\s+db\b') { $need += 'db' } + if ($text -match '(?m)^\s*from\s+util\.') { $need += 'util' } + if ($text -match '(?m)^\s*import\s+util\b') { $need += 'util' } + + foreach ($p in ($need | Select-Object -Unique)) { + $folder = Join-Path $scriptsDir $p + if (-not (Test-Path -LiteralPath $folder)) { + throw "Release check failed: scripts/main.py imports from '$p' but folder is missing: $folder" + } + } + + $pyFiles = @(Get-ChildItem -LiteralPath $scriptsDir -Filter *.py -Recurse -File -ErrorAction SilentlyContinue) + Write-Host "Packaging check: $($pyFiles.Count) Python file(s) under scripts/ (CI will obfuscate all recursively)." -ForegroundColor DarkGray +} + +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 + Remove-GitNoise -Lines @(& cmd /c "git status --short 2>&1") | ForEach-Object { Write-Host $_ } + throw "Abort: dirty working tree." + } + + $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." + } + + if ($DryRun) { + Write-Host "[DryRun] Would run: git fetch --tags --prune origin" -ForegroundColor Yellow + } else { + Invoke-Git "fetch --tags --prune origin" + } + + Ensure-CleanOrAutoCommit -DoAutoCommit:$AutoCommit -NeedClean:$RequireClean -IsDryRun:$DryRun -Msg $CommitMessage + + $skillRoot = (Get-GitOutput "rev-parse --show-toplevel" | Select-Object -First 1).Trim() + if (Test-Path -LiteralPath (Join-Path $skillRoot "SKILL.md")) { + Assert-SkillReleasePackagingSources -SkillRoot $skillRoot + } + + $null = Remove-GitNoise -Lines @(& cmd /c 'git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>&1') + $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 +} diff --git a/tests/test_docs_standards.py b/tests/test_docs_standards.py index 4e3c1ef..35738ee 100644 --- a/tests/test_docs_standards.py +++ b/tests/test_docs_standards.py @@ -146,7 +146,7 @@ class TestDocsStandards(unittest.TestCase): uses_line = _extract_workflow_uses_line(text) self.assertIsNotNone(uses_line, "release_skill.yaml must contain a uses: line") self.assertIn( - "admin/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml", + "client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml", uses_line, ) self.assertRegex(