nightly.yml

  1name: Nightly Release
  2
  3on:
  4  push:
  5    branches:
  6      - master
  7  workflow_dispatch:
  8
  9permissions:
 10  contents: write
 11
 12jobs:
 13  nightly:
 14    runs-on: macos-latest
 15    steps:
 16      - name: Checkout
 17        uses: actions/checkout@v7
 18        with:
 19          fetch-depth: 0
 20
 21      - name: Set up Go
 22        uses: actions/setup-go@v6
 23        with:
 24          go-version: "1.26.4"
 25
 26      - name: Set up Zig
 27        uses: goto-bus-stop/setup-zig@v2
 28
 29      - name: Set up libpcsclite for Linux cross-compilation
 30        run: |
 31          PCSC_DIR="$RUNNER_TEMP/pcsclite"
 32          mkdir -p "$PCSC_DIR/include" "$PCSC_DIR/lib/pkgconfig"
 33
 34          # Download pcsc-lite headers from upstream
 35          PCSC_URL="https://raw.githubusercontent.com/LudovicRousseau/PCSC/master/src/PCSC"
 36          for header in winscard.h wintypes.h; do
 37            curl -fsSL "$PCSC_URL/$header" -o "$PCSC_DIR/include/$header"
 38          done
 39          # pcsclite.h is generated from pcsclite.h.in — download and substitute the version placeholder
 40          curl -fsSL "$PCSC_URL/pcsclite.h.in" -o "$PCSC_DIR/include/pcsclite.h"
 41          sed -i '' 's/@VERSION@/1.9.0/' "$PCSC_DIR/include/pcsclite.h"
 42
 43          # Build stub library — the real libpcsclite is loaded at runtime on the user's system,
 44          # but the linker needs symbols to resolve during cross-compilation.
 45          cat > "$RUNNER_TEMP/pcsclite_stub.c" << 'STUB'
 46          #include <stddef.h>
 47          typedef long LONG;
 48          typedef unsigned long DWORD;
 49          typedef void *LPCVOID;
 50          typedef char *LPSTR;
 51          typedef const char *LPCSTR;
 52          typedef unsigned char *LPBYTE;
 53          typedef unsigned char BYTE;
 54          typedef LONG SCARDCONTEXT;
 55          typedef LONG SCARDHANDLE;
 56          typedef struct { DWORD dwProtocol; DWORD cbPciLength; } SCARD_IO_REQUEST;
 57          typedef struct { LPCSTR szReader; DWORD dwCurrentState; DWORD dwEventState; DWORD cbAtr; unsigned char rgbAtr[36]; void *pvUserData; } SCARD_READERSTATE;
 58          LONG SCardEstablishContext(DWORD s, LPCVOID r1, LPCVOID r2, SCARDCONTEXT *c) { return 0; }
 59          LONG SCardReleaseContext(SCARDCONTEXT c) { return 0; }
 60          LONG SCardIsValidContext(SCARDCONTEXT c) { return 0; }
 61          LONG SCardCancel(SCARDCONTEXT c) { return 0; }
 62          LONG SCardConnect(SCARDCONTEXT c, LPCSTR r, DWORD s, DWORD p, SCARDHANDLE *h, DWORD *ap) { return 0; }
 63          LONG SCardReconnect(SCARDHANDLE h, DWORD s, DWORD p, DWORD d, DWORD *ap) { return 0; }
 64          LONG SCardDisconnect(SCARDHANDLE h, DWORD d) { return 0; }
 65          LONG SCardBeginTransaction(SCARDHANDLE h) { return 0; }
 66          LONG SCardEndTransaction(SCARDHANDLE h, DWORD d) { return 0; }
 67          LONG SCardStatus(SCARDHANDLE h, LPSTR r, DWORD *rl, DWORD *s, DWORD *p, LPBYTE a, DWORD *al) { return 0; }
 68          LONG SCardTransmit(SCARDHANDLE h, const SCARD_IO_REQUEST *si, const BYTE *s, DWORD sl, SCARD_IO_REQUEST *ri, BYTE *r, DWORD *rl) { return 0; }
 69          LONG SCardControl(SCARDHANDLE h, DWORD c, LPCVOID i, DWORD il, void *o, DWORD ol, DWORD *br) { return 0; }
 70          LONG SCardGetAttrib(SCARDHANDLE h, DWORD a, LPBYTE b, DWORD *bl) { return 0; }
 71          LONG SCardSetAttrib(SCARDHANDLE h, DWORD a, const BYTE *b, DWORD bl) { return 0; }
 72          LONG SCardListReaders(SCARDCONTEXT c, LPCSTR g, LPSTR r, DWORD *rl) { return 0; }
 73          LONG SCardListReaderGroups(SCARDCONTEXT c, LPSTR g, DWORD *gl) { return 0; }
 74          LONG SCardGetStatusChange(SCARDCONTEXT c, DWORD t, SCARD_READERSTATE *s, DWORD n) { return 0; }
 75          LONG SCardFreeMemory(SCARDCONTEXT c, LPCVOID m) { return 0; }
 76          const char *pcsc_stringify_error(LONG e) { return "stub"; }
 77          STUB
 78
 79          zig cc -c -target x86_64-linux-musl -o "$RUNNER_TEMP/pcsclite_stub_amd64.o" "$RUNNER_TEMP/pcsclite_stub.c"
 80          zig cc -c -target aarch64-linux-musl -o "$RUNNER_TEMP/pcsclite_stub_arm64.o" "$RUNNER_TEMP/pcsclite_stub.c"
 81
 82          mkdir -p "$PCSC_DIR/lib/amd64" "$PCSC_DIR/lib/arm64"
 83          ar rcs "$PCSC_DIR/lib/amd64/libpcsclite.a" "$RUNNER_TEMP/pcsclite_stub_amd64.o"
 84          ar rcs "$PCSC_DIR/lib/arm64/libpcsclite.a" "$RUNNER_TEMP/pcsclite_stub_arm64.o"
 85
 86          # Create pkg-config file — Libs will be overridden per-arch via CGO_LDFLAGS in goreleaser
 87          cat > "$PCSC_DIR/lib/pkgconfig/libpcsclite.pc" << EOF
 88          Name: libpcsclite
 89          Description: PC/SC Lite
 90          Version: 1.9.0
 91          Cflags: -I$PCSC_DIR/include
 92          Libs: -L$PCSC_DIR/lib/amd64 -lpcsclite
 93          EOF
 94
 95          echo "PKG_CONFIG_PATH=$PCSC_DIR/lib/pkgconfig" >> $GITHUB_ENV
 96          echo "PCSC_DIR=$PCSC_DIR" >> $GITHUB_ENV
 97
 98      - name: Get macOS SDK path
 99        id: macos_sdk
100        run: echo "path=$(xcrun --show-sdk-path)" >> $GITHUB_OUTPUT
101
102      - name: Build with GoReleaser (snapshot)
103        uses: goreleaser/goreleaser-action@v7
104        with:
105          version: latest
106          args: release --snapshot --clean --config .goreleaser.nightly.yml
107        env:
108          SDK_PATH: ${{ steps.macos_sdk.outputs.path }}
109
110      - name: Delete existing nightly release
111        env:
112          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
113        run: |
114          gh release delete nightlyv0 --yes --cleanup-tag || true
115
116      - name: Create nightly release
117        env:
118          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
119        run: |
120          SHORT_SHA="$(git rev-parse --short HEAD)"
121          LATEST_TAG="$(git describe --tags --abbrev=0 --exclude='nightlyv*' --exclude='preview*' 2>/dev/null || echo '')"
122
123          NOTES_FILE="$RUNNER_TEMP/release_notes.md"
124          {
125            echo "> [!WARNING]"
126            echo "> This is an automated nightly build from the latest \`master\` commit (\`$SHORT_SHA\`). It may be unstable — use stable releases for production."
127            echo ""
128            echo "### Install"
129            echo ""
130            echo '- **Homebrew:** `brew install floatpane/matcha/matcha-nightly`'
131            echo '- **Snapcraft:** `snap install matcha --beta`'
132
133            if [ -n "$LATEST_TAG" ]; then
134              echo ""
135              echo "### Changes since $LATEST_TAG"
136              echo ""
137              git log --pretty=format:"- %s (%h)" "$LATEST_TAG..HEAD"
138              echo ""
139            fi
140          } > "$NOTES_FILE"
141
142          gh release create nightlyv0 dist/*.tar.gz dist/*.zip dist/checksums.txt \
143            --title "Nightly Build ($SHORT_SHA)" \
144            --notes-file "$NOTES_FILE" \
145            --prerelease \
146            --target master
147
148      - name: Update Homebrew tap
149        env:
150          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
151        run: |
152          VERSION="nightly-$(git rev-parse --short HEAD)"
153          DARWIN_AMD64_SHA=$(shasum -a 256 dist/matcha_nightly_darwin_amd64.tar.gz | cut -d ' ' -f 1)
154          DARWIN_ARM64_SHA=$(shasum -a 256 dist/matcha_nightly_darwin_arm64.tar.gz | cut -d ' ' -f 1)
155          LINUX_AMD64_SHA=$(shasum -a 256 dist/matcha_nightly_linux_amd64.tar.gz | cut -d ' ' -f 1)
156          LINUX_ARM64_SHA=$(shasum -a 256 dist/matcha_nightly_linux_arm64.tar.gz | cut -d ' ' -f 1)
157          BASE_URL="https://github.com/floatpane/matcha/releases/download/nightlyv0"
158
159          cat > /tmp/matcha-nightly.rb << EOF
160          class MatchaNightly < Formula
161            desc "A beautiful and functional email client for your terminal (nightly)"
162            homepage "https://matcha.email"
163            version "$VERSION"
164
165            on_macos do
166              if Hardware::CPU.intel?
167                url "$BASE_URL/matcha_nightly_darwin_amd64.tar.gz"
168                sha256 "$DARWIN_AMD64_SHA"
169              else
170                url "$BASE_URL/matcha_nightly_darwin_arm64.tar.gz"
171                sha256 "$DARWIN_ARM64_SHA"
172              end
173            end
174
175            on_linux do
176              if Hardware::CPU.intel?
177                url "$BASE_URL/matcha_nightly_linux_amd64.tar.gz"
178                sha256 "$LINUX_AMD64_SHA"
179              else
180                url "$BASE_URL/matcha_nightly_linux_arm64.tar.gz"
181                sha256 "$LINUX_ARM64_SHA"
182              end
183            end
184
185            def install
186              bin.install "matcha"
187            end
188
189            test do
190              system "#{bin}/matcha", "--version"
191            end
192          end
193          EOF
194
195          # Clone the tap repo and push the nightly formula
196          git clone "https://x-access-token:${GH_TOKEN}@github.com/floatpane/homebrew-matcha.git" /tmp/homebrew-matcha
197          cp /tmp/matcha-nightly.rb /tmp/homebrew-matcha/matcha-nightly.rb
198          cd /tmp/homebrew-matcha
199          git config user.name "Floatpane Bot"
200          git config user.email "us@floatpane.com"
201          git add matcha-nightly.rb
202          git diff --cached --quiet || (git commit -m "Update matcha-nightly to $VERSION" && git push)
203
204      - name: Update Nix flake tap
205        env:
206          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
207        run: |
208          VERSION="nightly-$(git rev-parse --short HEAD)"
209          DARWIN_AMD64_SHA=$(shasum -a 256 dist/matcha_nightly_darwin_amd64.tar.gz | cut -d ' ' -f 1)
210          DARWIN_ARM64_SHA=$(shasum -a 256 dist/matcha_nightly_darwin_arm64.tar.gz | cut -d ' ' -f 1)
211          LINUX_AMD64_SHA=$(shasum -a 256 dist/matcha_nightly_linux_amd64.tar.gz | cut -d ' ' -f 1)
212          LINUX_ARM64_SHA=$(shasum -a 256 dist/matcha_nightly_linux_arm64.tar.gz | cut -d ' ' -f 1)
213          BASE_URL="https://github.com/floatpane/matcha/releases/download/nightlyv0"
214
215          # Convert hex sha256 to nix SRI base64 form
216          to_sri() { printf 'sha256-%s' "$(printf '%s' "$1" | xxd -r -p | base64)"; }
217          DARWIN_AMD64_SRI=$(to_sri "$DARWIN_AMD64_SHA")
218          DARWIN_ARM64_SRI=$(to_sri "$DARWIN_ARM64_SHA")
219          LINUX_AMD64_SRI=$(to_sri "$LINUX_AMD64_SHA")
220          LINUX_ARM64_SRI=$(to_sri "$LINUX_ARM64_SHA")
221
222          mkdir -p /tmp/nix-pkg
223          cat > /tmp/nix-pkg/default.nix << EOF
224          { stdenvNoCC, fetchurl, lib }:
225          let
226            sources = {
227              x86_64-darwin = {
228                url = "$BASE_URL/matcha_nightly_darwin_amd64.tar.gz";
229                hash = "$DARWIN_AMD64_SRI";
230              };
231              aarch64-darwin = {
232                url = "$BASE_URL/matcha_nightly_darwin_arm64.tar.gz";
233                hash = "$DARWIN_ARM64_SRI";
234              };
235              x86_64-linux = {
236                url = "$BASE_URL/matcha_nightly_linux_amd64.tar.gz";
237                hash = "$LINUX_AMD64_SRI";
238              };
239              aarch64-linux = {
240                url = "$BASE_URL/matcha_nightly_linux_arm64.tar.gz";
241                hash = "$LINUX_ARM64_SRI";
242              };
243            };
244            src = sources.\${stdenvNoCC.hostPlatform.system}
245              or (throw "matcha-nightly: unsupported system \${stdenvNoCC.hostPlatform.system}");
246          in
247          stdenvNoCC.mkDerivation {
248            pname = "matcha-nightly";
249            version = "$VERSION";
250            src = fetchurl src;
251            sourceRoot = ".";
252            installPhase = ''
253              runHook preInstall
254              install -Dm755 matcha \$out/bin/matcha
255              runHook postInstall
256            '';
257            meta = {
258              description = "Beautiful and functional email client for the terminal (nightly)";
259              homepage = "https://matcha.email";
260              license = lib.licenses.mit;
261              mainProgram = "matcha";
262              platforms = builtins.attrNames sources;
263            };
264          }
265          EOF
266
267          cat > /tmp/nix-pkg/flake.nix << 'EOF'
268          {
269            description = "matcha email client — nightly";
270            inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
271            outputs = { self, nixpkgs }:
272              let
273                systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
274                forAll = nixpkgs.lib.genAttrs systems;
275              in {
276                packages = forAll (system: {
277                  default = nixpkgs.legacyPackages.${system}.callPackage ./default.nix { };
278                });
279              };
280          }
281          EOF
282
283          git clone "https://x-access-token:${GH_TOKEN}@github.com/floatpane/nix-matcha.git" /tmp/nix-matcha
284          cd /tmp/nix-matcha
285          git checkout nightly 2>/dev/null || git checkout -b nightly
286          mkdir -p nightly
287          cp /tmp/nix-pkg/default.nix nightly/default.nix
288          cp /tmp/nix-pkg/flake.nix nightly/flake.nix
289          git config user.name "Floatpane Bot"
290          git config user.email "us@floatpane.com"
291          git add nightly/default.nix nightly/flake.nix
292          git diff --cached --quiet || (git commit -m "matcha-nightly: bump to $VERSION" && git push -u origin nightly)
293
294  snapcraft:
295    runs-on: ${{ matrix.runner }}
296    needs: nightly
297    strategy:
298      matrix:
299        include:
300          - arch: amd64
301            runner: ubuntu-latest
302          - arch: arm64
303            runner: ubuntu-24.04-arm
304    steps:
305      - name: Checkout
306        uses: actions/checkout@v7
307        with:
308          fetch-depth: 0
309
310      - name: Install Snapcraft and LXD
311        run: |
312          sudo snap install snapcraft --classic
313          sudo snap install lxd
314          sudo lxd init --auto
315          sudo iptables -P FORWARD ACCEPT
316          sudo usermod -aG lxd $USER
317
318      - name: Build snap
319        run: |
320          touch .nightly
321          sg lxd -c 'snapcraft pack --use-lxd --build-for=${{ matrix.arch }}'
322
323      - name: Upload snap
324        env:
325          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
326        run: snapcraft upload --release=beta *.snap