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 draft: description: 'Create as draft release' required: true default: false 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: 1 - name: Setup MSBuild uses: microsoft/setup-msbuild@v2 - name: Setup dotnet uses: actions/setup-dotnet@v5 with: cache: true cache-dependency-path: '**/packages.lock.json' - name: Restore Package run: dotnet restore "Ink Canvas.sln" --locked-mode - name: Build the Solution (Release) env: DLASS_SENTRY_DSN: ${{ secrets.DLASS_SENTRY_DSN }} run: | msbuild /p:platform="AnyCPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false - name: Check if exe file is generated id: check-exe run: | $exePath = "Ink Canvas/bin/Release/net472/InkCanvasForClass.exe" if (Test-Path $exePath) { echo "build_success=true" >> $env:GITHUB_OUTPUT } else { echo "build_success=false" >> $env:GITHUB_OUTPUT exit 1 } - name: Install Inno Setup Unofficial Language Files if: steps.check-exe.outputs.build_success == 'true' 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 - name: Create Release Archive id: create_archive if: steps.check-exe.outputs.build_success == 'true' 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 if: steps.check-exe.outputs.build_success == 'true' 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 - name: Build MSI installer with Inno Setup if: steps.check-exe.outputs.build_success == 'true' uses: Minionguyjpro/Inno-Setup-Action@v1.2.7 with: path: build/InkCanvasForClass CE.iss options: /O. - name: Rename installer file if: steps.check-exe.outputs.build_success == 'true' 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 } else { Write-Error "Setup file not found: $setupFile" } - name: Calculate archive size id: calculate_size if: steps.check-exe.outputs.build_success == 'true' run: | $version = "${{ needs.prepare.outputs.version }}" $archiveName = "InkCanvasForClass.CE.$version.zip" # 获取文件大小(字节) $fileSize = (Get-Item $archiveName).Length echo "zip_size=$fileSize" >> $env:GITHUB_OUTPUT - name: Calculate installer size id: calculate_installer_size if: steps.check-exe.outputs.build_success == 'true' 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 } else { Write-Error "Installer file not found: $installerName" } - name: Upload Build Artifacts if: steps.check-exe.outputs.build_success == 'true' uses: actions/upload-artifact@v7 with: name: build-files-${{ needs.prepare.outputs.version }} path: | InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe - name: Create Build Summary if: always() shell: bash run: | echo "# Release Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.check-exe.outputs.build_success }}" = "true" ]; then echo "## ✅ Release Build Successful" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "**Tag:** \`${{ needs.prepare.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY echo "**Release Type:** ${{ needs.prepare.outputs.is_prerelease == 'true' && 'Pre-release' || 'Release' }}" >> $GITHUB_STEP_SUMMARY echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [ -n "${{ steps.calculate_size.outputs.zip_size }}" ]; then echo "**Archive Size:** ${{ steps.calculate_size.outputs.zip_size }} bytes" >> $GITHUB_STEP_SUMMARY fi if [ -n "${{ steps.calculate_installer_size.outputs.installer_size }}" ]; then echo "**Installer Size:** ${{ steps.calculate_installer_size.outputs.installer_size }} bytes" >> $GITHUB_STEP_SUMMARY fi else echo "## ❌ Release Build Failed" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "**Tag:** \`${{ needs.prepare.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY fi sign: needs: [prepare, build] if: success() runs-on: ubuntu-latest permissions: contents: write id-token: write steps: - name: Download Build Artifacts uses: actions/download-artifact@v8 with: name: build-files-${{ needs.prepare.outputs.version }} - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.14' - 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@v7 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-slim permissions: contents: write outputs: enhanced_changelog: ${{steps.enhanced_changelog.outputs.enhanced_changelog}} steps: - name: Download Build Artifacts uses: actions/download-artifact@v8 with: name: build-files-${{ needs.prepare.outputs.version }} - name: Download Signed Artifacts (if exists) uses: actions/download-artifact@v8 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 }}" # 检查是否为预发布版本,如果是则添加警告提示 if [ "${{ needs.prepare.outputs.is_prerelease }}" = "true" ]; then warningText=$'\n> [!CAUTION]\n' warningText+=$'> **注意:此版本为预览或测试版**\n' warningText+=$'> \n' warningText+=$'> 请注意,这是一个预览/测试版本,使用时可能出现BUG,常规用户建议使用预览版或正式版\n\n' originalChangelog="${warningText}${originalChangelog}" fi # 构建文件信息表格 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: ${{ github.event.inputs.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 }} post_release: needs: [prepare, release] if: success() runs-on: ubuntu-slim permissions: id-token: write contents: write steps: - name: Download Build Artifacts uses: actions/download-artifact@v8 with: name: build-files-${{ needs.prepare.outputs.version }} - name: Get beta token uses: octo-sts/action@main id: octo-sts-beta with: scope: InkCanvasForClass/community-beta identity: repo-sync - name: Get download token uses: octo-sts/action@main id: octo-sts-downloads with: scope: InkCanvasForClass/downloads identity: repo-sync - name: Sync downloads repos(Universal) env: GITHUB_TOKEN: ${{ steps.octo-sts-downloads.outputs.token }} run: | set -e git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" REPO_DIR=$(mktemp -d) git clone --depth 1 --filter=blob:none --branch main https://x-access-token:${{ steps.octo-sts-downloads.outputs.token }}@github.com/InkCanvasForClass/downloads.git $REPO_DIR cd $REPO_DIR IS_PRERELEASE="${{ needs.prepare.outputs.is_prerelease }}" VERSION="${{ needs.prepare.outputs.version }}" ZIP_FILE="$GITHUB_WORKSPACE/InkCanvasForClass.CE.$VERSION.zip" if [ "$IS_PRERELEASE" == "true" ]; then mkdir -p Beta cp "$ZIP_FILE" Beta/ git add Beta/InkCanvasForClass.CE.$VERSION.zip git commit -m "Add $VERSION PreRelease" else mkdir -p Release Beta cp "$ZIP_FILE" Release/ cp "$ZIP_FILE" Beta/ git add Release/InkCanvasForClass.CE.$VERSION.zip Beta/InkCanvasForClass.CE.$VERSION.zip git commit -m "Add $VERSION Release" fi git push origin main - name: Update AutomaticUpdateVersionControl in beta repo env: GITHUB_TOKEN: ${{ steps.octo-sts-beta.outputs.token }} run: | CONTENT=$(echo -n "${{ needs.prepare.outputs.version }}" | base64 -w0) SHA=$(gh api /repos/InkCanvasForClass/community-beta/contents/AutomaticUpdateVersionControl.txt --jq '.sha' 2>/dev/null || echo "") gh api \ --method PUT \ /repos/InkCanvasForClass/community-beta/contents/AutomaticUpdateVersionControl.txt \ -f message="Update AutomaticUpdateVersionControl.txt" \ -f content="$CONTENT" \ -f branch="main" \ ${SHA:+-f sha="$SHA"} - name: Create GitHub Release on beta repo uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare.outputs.tag_name }} name: ICC CE ${{ needs.prepare.outputs.version }} body: | ${{ needs.release.outputs.enhanced_changelog }} draft: false prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }} files: | InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip fail_on_unmatched_files: false repository: "InkCanvasForClass/community-beta" env: GITHUB_TOKEN: ${{ steps.octo-sts-beta.outputs.token }} - name: Update community repo AutomaticUpdateVersionControl if: ${{needs.prepare.outputs.is_prerelease == 'false'}} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | CONTENT=$(echo -n "${{ needs.prepare.outputs.version }}" | base64 -w0) SHA=$(gh api /repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt --jq '.sha' 2>/dev/null || echo "") gh api \ --method PUT \ /repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt \ -f message="Update AutomaticUpdateVersionControl.txt" \ -f content="$CONTENT" \ -f branch="beta" \ ${SHA:+-f sha="$SHA"}