Skip to content

ci: use standard C++20 coroutines for WGC helper #3

ci: use standard C++20 coroutines for WGC helper

ci: use standard C++20 coroutines for WGC helper #3

Workflow file for this run

name: Build Electron App
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
arch:
description: "macOS architecture to build"
required: true
default: "both"
type: choice
options:
- arm64
- x64
- both
release_tag:
description: "Optional release tag to create or update, e.g. v1.5.0"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-${{ github.ref_name }}-${{ github.event.inputs.release_tag || 'artifacts' }}
cancel-in-progress: false
jobs:
build-windows:
name: Windows installer
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Cache caption assets
uses: actions/cache@v4
with:
path: caption-assets
key: caption-assets-${{ hashFiles('scripts/fetch-caption-model.mjs') }}
- name: Build Windows app
run: npm run build:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Windows installer
uses: actions/upload-artifact@v4
with:
name: openscreen-windows
path: release/**/Openscreen.Setup.*.exe
if-no-files-found: error
retention-days: 30
build-macos:
name: macOS ${{ matrix.arch }} DMG
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
arch: ${{ fromJSON((github.event_name == 'workflow_dispatch' && github.event.inputs.arch != 'both') && format('["{0}"]', github.event.inputs.arch) || '["arm64", "x64"]') }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: npm ci
- name: Ensure sharp prebuilt
run: npm rebuild sharp
env:
npm_config_build_from_source: "false"
- name: Cache caption assets
uses: actions/cache@v4
with:
path: caption-assets
key: caption-assets-${{ hashFiles('scripts/fetch-caption-model.mjs') }}
- name: Resolve macOS signing
id: signing
env:
MAC_CERTIFICATE_P12: ${{ secrets.MAC_CERTIFICATE_P12 }}
MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
MAC_CSC_NAME: ${{ secrets.MAC_CSC_NAME }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
run: |
if [[ -n "$MAC_CERTIFICATE_P12" && -n "$MAC_CERTIFICATE_PASSWORD" && -n "$MAC_CSC_NAME" && -n "$APPLE_ID" && -n "$APPLE_TEAM_ID" && -n "$APPLE_APP_SPECIFIC_PASSWORD" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
- name: Import code signing certificate
if: steps.signing.outputs.enabled == 'true'
env:
MAC_CERTIFICATE_P12: ${{ secrets.MAC_CERTIFICATE_P12 }}
MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
echo "$MAC_CERTIFICATE_P12" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
security import "$RUNNER_TEMP/certificate.p12" \
-k "$KEYCHAIN_PATH" \
-P "$MAC_CERTIFICATE_PASSWORD" \
-T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
rm -f "$RUNNER_TEMP/certificate.p12"
- name: Build Vite + Electron
run: npx tsc && npx vite build
- name: Build native macOS helpers
run: npm run build:native:mac
env:
OPENSCREEN_MAC_HELPER_ARCHS: ${{ matrix.arch }}
- name: Package .app bundle
run: npx electron-builder --mac --${{ matrix.arch }} --dir
env:
CSC_NAME: ${{ secrets.MAC_CSC_NAME }}
CSC_IDENTITY_AUTO_DISCOVERY: ${{ steps.signing.outputs.enabled == 'true' && 'true' || 'false' }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version
id: version
run: echo "version=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT"
- name: Find .app bundle
id: find_app
run: |
VERSION="${{ steps.version.outputs.version }}"
APP_BUNDLE="$(find "release/${VERSION}" -maxdepth 4 -name "*.app" -type d | head -n1)"
if [[ -z "$APP_BUNDLE" ]]; then
echo "::error::No .app bundle found in release/${VERSION}/"
find "release/${VERSION}" -maxdepth 4 -print || true
exit 1
fi
echo "app_bundle=$APP_BUNDLE" >> "$GITHUB_OUTPUT"
- name: Verify .app code signature
if: steps.signing.outputs.enabled == 'true'
run: codesign --verify --deep --strict "${{ steps.find_app.outputs.app_bundle }}"
- name: Create DMG
id: dmg
run: |
VERSION="${{ steps.version.outputs.version }}"
ARCH="${{ matrix.arch }}"
DMG_NAME="Openscreen-Mac-${ARCH}-${VERSION}.dmg"
RELEASE_DIR="release/${VERSION}"
DMG_OUTPUT="${RELEASE_DIR}/${DMG_NAME}"
STAGING="${RELEASE_DIR}/dmg-staging"
rm -rf "$STAGING"
rm -f "$DMG_OUTPUT"
mkdir -p "$STAGING"
cp -R "${{ steps.find_app.outputs.app_bundle }}" "$STAGING/"
ln -s /Applications "$STAGING/Applications"
hdiutil create \
-srcfolder "$STAGING" \
-volname "Openscreen" \
-fs HFS+ \
-fsargs "-c c=64,a=16,e=16" \
-format UDBZ \
"$DMG_OUTPUT"
rm -rf "$STAGING"
echo "dmg_path=$DMG_OUTPUT" >> "$GITHUB_OUTPUT"
- name: Sign DMG
if: steps.signing.outputs.enabled == 'true'
run: |
codesign --force \
--sign "${{ secrets.MAC_CSC_NAME }}" \
--timestamp \
"${{ steps.dmg.outputs.dmg_path }}"
- name: Notarize DMG
if: steps.signing.outputs.enabled == 'true'
run: |
xcrun notarytool submit "${{ steps.dmg.outputs.dmg_path }}" \
--apple-id "${{ secrets.APPLE_ID }}" \
--team-id "${{ secrets.APPLE_TEAM_ID }}" \
--password "${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}" \
--wait
timeout-minutes: 15
- name: Staple notarization ticket
if: steps.signing.outputs.enabled == 'true'
run: xcrun stapler staple "${{ steps.dmg.outputs.dmg_path }}"
- name: Validate stapled DMG
if: steps.signing.outputs.enabled == 'true'
run: |
xcrun stapler validate "${{ steps.dmg.outputs.dmg_path }}"
spctl -a -vv -t install "${{ steps.dmg.outputs.dmg_path }}"
- name: Upload macOS DMG
uses: actions/upload-artifact@v4
with:
name: openscreen-mac-${{ matrix.arch }}
path: ${{ steps.dmg.outputs.dmg_path }}
if-no-files-found: error
retention-days: 30
- name: Cleanup keychain
if: always() && steps.signing.outputs.enabled == 'true'
run: security delete-keychain "$RUNNER_TEMP/build.keychain-db" || true
build-linux:
name: Linux packages
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Install pacman build dependencies
run: sudo apt-get update && sudo apt-get install -y libarchive-tools
- name: Cache caption assets
uses: actions/cache@v4
with:
path: caption-assets
key: caption-assets-${{ hashFiles('scripts/fetch-caption-model.mjs') }}
- name: Build Linux app
run: npm run build:linux
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Linux packages
uses: actions/upload-artifact@v4
with:
name: openscreen-linux
path: |
release/**/*.AppImage
release/**/*.zsync
release/**/*.deb
release/**/*.pacman
if-no-files-found: error
retention-days: 30
publish-release:
name: Publish GitHub release
runs-on: ubuntu-latest
needs:
- build-windows
- build-macos
- build-linux
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '') }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Resolve release tag
id: release
env:
INPUT_TAG: ${{ github.event.inputs.release_tag }}
run: |
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
TAG="${GITHUB_REF_NAME}"
else
TAG="${INPUT_TAG}"
fi
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Release tag must look like v1.5.0; got '${TAG}'"
exit 1
fi
VERSION="${TAG#v}"
PACKAGE_VERSION="$(node -p 'require("./package.json").version')"
if [[ "$PACKAGE_VERSION" != "$VERSION" ]]; then
echo "::error::package.json version ${PACKAGE_VERSION} does not match release tag ${TAG}"
exit 1
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Download installers
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Publish release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.release.outputs.tag }}
run: |
mapfile -t FILES < <(find artifacts -type f | sort)
if [[ "${#FILES[@]}" -eq 0 ]]; then
echo "::error::No installer artifacts were downloaded"
exit 1
fi
if gh release view "$TAG" >/dev/null 2>&1; then
gh release upload "$TAG" "${FILES[@]}" --clobber
else
gh release create "$TAG" "${FILES[@]}" \
--target "$GITHUB_SHA" \
--title "$TAG" \
--generate-notes
fi