name: Pre-release and Changelog on: push: tags: - '*' workflow_dispatch: inputs: version_type: description: 'Version bump type' required: true default: 'patch' type: choice options: - patch - minor - major - build prerelease: description: 'Create as pre-release' required: true default: true type: boolean concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.sha }} cancel-in-progress: true jobs: prepare: runs-on: windows-latest outputs: tag_name: ${{ steps.get_tag.outputs.tag_name }} version: ${{ steps.get_tag.outputs.version }} is_prerelease: ${{ steps.release_type.outputs.is_prerelease }} changelog: ${{ steps.read_changelog.outputs.changelog }} steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true # ========== 获取当前版本 ========== - name: Get current version from Git tag id: get_version run: | # 获取最新的tag $latestTag = git describe --tags --abbrev=0 2>$null if ($latestTag) { $version = $latestTag echo "Found latest tag: $latestTag" } else { # 如果没有tag,使用默认值 $version = "1.0.0.0" echo "No tag found, using default version: $version" } echo "current_version=$version" >> $env:GITHUB_OUTPUT echo "Current version: $version" # ========== 处理版本号和标签名 ========== - name: Get tag name and version id: get_tag run: | if ("${{ github.event_name }}" -eq "push") { # 从 push tag 事件获取原始标签名 $tagName = "${{ github.ref }}".Replace("refs/tags/", "") $cleanVersion = $tagName echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT echo "version=$cleanVersion" >> $env:GITHUB_OUTPUT echo "Using pushed tag: $tagName, version: $cleanVersion" } else { # 从 workflow_dispatch 计算新版本(4位格式) $currentVersion = "${{ steps.get_version.outputs.current_version }}" $versionParts = $currentVersion.Split('.') # 确保版本号格式正确(至少4部分) if ($versionParts.Length -ge 4) { $major = [int]$versionParts[0] $minor = [int]$versionParts[1] $patch = [int]$versionParts[2] $build = [int]$versionParts[3] } else { # 如果版本号格式不正确,补充为4位 if ($versionParts.Length -ge 3) { $major = [int]$versionParts[0] $minor = [int]$versionParts[1] $patch = [int]$versionParts[2] $build = 0 } else { # 如果版本号格式不正确,抛出错误 echo "Error: Invalid version format. Expected format: x.y.z.w (e.g., 1.7.18.0)" exit 1 } } $versionType = "${{ github.event.inputs.version_type }}" $isPrerelease = "${{ github.event.inputs.prerelease }}" -eq "true" switch ($versionType) { "major" { $major++ $minor = 0 $patch = 0 $build = 0 } "minor" { $minor++ $patch = 0 $build = 0 } "patch" { $patch++ $build = 0 } "build" { $build++ } } # 生成新版本号(4位格式,如1.7.18.0) $newVersion = "$major.$minor.$patch.$build" # 根据是否为预发布决定版本号最后一位 # 如果是预发布,确保最后一位不为0(使用1) if ($isPrerelease -and $build -eq 0) { $build = 1 $newVersion = "$major.$minor.$patch.$build" } $tagName = $newVersion echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT echo "version=$newVersion" >> $env:GITHUB_OUTPUT echo "New tag: $tagName, version: $newVersion" } - name: Determine release type id: release_type run: | if ("${{ github.event_name }}" -eq "push") { # 根据版本号最后一位确定是否为预发布版本 # 最后一位为0表示正式版本,非0表示预发布版本 $version = "${{ steps.get_tag.outputs.version }}" $versionParts = $version.Split('.') if ($versionParts.Length -ge 4) { $build = [int]$versionParts[3] if ($build -eq 0) { echo "is_prerelease=false" >> $env:GITHUB_OUTPUT echo "This is a release" } else { echo "is_prerelease=true" >> $env:GITHUB_OUTPUT echo "This is a pre-release (beta)" } } else { echo "is_prerelease=false" >> $env:GITHUB_OUTPUT echo "This is a release (invalid version format)" } } else { # workflow_dispatch 方式 echo "is_prerelease=${{ github.event.inputs.prerelease }}" >> $env:GITHUB_OUTPUT } # ========== 使用 git-cliff 生成变更日志 ========== - name: Generate changelog with git-cliff (for pushed tag) if: github.event_name == 'push' id: git_cliff_tag uses: orhun/git-cliff-action@v4 with: config: build/cliff.toml # 使用项目build目录的 cliff.toml 配置 args: --latest --tag ${{ steps.get_tag.outputs.tag_name }} --output CHANGELOG.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate changelog with git-cliff (for workflow_dispatch) if: github.event_name == 'workflow_dispatch' id: git_cliff_unreleased uses: orhun/git-cliff-action@v4 with: config: build/cliff.toml args: --unreleased --tag ${{ steps.get_tag.outputs.tag_name }} --output CHANGELOG.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Read changelog content id: read_changelog run: | $changelogContent = Get-Content -Path CHANGELOG.md -Raw echo "changelog<> $env:GITHUB_OUTPUT echo $changelogContent >> $env:GITHUB_OUTPUT echo "EOF" >> $env:GITHUB_OUTPUT build: needs: prepare if: success() runs-on: windows-latest outputs: archive_name: ${{ steps.create_archive.outputs.archive_name }} zip_size: ${{ steps.calculate_size.outputs.zip_size }} installer_size: ${{ steps.calculate_installer_size.outputs.installer_size }} steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true - name: Setup NuGet uses: NuGet/setup-nuget@v2.0.1 - name: Setup MSBuild uses: microsoft/setup-msbuild@v2 - name: Install Inno Setup Unofficial Language Files run: | # 创建临时目录用于下载文件 New-Item -ItemType Directory -Path "temp_lang" -Force # 下载英语英国版语言文件 Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/refs/heads/main/Files/Languages/Unofficial/EnglishBritish.isl" -OutFile "temp_lang\EnglishBritish.isl" # 下载简体中文版语言文件 Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/refs/heads/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "temp_lang\ChineseSimplified.isl" # 将文件移动到 Inno Setup 的语言目录 Move-Item -Path "temp_lang\EnglishBritish.isl" -Destination "C:\Program Files (x86)\Inno Setup 6\Languages\EnglishBritish.isl" -Force Move-Item -Path "temp_lang\ChineseSimplified.isl" -Destination "C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl" -Force # 清理临时目录 Remove-Item -Path "temp_lang" -Recurse -Force Write-Host "✅ Inno Setup unofficial language files installed successfully" -ForegroundColor Green - name: Cache NuGet packages and obj files id: cache-nuget uses: actions/cache@v4 with: path: | # NuGet 全局包缓存(已下载的包) ~/.nuget/packages # NuGet 缓存目录(包索引) ~\AppData\Local\NuGet\Cache # 项目 obj 目录(包含 project.assets.json) Ink Canvas/obj/ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.config', '**/*.sln') }} restore-keys: | ${{ runner.os }}-nuget- - name: Restore NuGet packages (if cache missed) if: steps.cache-nuget.outputs.cache-hit != 'true' run: | Write-Host "📥 缓存未命中,正在恢复 NuGet 包..." -ForegroundColor Yellow # 恢复解决方案级别的包 nuget restore "Ink Canvas.sln" -Verbosity minimal # 恢复项目级别的包 msbuild -t:restore "Ink Canvas/InkCanvasForClass.csproj" /p:GitFlow="Github Action" /p:RestorePackagesConfig=true /verbosity:minimal Write-Host "✅ NuGet 包恢复完成" -ForegroundColor Green - name: Display version info run: | echo "Building version: ${{ needs.prepare.outputs.version }}" echo "Tag: ${{ needs.prepare.outputs.tag_name }}" echo "Release type: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'Pre-release' || 'Release' }}" - name: Build the Solution run: | Write-Host "🔨 正在构建项目..." -ForegroundColor Cyan msbuild /p:platform="AnyCPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal Write-Host "🏗️ 构建命令执行完成" -ForegroundColor Cyan - name: Create Release Archive id: create_archive run: | $version = "${{ needs.prepare.outputs.version }}" $archiveName = "InkCanvasForClass.CE.$version.zip" # 创建发布目录 New-Item -ItemType Directory -Path "release" -Force # 复制发布文件 Copy-Item "Ink Canvas\bin\Release\net472\*" "release\" -Recurse -Force # 创建压缩包 Compress-Archive -Path "release\*" -DestinationPath $archiveName -Force echo "archive_name=$archiveName" >> $env:GITHUB_OUTPUT - name: Prepare Inno Setup script run: | $version = "${{ needs.prepare.outputs.version }}" # 更新 ISS 文件中的版本信息 $issPath = "build\InkCanvasForClass CE.iss" $issContent = Get-Content -Path $issPath -Raw # 替换版本信息 $issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`"" # 替换源文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录) $issContent = $issContent -replace 'Source: ".*\\{#MyAppExeName}";', 'Source: "..\release\{#MyAppExeName}";' $issContent = $issContent -replace 'Source: ".*\\InkCanvasForClass.exe.config";', 'Source: "..\release\InkCanvasForClass.exe.config";' # 更新输出目录为当前目录 $issContent = $issContent -replace 'OutputDir=.*', 'OutputDir=.' # 更新默认安装目录 $issContent = $issContent -replace 'DefaultDirName=.*', 'DefaultDirName={autopf}\{#MyAppName}' # 更新许可证文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录) $issContent = $issContent -replace 'LicenseFile=.*', 'LicenseFile=..\LICENSE' # 保存修改后的 ISS 文件 $issContent | Set-Content -Path $issPath -Encoding UTF8 # 显示修改后的 ISS 文件内容 Write-Host "Modified ISS file content:" Write-Host $issContent - name: Build MSI installer with Inno Setup uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 with: path: build\InkCanvasForClass CE.iss options: /O. - name: Rename installer file run: | $version = "${{ needs.prepare.outputs.version }}" $setupFile = "InkCanvasForClass CE Setup.exe" $newSetupName = "InkCanvasForClass.CE.$version.Setup.exe" if (Test-Path $setupFile) { Rename-Item -Path $setupFile -NewName $newSetupName Write-Host "Renamed setup file to: $newSetupName" } else { Write-Host "Setup file not found: $setupFile" } - name: Calculate archive size id: calculate_size run: | $version = "${{ needs.prepare.outputs.version }}" $archiveName = "InkCanvasForClass.CE.$version.zip" # 获取文件大小(字节) $fileSize = (Get-Item $archiveName).Length echo "zip_size=$fileSize" >> $env:GITHUB_OUTPUT echo "Archive size: $fileSize bytes" - name: Calculate installer size id: calculate_installer_size run: | $version = "${{ needs.prepare.outputs.version }}" $installerName = "InkCanvasForClass.CE.$version.Setup.exe" if (Test-Path $installerName) { # 获取文件大小(字节) $fileSize = (Get-Item $installerName).Length echo "installer_size=$fileSize" >> $env:GITHUB_OUTPUT echo "Installer size: $fileSize bytes" } else { echo "Installer file not found: $installerName" } - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: name: build-files-${{ needs.prepare.outputs.version }} path: | InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe sign: needs: [prepare, build] if: success() runs-on: ubuntu-latest # 改为 Ubuntu 以使用 Python 签名工具 permissions: contents: write id-token: write # 需要这个权限来验证签名 steps: - name: Download Build Artifacts uses: actions/download-artifact@v4 with: name: build-files-${{ needs.prepare.outputs.version }} - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Sign release artifacts with sigstore-python uses: sigstore/gh-action-sigstore-python@v3.2.0 with: inputs: | InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe release-signing-artifacts: true upload-signing-artifacts: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Signed Artifacts uses: actions/upload-artifact@v4 with: name: signed-files-${{ needs.prepare.outputs.version }} path: | InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe.sigstore.json release: needs: [prepare, build, sign] if: success() runs-on: ubuntu-latest permissions: contents: write steps: - name: Download Build Artifacts uses: actions/download-artifact@v4 with: name: build-files-${{ needs.prepare.outputs.version }} - name: Download Signed Artifacts (if exists) uses: actions/download-artifact@v4 with: name: signed-files-${{ needs.prepare.outputs.version }} continue-on-error: true - name: Create enhanced changelog with file table id: enhanced_changelog run: | version="${{ needs.prepare.outputs.version }}" # 读取git-cliff生成的changelog内容 originalChangelog="${{ needs.prepare.outputs.changelog }}" # 构建文件信息表格 fileTable=$'\n## 文件信息 (File Information)\n' fileTable+=$'| 文件名 | 大小 |\n' fileTable+=$'|--------|------|\n' # ZIP 文件信息 fileTable+=$'| InkCanvasForClass.CE.'"$version" fileTable+=$'.zip | ${{ needs.build.outputs.zip_size }} bytes |\n' # 安装包文件信息 installerSize="${{ needs.build.outputs.installer_size }}" if [ -n "$installerSize" ]; then fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe | '"$installerSize"' bytes |\n' fi # 检查是否有签名文件 if [ -f "InkCanvasForClass.CE.$version.zip.sigstore.json" ]; then sigstoreSize=$(stat -c%s "InkCanvasForClass.CE.$version.zip.sigstore.json") fileTable+=$'| InkCanvasForClass.CE.'"$version"'.zip.sigstore.json | '"$sigstoreSize"' bytes |\n' fi # 检查安装程序签名文件 if [ -f "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json" ]; then sigstoreSize=$(stat -c%s "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json") fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe.sigstore.json | '"$sigstoreSize"' bytes |\n' fi fileTable+=$'\n*文件大小信息由GitHub Actions自动生成*\n' # 将表格附加到原始changelog enhancedChangelog="${originalChangelog}${fileTable}" # 输出增强版changelog内容 echo "enhanced_changelog<> $GITHUB_OUTPUT echo "$enhancedChangelog" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT echo "Enhanced changelog created with file information table" - name: Display Release Info run: | echo "=== Creating Release ===" echo "Version: ${{ needs.prepare.outputs.version }}" echo "Tag: ${{ needs.prepare.outputs.tag_name }}" echo "Pre-release: ${{ needs.prepare.outputs.is_prerelease }}" - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare.outputs.tag_name }} name: ICC CE ${{ needs.prepare.outputs.version }} body: | ${{ steps.enhanced_changelog.outputs.enhanced_changelog }} draft: false prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }} files: | InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe.sigstore.json fail_on_unmatched_files: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}