Repository navigation
v2.40.0 — Tiger Studio in the browser (tigersystem.io/studio) with on… #182
Workflow file for this run
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 & 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 signing keychain — built HERE rather than by electron-builder. | |
| # | |
| # Its own path passes the certificate's IMPORT password to | |
| # `security set-key-partition-list -k`, but that flag authenticates | |
| # against the KEYCHAIN, not against the .p12. The two are different | |
| # secrets, and macOS used not to check — the runner image that ships | |
| # macOS 26 does, so signing began failing with | |
| # security: SecKeychainUnlock: The user name or passphrase you | |
| # entered is not correct | |
| # on 2026-09-12, with nothing changed on our side: same certificate | |
| # (valid to 2031), same secrets (untouched since May), same | |
| # electron-builder (26.15.3). Only the image moved, 20260728 → 20260907. | |
| # | |
| # Upstream fixed it in PR #10101, but that sits in 27.0.0-alpha.8 and is | |
| # in NO stable release — 26.15.7 and 26.16.1 both lack it. So we own the | |
| # keychain instead of waiting: create it, import the cert, and authorise | |
| # codesign with the keychain's OWN password. `CSC_KEYCHAIN` then tells | |
| # electron-builder to use it and skip the broken path entirely. | |
| # | |
| # The keychain password is generated per run and never leaves the runner. | |
| # Delete this step once the fix lands in a stable electron-builder and | |
| # the dependency is bumped — not before. | |
| # ───────────────────────────────────────────────────────────────── | |
| - name: Prepare macOS signing keychain | |
| if: matrix.os == 'macos-latest' | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| KEYCHAIN="$RUNNER_TEMP/tigertag-signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 24)" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" | |
| echo "$MACOS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/cert.p12" | |
| security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" \ | |
| -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security | |
| rm -f "$RUNNER_TEMP/cert.p12" | |
| # The keychain's OWN password — this is the line electron-builder gets wrong. | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" > /dev/null | |
| # Put it in the search list so codesign can find the identity. | |
| security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"') | |
| echo "CSC_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV" | |
| echo "Identities available for signing:" | |
| security find-identity -v -p codesigning "$KEYCHAIN" | sed 's/[A-F0-9]\{40\}/<hash>/' | |
| # ───────────────────────────────────────────────────────────────── | |
| # 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 — the identity lives in the keychain prepared above | |
| # (CSC_KEYCHAIN, exported to GITHUB_ENV). CSC_LINK is deliberately NOT | |
| # set: passing it makes electron-builder build its own keychain again | |
| # and take the path this works around. | |
| 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 }} |