refactor: remove dead semver API version gates (floor 1.40.0) #1154
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
| on: | |
| push: | |
| tags: | |
| - '*' | |
| pull_request: | |
| branches: | |
| - '*' | |
| workflow_dispatch: | |
| name: Build EmuConfigurator (Electron) | |
| jobs: | |
| build: | |
| timeout-minutes: 60 | |
| runs-on: ${{ matrix.os }} | |
| # Node.js 24.x "Krypton" - Active LTS (confirmed 2026-03-30) | |
| # https://nodejs.org/en/about/previous-releases | |
| # actions/checkout@v6, actions/setup-node@v6 (confirmed 2026-03-30) | |
| # Runner versions pinned 2026-06-02; update intentionally when upgrading. | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | |
| COREPACK_ENABLE_DOWNLOAD_PROMPT: 0 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Pure include-only matrix: each entry is an explicit standalone job. | |
| # Avoids GitHub Actions ambiguity when two includes share the same os value. | |
| include: | |
| - os: ubuntu-24.04 | |
| artifact-suffix: linux-x64 | |
| - os: macos-26 | |
| artifact-suffix: macos-arm64 | |
| - os: macos-26-intel | |
| artifact-suffix: macos-x86_64 | |
| - os: windows-2022 | |
| artifact-suffix: windows-x64 | |
| arch: x64 | |
| - os: windows-2022 | |
| artifact-suffix: windows-ia32 | |
| arch: ia32 | |
| outputs: | |
| buildtag: ${{ steps.build-info.outputs.tag }} | |
| shortsha: ${{ steps.build-info.outputs.sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Clean Output Directories | |
| shell: bash | |
| run: | | |
| rm -rf out dist | |
| echo "[OK] Cleaned out/ and dist/ directories" | |
| - name: Build Info | |
| id: build-info | |
| shell: bash | |
| run: | | |
| TAG=${GITHUB_REF#refs/tags/} | |
| if [ "$TAG" = "$GITHUB_REF" ]; then TAG="$GITHUB_RUN_NUMBER"; fi | |
| SHA=$(git rev-parse --short HEAD) | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| echo "sha=$SHA" >> $GITHUB_OUTPUT | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| cache: 'yarn' | |
| - name: Setup Linux (rpm, build tools) | |
| if: runner.os == 'Linux' | |
| # zip is required by @electron-forge/maker-zip (cross-zip uses system zip binary) | |
| run: sudo apt update && sudo apt install -y rpm build-essential python3 libudev-dev zip | |
| - name: Cache Electron binary | |
| if: runner.os != 'Windows' | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cache/electron | |
| ~/Library/Caches/electron | |
| key: electron-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('package.json') }} | |
| restore-keys: | | |
| electron-${{ runner.os }}-${{ runner.arch }}- | |
| - name: Yarn Install | |
| run: yarn install --frozen-lockfile | |
| - name: Lint | |
| run: yarn lint | |
| - name: Make Packages | |
| env: | |
| APPLE_SIGNING_ENTITLEMENTS: ./sign/entitlements.plist | |
| EMUCFG_ARCH: ${{ matrix.arch || '' }} | |
| # For certificate-based signing, set these as GitHub secrets: | |
| # APPLE_SIGNING_IDENTITY: "Apple Distribution: [Team Name]" | |
| # APPLE_TEAM_ID: "XXXXXXXXXX" | |
| # WINDOWS_CERT_FILE: path to EmuCert.p12 | |
| # WINDOWS_CERT_PASSWORD: certificate password | |
| shell: bash | |
| run: | | |
| node scripts/build.js | |
| if [ -n "${EMUCFG_ARCH:-}" ]; then | |
| # Explicitly pass --arch: electron-forge make defaults to host arch (always x64) | |
| # and ignores packagerConfig.arch unless --arch is given on the command line. | |
| yarn run electron-forge make --arch "$EMUCFG_ARCH" | |
| else | |
| yarn run electron-forge make | |
| fi | |
| # Packages built on each platform (architecture-specific for Windows): | |
| # macOS: .zip (portable, DMG skipped in CI - requires native macos-alias module) | |
| # Windows: .msi (WiX installer → Program Files, proper Add/Remove, no auto-launch) + .zip (portable) | |
| # Linux: .deb, .rpm (native package managers), plus .zip (portable) | |
| - name: Restructure .zip Artifacts (flatten OS folders) | |
| if: runner.os == 'Linux' | |
| # macOS ZIPs are handled by the macOS-specific step below (preserves .app bundle). | |
| # Windows ZIPs use backslash path separators which Git Bash unzip cannot handle. | |
| # Windows portable ZIPs are uploaded as-is (app folder inside). | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ROOT_DIR=$(pwd) | |
| ZIP_DIR="$ROOT_DIR/out/make/zip" | |
| # Electron Forge places ZIPs at zip/platform/arch/file.zip (three levels). | |
| # Use nullglob to safely handle no matches | |
| shopt -s nullglob | |
| zipfiles=( "$ZIP_DIR"/*/*/*.zip ) | |
| shopt -u nullglob | |
| if [ ${#zipfiles[@]} -eq 0 ]; then | |
| echo "[WARN] No .zip files found in $ZIP_DIR - skipping restructure" | |
| exit 0 | |
| fi | |
| for zipfile in "${zipfiles[@]}"; do | |
| basename=$(basename "$zipfile") | |
| tmpdir=$(mktemp -d) || { echo "[FAIL] mktemp failed"; exit 1; } | |
| output_zip="$ZIP_DIR/$basename" | |
| echo "Processing: $zipfile" | |
| # Unzip with error handling | |
| if ! unzip -q "$zipfile" -d "$tmpdir"; then | |
| echo "[FAIL] unzip failed for $zipfile" | |
| rm -rf "$tmpdir" | |
| exit 1 | |
| fi | |
| # Find the first directory safely | |
| if ! cd "$tmpdir"; then | |
| echo "[FAIL] cannot cd to tmpdir $tmpdir" | |
| rm -rf "$tmpdir" | |
| exit 1 | |
| fi | |
| # Find first directory using a robust method | |
| app_folder=$(find . -maxdepth 1 -type d ! -name '.' -print -quit) | |
| if [ -z "$app_folder" ]; then | |
| echo "[FAIL] no app folder found in $tmpdir for $zipfile" | |
| cd "$ZIP_DIR" | |
| rm -rf "$tmpdir" | |
| exit 1 | |
| fi | |
| app_folder="${app_folder#./}" # Remove leading ./ | |
| if ! cd "$app_folder"; then | |
| echo "[FAIL] cannot cd to app folder $app_folder in $tmpdir" | |
| cd "$ZIP_DIR" | |
| rm -rf "$tmpdir" | |
| exit 1 | |
| fi | |
| # Re-zip with error handling | |
| if ! zip -r -q "$output_zip" .; then | |
| echo "[FAIL] zip failed for $zipfile in app folder $app_folder" | |
| cd "$ZIP_DIR" | |
| rm -rf "$tmpdir" | |
| exit 1 | |
| fi | |
| # Return to zip directory | |
| if ! cd "$ZIP_DIR"; then | |
| echo "[FAIL] cannot return to $ZIP_DIR" | |
| rm -rf "$tmpdir" | |
| exit 1 | |
| fi | |
| # Remove the original nested zip only after successful re-zip | |
| if [ -f "$zipfile" ] && [ "$zipfile" != "$output_zip" ]; then | |
| rm -f "$zipfile" || { echo "[WARN] failed to remove original zip $zipfile"; } | |
| fi | |
| # Cleanup | |
| rm -rf "$tmpdir" || { echo "[WARN] failed to remove tmpdir $tmpdir"; } | |
| echo "[OK] Restructured: $basename" | |
| done | |
| echo "[OK] All .zip files restructured (OS folders removed)" | |
| - name: "Extract macOS .app from Forge zip" | |
| if: startsWith(matrix.os, 'macos') | |
| shell: bash | |
| env: | |
| APP_SUFFIX: ${{ matrix.artifact-suffix }} | |
| run: | | |
| set -euo pipefail | |
| ROOT_DIR=$(pwd) | |
| ZIP_DIR="$ROOT_DIR/out/make/zip" | |
| ARCH_NAME="${APP_SUFFIX#macos-}" | |
| APP_NAME="EmuConfigurator-${ARCH_NAME}.app" | |
| # Forge output: zip/darwin/{arch}/EmuConfigurator-darwin-{arch}-{ver}.zip | |
| shopt -s nullglob | |
| zipfiles=( "$ZIP_DIR"/*/*/*.zip ) | |
| shopt -u nullglob | |
| if [ ${#zipfiles[@]} -eq 0 ]; then | |
| echo "[WARN] No .zip files found in $ZIP_DIR - skipping macOS extract" | |
| exit 0 | |
| fi | |
| for zipfile in "${zipfiles[@]}"; do | |
| orig_name=$(basename "$zipfile") | |
| version=$(echo "$orig_name" | sed -nE 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' || echo '0.0.0') | |
| [ -z "$version" ] && version='0.0.0' | |
| OUTPUT_ZIP="$ZIP_DIR/EmuConfigurator-darwin-${ARCH_NAME}-${version}.zip" | |
| echo "Processing: $zipfile -> $OUTPUT_ZIP" | |
| tmpdir=$(mktemp -d) || { echo "[FAIL] mktemp failed"; exit 1; } | |
| # Extract outer zip | |
| unzip -q "$zipfile" -d "$tmpdir" | |
| # Handle nested zip (Forge sometimes wraps in another zip) | |
| nested_zip=$(find "$tmpdir" -type f -name '*.zip' | head -n1 || true) | |
| if [ -n "$nested_zip" ]; then | |
| nested_tmp=$(mktemp -d) | |
| unzip -q "$nested_zip" -d "$nested_tmp" | |
| rm -rf "$tmpdir" | |
| tmpdir="$nested_tmp" | |
| fi | |
| # Find .app bundle | |
| app_bundle=$(find "$tmpdir" -type d -name '*.app' | head -n1 || true) | |
| if [ -z "$app_bundle" ]; then | |
| contents_dir=$(find "$tmpdir" -type d -name 'Contents' | head -n1 || true) | |
| if [ -n "$contents_dir" ]; then | |
| app_bundle="$(dirname "$contents_dir")" | |
| else | |
| echo "[FAIL] no .app or Contents/ found in $zipfile" | |
| rm -rf "$tmpdir"; exit 1 | |
| fi | |
| fi | |
| # Rename .app bundle to arch-specific name | |
| app_parent="$(dirname "$app_bundle")" | |
| mv "$app_bundle" "$app_parent/$APP_NAME" | |
| # Re-zip the .app so upload-artifact gets a plain .zip file | |
| # Result: EmuConfigurator-darwin-arm64-0.4.3.zip → EmuConfigurator-arm64.app/ | |
| (cd "$app_parent" && zip -r -q "$OUTPUT_ZIP" "$APP_NAME") | |
| rm -rf "$tmpdir" | |
| rm -f "$zipfile" | |
| echo "[OK] Created $OUTPUT_ZIP containing $APP_NAME" | |
| done | |
| # Clean empty subdirs | |
| find "$ZIP_DIR" -mindepth 1 -type d -empty -delete 2>/dev/null || true | |
| # Verify | |
| shopt -s nullglob | |
| out_zips=( "$ZIP_DIR"/*.zip ) | |
| shopt -u nullglob | |
| if [ ${#out_zips[@]} -eq 0 ]; then | |
| echo "[FAIL] No output zip found after macOS extraction" | |
| exit 1 | |
| fi | |
| echo "[OK] macOS release zip ready: ${out_zips[*]}" | |
| - name: Flatten Artifacts for Upload | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Move every artifact to the root of its maker output directory. | |
| # Electron Forge nests output at <maker>/[platform/][arch]/file — we want <maker>/file. | |
| # On non-Windows the zip restructure step already handled out/make/zip. | |
| for dir in out/make/zip out/make/wix out/make/deb out/make/rpm; do | |
| [ -d "$dir" ] || continue | |
| find "$dir" -mindepth 2 -type f | while IFS= read -r f; do | |
| dest="$dir/$(basename "$f")" | |
| [ "$f" = "$dest" ] && continue | |
| mv -f "$f" "$dest" && echo "[OK] Flattened: $(basename "$f")" | |
| done | |
| find "$dir" -mindepth 1 -type d -empty -delete 2>/dev/null || true | |
| done | |
| - name: Rename Windows MSI artifacts | |
| if: startsWith(matrix.os, 'windows') | |
| shell: bash | |
| env: | |
| EMUCFG_ARCH: ${{ matrix.arch }} | |
| run: | | |
| set -euo pipefail | |
| PKG_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo '0.0.0') | |
| for src in out/make/wix/*.msi; do | |
| [ -f "$src" ] || continue | |
| filename=$(basename "$src") | |
| if [[ "$filename" =~ win32-(x64|ia32)-(.+)\.msi$ ]]; then | |
| arch="${BASH_REMATCH[1]}" | |
| version="${BASH_REMATCH[2]}" | |
| else | |
| arch="$EMUCFG_ARCH" | |
| version="$PKG_VERSION" | |
| fi | |
| dest="$(dirname "$src")/EmuConfigurator-${arch}-${version}.msi" | |
| mv -f "$src" "$dest" | |
| echo "[OK] Renamed $filename -> $(basename "$dest")" | |
| done | |
| - name: Rename Windows x64 zip (win32 -> win64) | |
| if: startsWith(matrix.os, 'windows') && matrix.arch == 'x64' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| for src in out/make/zip/EmuConfigurator-win32-x64-*.zip; do | |
| [ -f "$src" ] || continue | |
| dest="${src/win32/win64}" | |
| mv -f "$src" "$dest" | |
| echo "[OK] Renamed $(basename "$src") -> $(basename "$dest")" | |
| done | |
| - name: Validate Artifacts Exist | |
| shell: bash | |
| run: | | |
| if [ ! -d "out/make" ] || [ -z "$(find out/make -type f 2>/dev/null | head -1)" ]; then | |
| echo "[ERROR] No artifacts found in out/make/" | |
| exit 1 | |
| fi | |
| echo "[OK] Artifacts validated" | |
| # Upload Linux .deb | |
| - name: Upload Artifact (linux .deb) | |
| if: startsWith(matrix.os, 'ubuntu') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-linux-deb | |
| path: out/make/deb/*.deb | |
| retention-days: 20 | |
| # Upload Linux .rpm | |
| - name: Upload Artifact (linux .rpm) | |
| if: startsWith(matrix.os, 'ubuntu') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-linux-rpm | |
| path: out/make/rpm/*.rpm | |
| retention-days: 20 | |
| # Upload Linux .zip | |
| - name: Upload Artifact (linux .zip) | |
| if: startsWith(matrix.os, 'ubuntu') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-linux-zip | |
| path: out/make/zip/*.zip | |
| retention-days: 20 | |
| # Upload macOS ARM64 .zip (contains EmuConfigurator-arm64.app at root) | |
| - name: Upload Artifact (macos arm64 .app) | |
| if: matrix.os == 'macos-26' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-macos-arm64 | |
| path: out/make/zip/*.zip | |
| retention-days: 20 | |
| # Upload macOS x86_64 .zip (contains EmuConfigurator-x86_64.app at root) | |
| - name: Upload Artifact (macos x86_64 .app) | |
| if: matrix.os == 'macos-26-intel' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-macos-x86_64 | |
| path: out/make/zip/*.zip | |
| retention-days: 20 | |
| # Upload Windows .msi (WiX installer) | |
| - name: Upload Artifact (windows .msi) | |
| if: startsWith(matrix.os, 'windows') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-windows-msi-${{ matrix.arch }} | |
| path: out/make/wix/*.msi | |
| retention-days: 20 | |
| # Upload Windows .zip (portable) | |
| - name: Upload Artifact (windows .zip) | |
| if: startsWith(matrix.os, 'windows') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EmuConfigurator-windows-zip-${{ matrix.arch }} | |
| path: out/make/zip/*.zip | |
| retention-days: 20 | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/') | |
| steps: | |
| - name: Download All Artifacts | |
| uses: actions/download-artifact@v4 | |
| # v4 is current latest for download-artifact | |
| with: | |
| path: ./Artifacts | |
| - name: List Release Artifacts | |
| run: | | |
| echo "### Release Artifacts ###" | |
| find ./Artifacts -type f \( -name "*.msi" -o -name "*.deb" -o -name "*.rpm" -o -name "*.zip" \) -exec ls -lh {} \; | |
| echo "NOW=$(date +'%Y%m%d.%H%M%S')" >> $GITHUB_ENV | |
| # Draft Dev-Unstable releases via ncipollo/release-action | |
| - name: Draft Release Dev-Unstable repo | |
| if: contains(github.ref, 'test') || contains(github.ref, 'unstab') | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| repo: dev-unstable | |
| owner: emuflight | |
| token: ${{ secrets.NC_PAT_EMUF }} | |
| tag: "${{ env.NOW }}-cfg" | |
| draft: true | |
| prerelease: true | |
| allowUpdates: true | |
| replacesArtifacts: true | |
| artifacts: ./Artifacts/**/*.msi, ./Artifacts/**/*.deb, ./Artifacts/**/*.rpm, ./Artifacts/**/*.zip | |
| artifactContentType: raw | |
| name: "DEV-UNSTABLE EmuConfig / Build ${{ github.run_number }}" | |
| body: | | |
| ## EmuConfigurator BUILD for TESTING | |
| ### Build ${{ github.run_number }} | |
| ### Commit SHA: ${{ needs.build.outputs.shortsha }} (${{ github.sha }}) | |
| ### BuildTag: ${{ needs.build.outputs.buildtag }} | |
| ### EmuConfigurator base plus test code | |
| ### What to Test/Feedback: (Feedback in EmuFlight's Discord or GitHub Discussions) | |
| <details><summary>Changes in this Build:</summary> | |
| <!-- require single blank line below --> | |
| ``` | |
| [insert commit history here] | |
| ``` | |
| </details> | |
| continue-on-error: true | |
| # Draft Dev-master releases via ncipollo/release-action | |
| - name: Draft Release Dev-Master repo | |
| if: contains(github.ref, 'master') | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| repo: dev-master | |
| owner: emuflight | |
| token: ${{ secrets.NC_PAT_EMUF }} | |
| tag: "${{ env.NOW }}-cfg" | |
| draft: true | |
| prerelease: true | |
| allowUpdates: true | |
| replacesArtifacts: true | |
| artifacts: ./Artifacts/**/*.msi, ./Artifacts/**/*.deb, ./Artifacts/**/*.rpm, ./Artifacts/**/*.zip | |
| artifactContentType: raw | |
| name: "DEV-MASTER EmuConfig / Build ${{ github.run_number }}" | |
| body: | | |
| ## EmuConfigurator BUILD of MASTER | |
| ### Build ${{ github.run_number }} | |
| ### Commit SHA: ${{ needs.build.outputs.shortsha }} (${{ github.sha }}) | |
| ### BuildTag: ${{ needs.build.outputs.buildtag }} | |
| ### Feedback Welcome in EmuFlight's Discord or GitHub Discussions. | |
| <details><summary>Changes in this Build:</summary> | |
| <!-- require single blank line below --> | |
| ``` | |
| [insert commit history here] | |
| ``` | |
| </details> | |
| continue-on-error: true | |
| # Draft Releases on main Repo | |
| - name: Draft Release Main Repo | |
| if: contains(github.ref, 'releas') | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| draft: true | |
| prerelease: true | |
| allowUpdates: true | |
| replacesArtifacts: true | |
| tag: ${{ github.ref_name }} | |
| artifacts: ./Artifacts/**/*.msi, ./Artifacts/**/*.deb, ./Artifacts/**/*.rpm, ./Artifacts/**/*.zip | |
| artifactContentType: raw | |
| name: "DRAFT / EmuConfigurator / GitHub Build ${{ github.run_number }}" | |
| body: | | |
| ## EmuConfigurator | |
| ### Build ${{ github.run_number }} | |
| ### Commit SHA: ${{ needs.build.outputs.shortsha }} (${{ github.sha }}) | |
| ### BuildTag: ${{ needs.build.outputs.buildtag }} | |
| <details><summary>Changes in this Release:</summary> | |
| <!-- require single blank line below --> | |
| ``` | |
| [insert commit history here] | |
| ``` | |
| </details> | |
| continue-on-error: false | |
| - name: Release Failed Notification | |
| if: failure() && contains(github.ref, 'releas') | |
| run: | | |
| echo "[ERROR] Release upload failed for ${{ github.ref_name }}" | |
| exit 1 |