Skip to content

v2.21.0 — fold in the merged community PRs: two masterspool container… #153

v2.21.0 — fold in the merged community PRs: two masterspool container…

v2.21.0 — fold in the merged community PRs: two masterspool container… #153

Workflow file for this run

name: Build & Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
# ─────────────────────────────────────────────────────────────────
# Runs FIRST — creates the draft release for this tag and fills its
# body with the CHANGELOG.md section, BEFORE any platform build runs.
# The three build jobs then just attach their assets to this already-
# described draft, so none of them races to write (or accidentally
# blank) the release body.
# ─────────────────────────────────────────────────────────────────
prepare-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Build release notes from CHANGELOG.md
run: node scripts/extract-changelog.mjs "${{ github.ref_name }}" > release-notes.md
# Release NAME is the version without the leading "v" (e.g. tag v2.0.0 → "2.0.0"),
# matching the historical naming. softprops defaults the name to the tag otherwise.
- name: Release version (strip leading v)
id: ver
run: echo "num=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Create draft release with description
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: ${{ steps.ver.outputs.num }}
draft: true
body_path: release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build:
needs: prepare-release
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
platform: mac
- os: windows-latest
platform: win
- os: ubuntu-latest
platform: linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update -y
sudo apt-get install -y \
libpcsclite-dev \
libusb-1.0-0-dev \
libudev-dev \
build-essential \
python3 \
rpm \
fakeroot
- run: npm ci
# --- macOS only: decode the App Store Connect API Key (.p8) into a file so notarytool can read it ---
- name: Decode App Store Connect API Key (.p8)
if: matrix.os == 'macos-latest'
env:
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: |
mkdir -p ~/private_keys
echo -n "$APPLE_API_KEY_BASE64" | base64 --decode > ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8
chmod 600 ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8
# Export the resolved path for the next step
echo "APPLE_API_KEY=$HOME/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8" >> "$GITHUB_ENV"
# ─────────────────────────────────────────────────────────────────
# macOS / Linux — build & publish in one step (no post-build signing).
# Windows is handled in the next 4 steps so we can SIGN the .exe with
# Microsoft Trusted Signing (Azure) BEFORE publishing it to the Release.
# ─────────────────────────────────────────────────────────────────
- name: Build & publish (macOS / Linux)
if: matrix.os != 'windows-latest'
run: npm run build:${{ matrix.platform }}
env:
# GitHub Releases publish (electron-builder reads GH_TOKEN automatically)
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# macOS code signing — electron-builder reads CSC_LINK (base64 .p12) + CSC_KEY_PASSWORD
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
# macOS notarization via App Store Connect API Key (preferred over Apple ID + password)
# APPLE_API_KEY is set in the previous step via GITHUB_ENV (path to decoded .p8)
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Google Desktop OAuth — Touch ID / loopback sign-in
TIGERTAG_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.TIGERTAG_GOOGLE_DESKTOP_CLIENT_ID }}
TIGERTAG_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.TIGERTAG_GOOGLE_DESKTOP_CLIENT_SECRET }}
# ─────────────────────────────────────────────────────────────────
# Windows path — split into:
# 1. Build .exe locally with --publish never (so we can sign first)
# 2. Check whether Trusted Signing secrets are configured in repo
# 3. Sign .exe artifacts via Microsoft Trusted Signing (skipped
# gracefully when secrets are absent → unsigned build still ships)
# 4. Upload .exe (+ .blockmap + latest.yml) to the GitHub Release
# ─────────────────────────────────────────────────────────────────
- name: Build Windows (no publish — sign first, publish after)
if: matrix.os == 'windows-latest'
run: npm run build:win:nopublish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Google Desktop OAuth — Touch ID / loopback sign-in
TIGERTAG_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.TIGERTAG_GOOGLE_DESKTOP_CLIENT_ID }}
TIGERTAG_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.TIGERTAG_GOOGLE_DESKTOP_CLIENT_SECRET }}
- name: Check Trusted Signing secrets availability
id: check_signing
if: matrix.os == 'windows-latest'
shell: bash
env:
AZ_TENANT: ${{ secrets.AZURE_TENANT_ID }}
AZ_CLIENT: ${{ secrets.AZURE_CLIENT_ID }}
AZ_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
CERT_PROFILE: ${{ vars.TRUSTED_SIGNING_CERT_PROFILE }}
run: |
if [ -n "$AZ_TENANT" ] && [ -n "$AZ_CLIENT" ] && [ -n "$AZ_SECRET" ] && [ -n "$CERT_PROFILE" ]; then
echo "ready=true" >> "$GITHUB_OUTPUT"
echo "✓ Trusted Signing secrets present — Windows artifacts will be signed under cert profile '$CERT_PROFILE'."
else
echo "ready=false" >> "$GITHUB_OUTPUT"
echo "⚠ Trusted Signing secrets / cert profile missing — Windows artifacts will be UNSIGNED for this run."
echo " Configure secrets AZURE_TENANT_ID / AZURE_CLIENT_ID / AZURE_CLIENT_SECRET and repo variable TRUSTED_SIGNING_CERT_PROFILE to enable signing."
fi
- name: Sign Windows artifacts (Microsoft Trusted Signing)
if: matrix.os == 'windows-latest' && steps.check_signing.outputs.ready == 'true'
uses: azure/trusted-signing-action@v0.5.1
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: https://neu.codesigning.azure.net/
trusted-signing-account-name: TigerTagStudioSigning
certificate-profile-name: ${{ vars.TRUSTED_SIGNING_CERT_PROFILE }}
files-folder: dist
files-folder-filter: exe
files-folder-recurse: false
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
# Signing changes the .exe (the authenticode signature is embedded into
# the PE binary), which invalidates the SHA-512 + size that
# electron-builder wrote into dist/latest.yml. Without this regen step,
# auto-updater clients would download the new signed .exe, hash-check it
# against the stale latest.yml, mismatch, and refuse the update.
- name: Regenerate dist/latest.yml after signing
if: matrix.os == 'windows-latest' && steps.check_signing.outputs.ready == 'true'
run: node scripts/regen-latest-yml.mjs
# Windows ships TWO .exe (NSIS installer + portable). electron-builder only
# writes latest.yml for `nsis` targets (isSuitableWindowsTarget in
# PublishManager), so the portable can never become the auto-update
# target — but that is a library invariant, not ours. Assert it here so a
# future target rename or an electron-builder change cannot silently point
# every Windows user's updater at a build that does not install.
- name: Assert latest.yml never points at the portable build
if: matrix.os == 'windows-latest'
shell: bash
run: |
if grep -qi "portable" dist/latest.yml; then
echo "::error::dist/latest.yml references the portable build — the auto-updater would download an app that does not install."
cat dist/latest.yml
exit 1
fi
echo "✓ latest.yml points only at the installer:"
grep -E "^(path| - url):" dist/latest.yml
- name: Release version (strip leading v)
if: matrix.os == 'windows-latest'
id: verwin
shell: bash # windows runner defaults to PowerShell; force bash for ${VAR#v}
run: echo "num=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Publish Windows artifacts to GitHub Release
if: matrix.os == 'windows-latest'
# v3.0.0 (2026-04-12) moved the action runtime from Node 20 to
# Node 24, silencing the GitHub Actions deprecation warning that
# v2.6.2 emits. Same inputs/outputs, no breaking API changes.
#
# `draft: true` matches the `releaseType: "draft"` set in
# package.json so electron-builder (mac/linux) and softprops
# (windows) agree on the same release type. With a mismatch,
# the second tool to fire either skips its uploads or creates
# a duplicate release. The release stays in draft until manually
# promoted via the GitHub UI — that's how we want it: lets us
# verify all artifacts are present + spot a broken build
# before the auto-updater picks the new version.
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: ${{ steps.verwin.outputs.num }}
draft: true
files: |
dist/*.exe
dist/*.exe.blockmap
dist/latest.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}