Skip to content

Add manual cancel for breaks; update CI release #39

Add manual cancel for breaks; update CI release

Add manual cancel for breaks; update CI release #39

Workflow file for this run

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