1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
name: Release
# Tag a version to cut a release: git tag v0.10.6 && git push origin v0.10.6
on:
push:
tags:
permissions:
contents: write
jobs:
# Gate: a tag on a commit that can't pass checks must never publish.
check:
name: fmt + clippy + test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Format check
run: cargo fmt --all -- --check
# Blocking, matching CI since v0.16. The `continue-on-error` here said
# "matches CI: clippy non-fatal (for now)" and stopped matching the
# moment CI flipped - which made the RELEASE gate weaker than the PR
# gate, exactly backwards for the job whose own comment says a tag that
# cannot pass checks must never publish.
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Tests
run: cargo test --all
build:
name: ${{ matrix.target }}
needs: check
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# musl, statically linked: the glibc build needed 2.39 (ubuntu-24.04's)
# and would not start on Debian 12 or Ubuntu 22.04 (found Sep 19, 2026,
# building the playground image). Same asset name.
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
name: termaxa-linux-x86_64
- os: macos-latest
target: x86_64-apple-darwin
name: termaxa-macos-x86_64
- os: macos-latest
target: aarch64-apple-darwin
name: termaxa-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
name: termaxa-windows-x86_64.exe
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: musl toolchain
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build
run: cargo build --release --target ${{ matrix.target }}
- name: Stage binary (unix)
if: runner.os != 'Windows'
run: |
cp target/${{ matrix.target }}/release/termaxa ${{ matrix.name }}
chmod +x ${{ matrix.name }}
- name: Stage binary (windows)
if: runner.os == 'Windows'
run: cp target/${{ matrix.target }}/release/termaxa.exe ${{ matrix.name }}
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: ${{ matrix.name }}
if-no-files-found: error
# Runs exactly once: creates the release, attaches all binaries,
# generates notes a single time.
release:
name: publish release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all binaries
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: dist/*
fail_on_unmatched_files: true
generate_release_notes: true
# Opens the winget update PR on microsoft/winget-pkgs from the published
# release, so no one runs `wingetcreate` by hand. Needs a classic PAT
# with `public_repo` in the WINGET_TOKEN secret (it pushes to the
# devdoc83/winget-pkgs fork and opens the PR); the first package
# submission (#424831) took 55 days of human validation, updates are
# auto-validated and usually merge within a day. Skipped, not failed, when
# the token is absent, so a release never depends on it.
winget:
name: winget update PR
needs: release
runs-on: windows-latest
if: ${{ !contains(github.ref_name, '-') }}
steps:
- name: Version without the v
id: ver
shell: bash
run: echo "v=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Open the winget PR
if: ${{ env.WINGET_TOKEN != '' }}
uses: vedantmgoyal9/winget-releaser@main
with:
identifier: Termaxa.Termaxa
version: ${{ steps.ver.outputs.v }}
release-tag: ${{ github.ref_name }}
installers-regex: 'termaxa-windows-x86_64\.exe$'
token: ${{ secrets.WINGET_TOKEN }}
env:
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}