Add manual cancel for breaks; update CI release #39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| release: | |
| types: [published] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: windows-latest | |
| permissions: | |
| contents: write | |
| # Required for Sigstore-backed build-provenance attestations. | |
| # Without id-token: write the OIDC handshake fails and the | |
| # actions/attest-build-provenance step errors out. | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Full history + tags so the release-notes step can `git log` | |
| # between the previous release tag and this one to build the | |
| # commit-message changelog. | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| # Resolve the build version. Order of precedence: | |
| # 1. release.published → github.event.release.tag_name (the canonical | |
| # source — what the user typed when creating the release on GitHub). | |
| # 2. tag push (refs/tags/v*) → github.ref_name. | |
| # 3. anything else (main push, PR, manual dispatch) → VERSION file. | |
| # The leading `v` is stripped so MSBuild gets a plain SemVer ("1.2.3"). | |
| - name: Resolve build version | |
| id: ver | |
| shell: pwsh | |
| run: | | |
| $tag = "${{ github.event.release.tag_name }}" | |
| if (-not $tag -and "${{ github.ref }}" -like 'refs/tags/*') { | |
| $tag = "${{ github.ref_name }}" | |
| } | |
| if ($tag) { | |
| $v = $tag.TrimStart('v','V') | |
| $isRelease = 'true' | |
| } else { | |
| if (Test-Path VERSION) { | |
| $v = (Get-Content VERSION -Raw).Trim() | |
| } else { | |
| $v = '0.0.0' | |
| } | |
| $tag = "v$v" | |
| $isRelease = 'false' | |
| } | |
| "version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "isRelease=$isRelease" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "Building Twenti $v (tag=$tag, isRelease=$isRelease)" | |
| # ── On a tag push, drop a placeholder release immediately so the Releases | |
| # page shows "🚧 Building…" while CI is still running. We update it with | |
| # the real assets at the end. If anything fails, the draft remains visible | |
| # for the maintainer to delete or retry. Skipped when the trigger is | |
| # `release.published` because the release already exists. | |
| - name: Create placeholder release | |
| if: startsWith(github.ref, 'refs/tags/v') && github.event_name != 'release' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.ver.outputs.tag }} | |
| name: "🚧 ${{ steps.ver.outputs.tag }} — Building…" | |
| body: | | |
| Build is in progress. The portable `.exe` and `Setup.exe` will appear here once CI completes (usually a few minutes). | |
| See the [Actions tab](../../actions) for live progress. | |
| draft: true | |
| prerelease: false | |
| - name: Set up .NET 8 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| # Single publish, used for BOTH the portable download and the installer | |
| # input. We tested splitting into a compressed publish (for the portable) | |
| # and an uncompressed publish (for Inno lzma2 to work on) — turns out | |
| # Brotli's per-file inner compression in EnableCompressionInSingleFile | |
| # beats lzma2/max applied to the assembled bundle, so the dual-publish | |
| # produced a LARGER installer (~100MB vs ~60MB). One publish wins. | |
| # Trim is ON; csproj has TrimmerRootAssembly entries to guard | |
| # H.NotifyIcon's reflection-reachable helpers. | |
| - name: Publish single-file portable | |
| run: dotnet publish Twenti.csproj -c Release -r win-x64 -o publish -p:PublishSingleFile=true -p:PublishTrimmed=true -p:EnableCompressionInSingleFile=true -p:Version=${{ steps.ver.outputs.version }} | |
| - name: Upload portable artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Twenti-portable-win-x64 | |
| path: publish/Twenti.exe | |
| if-no-files-found: error | |
| - name: Compile installer (Inno Setup 6) | |
| shell: pwsh | |
| run: | | |
| $iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" | |
| if (-not (Test-Path $iscc)) { | |
| choco install innosetup -y --no-progress | |
| } | |
| if (-not (Test-Path $iscc)) { | |
| throw "ISCC.exe was not found after installation." | |
| } | |
| & $iscc "/DAppVersion=${{ steps.ver.outputs.version }}" installer\Twenti.iss | |
| - name: Upload installer artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Twenti-installer-win-x64 | |
| path: installer/Output/Twenti-Setup.exe | |
| if-no-files-found: error | |
| # Sigstore-backed build provenance. Generates a verifiable attestation | |
| # for both shipped artifacts using the workflow's GitHub-issued OIDC | |
| # identity. Anyone can verify with `gh attestation verify <file> | |
| # --repo Sammeeeeeeee/Twenti` to confirm the binary came from this | |
| # workflow run on this commit. This is NOT Authenticode — it does not | |
| # remove SmartScreen warnings — it provides supply-chain provenance. | |
| - name: Attest build provenance (Sigstore) | |
| id: attest | |
| if: steps.ver.outputs.isRelease == 'true' | |
| uses: actions/attest-build-provenance@v2 | |
| with: | |
| subject-path: | | |
| publish/Twenti.exe | |
| installer/Output/Twenti-Setup.exe | |
| - name: Build release body | |
| if: steps.ver.outputs.isRelease == 'true' | |
| shell: pwsh | |
| env: | |
| ATTESTATION_URL: ${{ steps.attest.outputs.attestation-url }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ steps.ver.outputs.tag }} | |
| run: | | |
| "### SHA-256 Checksums" > release_body.md | |
| '```text' >> release_body.md | |
| Get-FileHash publish/Twenti.exe, installer/Output/Twenti-Setup.exe -Algorithm SHA256 | | |
| ForEach-Object { "$($_.Hash.ToLower()) $([System.IO.Path]::GetFileName($_.Path))" } >> release_body.md | |
| '```' >> release_body.md | |
| "" >> release_body.md | |
| # Attestation: prefer the specific URL returned by the attest | |
| # step (deep-links to this run's attestation page); fall back to | |
| # the repo-wide attestations index if the output is unavailable | |
| # (older action versions, etc.). | |
| $attUrl = $env:ATTESTATION_URL | |
| if (-not $attUrl) { $attUrl = "https://github.com/$($env:REPO)/attestations" } | |
| "[Sigstore-backed attestation]($attUrl)" >> release_body.md | |
| "" >> release_body.md | |
| # Changelog: every non-merge commit between the previous release | |
| # tag and this one, one per line, in the same order git log | |
| # produces (newest first). Falls back to a placeholder for the | |
| # first-ever release where there's no prior tag to compare to. | |
| $tag = $env:TAG | |
| $prevTag = (git describe --tags --abbrev=0 "$tag^" 2>$null) | |
| if ($LASTEXITCODE -eq 0 -and $prevTag) { | |
| "**Full Changelog**: https://github.com/$($env:REPO)/compare/$prevTag...$tag" >> release_body.md | |
| "" >> release_body.md | |
| git log --no-merges --format='* %s' "$prevTag..$tag" >> release_body.md | |
| } else { | |
| "**Initial release.**" >> release_body.md | |
| } | |
| - name: Publish release | |
| if: steps.ver.outputs.isRelease == 'true' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.ver.outputs.tag }} | |
| name: ${{ steps.ver.outputs.tag }} | |
| files: | | |
| publish/Twenti.exe | |
| installer/Output/Twenti-Setup.exe | |
| draft: false | |
| # body_path supplies the full release body (checksums + | |
| # attestation + commit changelog). Auto-generated notes are | |
| # off so GitHub doesn't append a second PR-based list below. | |
| generate_release_notes: false | |
| body_path: release_body.md |