Skip to content

Refine Verge Slim title interactions #78

Refine Verge Slim title interactions

Refine Verge Slim title interactions #78

Workflow file for this run

name: CI
on:
push:
branches:
- "**"
tags:
- "*"
pull_request:
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
linux-build:
name: Linux Build
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18.x
cache: npm
cache-dependency-path: npm-shrinkwrap.json
- name: Cache Electron build tools
uses: actions/cache@v4
with:
path: |
~/.cache/electron
~/.cache/electron-builder
~/.npm/_prebuilds
key: ${{ runner.os }}-${{ runner.arch }}-electron-tools-linux-${{ hashFiles('package.json', 'npm-shrinkwrap.json') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-electron-tools-linux-
- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libsecret-1-dev
- name: Install dependencies
run: npm ci
- name: Ensure Electron binary is installed
run: node node_modules/electron/install.js
- name: Build AppImage
run: npm run electron:build:linux -- --publish never
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: linux-build
path: |
dist_electron/*.AppImage
dist_electron/latest-linux.yml
dist_electron/linux-unpacked/**
if-no-files-found: error
windows-build:
name: Windows Build
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18.x
cache: npm
cache-dependency-path: npm-shrinkwrap.json
- name: Cache Electron build tools
uses: actions/cache@v4
with:
path: |
~/.cache/electron
~/.cache/electron-builder
~/.npm/_prebuilds
key: ${{ runner.os }}-${{ runner.arch }}-electron-tools-win-${{ hashFiles('package.json', 'npm-shrinkwrap.json') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-electron-tools-win-
- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libsecret-1-dev
- name: Install dependencies
run: npm ci
- name: Ensure Electron binary is installed
run: node node_modules/electron/install.js
- name: Build Windows exe from Linux
run: npm run electron:build:win -- --publish never
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: windows-build
path: |
dist_electron/*.exe
if-no-files-found: error
macos-build:
name: macOS Build
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18.x
architecture: x64
cache: npm
cache-dependency-path: npm-shrinkwrap.json
- name: Cache Electron build tools
uses: actions/cache@v4
with:
path: |
~/Library/Caches/electron
~/Library/Caches/electron-builder
~/.npm/_prebuilds
key: ${{ runner.os }}-${{ runner.arch }}-electron-tools-macos-${{ hashFiles('package.json', 'npm-shrinkwrap.json') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-electron-tools-macos-
- name: Install dependencies
run: npm ci
env:
npm_config_arch: x64
npm_config_target_arch: x64
- name: Ensure Electron binary is installed
run: node node_modules/electron/install.js
env:
npm_config_arch: x64
npm_config_target_arch: x64
- name: Import Developer ID certificate
run: |
set -euo pipefail
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
if ! echo "$MACOS_CERT_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12" 2>/dev/null; then
echo "$MACOS_CERT_P12_BASE64" | base64 -D > "$RUNNER_TEMP/cert.p12"
fi
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
security import "$RUNNER_TEMP/cert.p12" \
-k "$KEYCHAIN_PATH" \
-P "$MACOS_CERT_PASSWORD" \
-T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
SIGN_IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | sed -n 's/.*"\(Developer ID Application:.*\)"/\1/p' | head -n1)"
if [ -z "${SIGN_IDENTITY:-}" ]; then
echo "Developer ID Application identity not found in imported certificate"
security find-identity -v -p codesigning "$KEYCHAIN_PATH" || true
exit 1
fi
SIGN_IDENTITY_NO_PREFIX="${SIGN_IDENTITY#Developer ID Application: }"
echo "SIGN_IDENTITY=$SIGN_IDENTITY" >> "$GITHUB_ENV"
echo "SIGN_IDENTITY_NO_PREFIX=$SIGN_IDENTITY_NO_PREFIX" >> "$GITHUB_ENV"
echo "CSC_KEYCHAIN=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
env:
MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
- name: Build macOS app bundle
run: npm run electron:build -- --mac dir --x64 --publish never
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
CSC_NAME: ${{ env.SIGN_IDENTITY_NO_PREFIX }}
CSC_KEYCHAIN: ${{ env.CSC_KEYCHAIN }}
NOTARIZE: "false"
npm_config_arch: x64
npm_config_target_arch: x64
- name: Sign nested binaries and app bundle
run: |
set -euo pipefail
APP_PATH="$(find dist_electron/mac -maxdepth 1 -name '*.app' -type d | head -n1)"
ENTITLEMENTS_PATH="dist_electron/entitlements.mac.plist"
if [ -z "${APP_PATH:-}" ]; then
echo "No .app found in dist_electron/mac"
exit 1
fi
if [ ! -f "$ENTITLEMENTS_PATH" ]; then
echo "Missing entitlements file at $ENTITLEMENTS_PATH"
exit 1
fi
xattr -cr "$APP_PATH" || true
# Sign every Mach-O file (executables, dylibs, helper binaries, bundled Tor files).
while IFS= read -r -d '' f; do
if file "$f" | grep -q "Mach-O"; then
codesign --force --timestamp --options runtime --sign "$SIGN_IDENTITY" "$f"
fi
done < <(find "$APP_PATH" -type f -print0)
# Sign nested code bundles from deepest to shallowest, then the root app.
while IFS= read -r -d '' bundle; do
codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGN_IDENTITY" "$bundle"
done < <(find "$APP_PATH" -depth -type d \( -name "*.framework" -o -name "*.app" -o -name "*.xpc" -o -name "*.appex" \) -print0)
codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGN_IDENTITY" "$APP_PATH"
codesign --verify --deep --strict "$APP_PATH"
codesign --display --verbose=2 "$APP_PATH"
codesign -d --entitlements :- "$APP_PATH"
env:
SIGN_IDENTITY: ${{ env.SIGN_IDENTITY }}
- name: Create DMG from app bundle
run: |
set -euo pipefail
APP_PATH="$(find dist_electron/mac -maxdepth 1 -name '*.app' -type d | head -n1)"
APP_NAME="$(basename "$APP_PATH" .app)"
APP_VERSION="$(node -p "require('./package.json').version")"
DMG_PATH="dist_electron/${APP_NAME}-${APP_VERSION}.dmg"
VOL_PATH="/Volumes/${APP_NAME}"
if mount | grep -Fq "on ${VOL_PATH} "; then
hdiutil detach "$VOL_PATH" -force || true
sleep 2
fi
SRC_DIR="$RUNNER_TEMP/dmg-src"
TMP_DMG="$RUNNER_TEMP/${APP_NAME}-${APP_VERSION}.dmg"
rm -rf "$SRC_DIR"
mkdir -p "$SRC_DIR"
cp -R "$APP_PATH" "$SRC_DIR/"
for attempt in 1 2 3; do
if hdiutil create "$TMP_DMG" -srcfolder "$SRC_DIR" -volname "$APP_NAME" -ov -format UDZO; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "Failed to create DMG after 3 attempts"
exit 1
fi
sleep 3
done
mv -f "$TMP_DMG" "$DMG_PATH"
echo "Created $DMG_PATH"
- name: Sign DMG
run: |
set -euo pipefail
DMG_PATH="$(find dist_electron -maxdepth 1 -name '*.dmg' -type f | head -n1)"
if [ -z "${DMG_PATH:-}" ]; then
echo "No DMG found to sign"
exit 1
fi
codesign --force --sign "$SIGN_IDENTITY" "$DMG_PATH"
codesign --verify --verbose "$DMG_PATH"
codesign --display --verbose=2 "$DMG_PATH"
env:
SIGN_IDENTITY: ${{ env.SIGN_IDENTITY }}
- name: Smoke test app launch from DMG (30s)
run: |
set -euo pipefail
DMG_PATH="$(find dist_electron -maxdepth 1 -name '*.dmg' -type f | head -n1)"
if [ -z "${DMG_PATH:-}" ]; then
echo "No DMG found to test"
exit 1
fi
MOUNT_OUTPUT="$(hdiutil attach "$DMG_PATH" -nobrowse -readonly)"
MOUNT_DEVICE="$(echo "$MOUNT_OUTPUT" | awk '/^\/dev\// {print $1; exit}')"
MOUNT_POINT="$(echo "$MOUNT_OUTPUT" | awk '/\/Volumes\// {print substr($0, index($0, "/Volumes/")); exit}')"
APP_PATH="$(find "$MOUNT_POINT" -maxdepth 1 -name '*.app' -type d | head -n1)"
if [ -z "${APP_PATH:-}" ]; then
echo "No .app found in mounted DMG"
echo "$MOUNT_OUTPUT"
exit 1
fi
cleanup() {
pkill -f "$APP_PATH/Contents/MacOS" || true
if [ -n "${APP_PID:-}" ]; then
kill "$APP_PID" >/dev/null 2>&1 || true
sleep 2
kill -9 "$APP_PID" >/dev/null 2>&1 || true
fi
hdiutil detach "$MOUNT_DEVICE" -force >/dev/null 2>&1 || true
}
trap cleanup EXIT
APP_EXECUTABLE="$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$APP_PATH/Contents/Info.plist")"
APP_BIN="$APP_PATH/Contents/MacOS/$APP_EXECUTABLE"
if [ ! -x "$APP_BIN" ]; then
echo "App binary not executable at $APP_BIN"
exit 1
fi
"$APP_BIN" &
APP_PID=$!
sleep 30
if ! kill -0 "$APP_PID" >/dev/null 2>&1; then
echo "App exited before 30-second smoke window"
exit 1
fi
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-build
path: |
dist_electron/*.dmg
dist_electron/mac/**
if-no-files-found: error
macos-notarize:
name: macOS Notarize
runs-on: macos-15
needs:
- macos-build
steps:
- name: Download previous notary state (if present)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: macos-notary-state
path: notary_state
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos-build
path: notarize_input
- name: Notarize and staple DMG
run: |
set -euo pipefail
if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then
echo "Missing one or more required Apple notarization secrets: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID"
exit 1
fi
DMG_PATH="$(find notarize_input -name '*.dmg' -type f | head -n1)"
if [ -z "${DMG_PATH:-}" ]; then
echo "No DMG found to notarize"
exit 1
fi
if [ -f "notary_state/notary_submission_id.txt" ]; then
SUBMISSION_ID="$(cat notary_state/notary_submission_id.txt)"
echo "Reusing prior notary submission id: $SUBMISSION_ID"
else
SUBMIT_OUTPUT="$(xcrun notarytool submit "$DMG_PATH" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--output-format json)"
echo "$SUBMIT_OUTPUT"
SUBMISSION_ID="$(echo "$SUBMIT_OUTPUT" | sed -n 's/.*"id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)"
if [ -z "${SUBMISSION_ID:-}" ]; then
echo "Failed to parse notarization submission id"
exit 1
fi
fi
echo "Notary submission id: $SUBMISSION_ID"
mkdir -p notary_state
echo "$SUBMISSION_ID" > notary_state/notary_submission_id.txt
set +e
xcrun notarytool wait "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--timeout 90m
WAIT_RC=$?
set -e
INFO_OUTPUT="$(xcrun notarytool info "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--output-format json || true)"
echo "$INFO_OUTPUT"
STATUS="$(echo "$INFO_OUTPUT" | sed -n 's/.*"status"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)"
if [ "$WAIT_RC" -ne 0 ]; then
echo "Notarization failed or timed out. Submission id: $SUBMISSION_ID"
echo "Printing Apple notary log for diagnostics"
xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" || true
exit "$WAIT_RC"
fi
if [ "${STATUS:-}" != "Accepted" ]; then
echo "Notarization did not return Accepted status (status=${STATUS:-unknown}); printing Apple notary log"
xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" || true
exit 1
fi
xcrun stapler staple "$DMG_PATH"
xcrun stapler validate "$DMG_PATH"
set +e
spctl --assess --type open --verbose "$DMG_PATH"
SPCTL_RC=$?
set -e
if [ "$SPCTL_RC" -ne 0 ]; then
echo "::warning::spctl assessment rejected the DMG in CI (often \"source=Insufficient Context\") despite successful notarization/stapling."
fi
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Upload notary state
if: always()
uses: actions/upload-artifact@v4
with:
name: macos-notary-state
path: notary_state/notary_submission_id.txt
if-no-files-found: ignore
- name: Upload notarized macOS DMG
uses: actions/upload-artifact@v4
with:
name: macos-notarized
path: notarize_input/*.dmg
if-no-files-found: error