name: CI and release on: pull_request: push: branches: - '**' tags: - '*' permissions: contents: read jobs: validate: name: Validate (${{ matrix.label }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: windows-latest label: Windows - os: macos-15-intel label: macOS (x64) - os: macos-15 label: macOS (arm64) - os: ubuntu-latest label: Linux steps: - name: Check out repository uses: actions/checkout@v4 - name: Set up Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.6 - name: Set up .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x - name: Install Linux native dependencies if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libsecret-1-dev - name: Install dependencies run: bun install --frozen-lockfile - name: Run web tests run: bun run test - name: Run sidecar tests run: bun run sidecar:test - name: Build the application run: bun run build create-release: name: Create GitHub release if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') needs: validate runs-on: ubuntu-latest permissions: contents: write steps: - name: Check out repository uses: actions/checkout@v4 - name: Create release if needed env: GH_TOKEN: ${{ github.token }} TAG_NAME: ${{ github.ref_name }} run: | if gh release view "$TAG_NAME" >/dev/null 2>&1; then echo "Release $TAG_NAME already exists." else gh release create "$TAG_NAME" --title "$TAG_NAME" --generate-notes fi publish-release-assets: name: Publish release asset (${{ matrix.label }}) if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') needs: create-release runs-on: ${{ matrix.os }} permissions: contents: write strategy: fail-fast: false matrix: include: - os: windows-latest label: Windows - os: macos-15-intel label: macOS (x64) - os: macos-15 label: macOS (arm64) - os: ubuntu-latest label: Linux steps: - name: Check out repository uses: actions/checkout@v4 - name: Set up Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.6 - name: Set up .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x - name: Install Linux native dependencies if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libsecret-1-dev - name: Install dependencies run: bun install --frozen-lockfile - name: Prepare Apple signing assets if: runner.os == 'macOS' shell: bash env: APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }} APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: | write_github_env() { local name="$1" local value="$2" local delimiter delimiter="ARYX_ENV_$(uuidgen | tr '[:lower:]' '[:upper:]')" { printf '%s<<%s\n' "$name" "$delimiter" printf '%s\n' "$value" printf '%s\n' "$delimiter" } >> "$GITHUB_ENV" } if [[ -z "$APPLE_CERT_P12_BASE64" ]]; then echo "Missing required secret: APPLE_CERT_P12_BASE64" >&2 exit 1 fi if [[ -z "$APPLE_CERT_PASSWORD" ]]; then echo "Missing required secret: APPLE_CERT_PASSWORD" >&2 exit 1 fi if [[ -z "$APPLE_API_KEY_P8" ]]; then echo "Missing required secret: APPLE_API_KEY_P8" >&2 exit 1 fi if [[ -z "$APPLE_API_KEY_ID" ]]; then echo "Missing required secret: APPLE_API_KEY_ID" >&2 exit 1 fi if [[ -z "$APPLE_API_ISSUER" ]]; then echo "Missing required secret: APPLE_API_ISSUER" >&2 exit 1 fi if [[ -z "$APPLE_TEAM_ID" ]]; then echo "Missing required secret: APPLE_TEAM_ID" >&2 exit 1 fi SOURCE_CERT_PATH="$RUNNER_TEMP/apple-signing-source.p12" CERT_PATH="$RUNNER_TEMP/apple-signing.p12" PEM_PATH="$RUNNER_TEMP/apple-signing.pem" PRECHECK_KEYCHAIN_PATH="$RUNNER_TEMP/apple-signing-preflight.keychain-db" PRECHECK_KEYCHAIN_PASSWORD="$(uuidgen)" API_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" cleanup_precheck_keychain() { security delete-keychain "$PRECHECK_KEYCHAIN_PATH" >/dev/null 2>&1 || true } trap cleanup_precheck_keychain EXIT CERT_PATH="$SOURCE_CERT_PATH" python3 - <<'PY' import base64 import binascii import os from pathlib import Path raw_value = os.environ["APPLE_CERT_P12_BASE64"] normalized_value = "".join(raw_value.split()) if not normalized_value: raise SystemExit("APPLE_CERT_P12_BASE64 is empty after whitespace normalization") try: decoded = base64.b64decode(normalized_value, validate=True) except binascii.Error: raise SystemExit("APPLE_CERT_P12_BASE64 is not valid base64") if not decoded: raise SystemExit("Decoded Apple signing certificate is empty") Path(os.environ["CERT_PATH"]).write_bytes(decoded) PY printf '%s' "$APPLE_API_KEY_P8" > "$API_KEY_PATH" if [[ ! -s "$SOURCE_CERT_PATH" ]]; then echo "Decoded Apple signing certificate file is empty." >&2 exit 1 fi if ! openssl pkcs12 -in "$SOURCE_CERT_PATH" -noout -passin env:APPLE_CERT_PASSWORD >/dev/null 2>&1; then echo "Decoded Apple signing certificate could not be opened with APPLE_CERT_PASSWORD." >&2 exit 1 fi if ! openssl pkcs12 -in "$SOURCE_CERT_PATH" -passin env:APPLE_CERT_PASSWORD -nodes -out "$PEM_PATH" >/dev/null 2>&1; then echo "Decoded Apple signing certificate could not be converted to PEM." >&2 exit 1 fi if ! openssl pkcs12 -export -out "$CERT_PATH" -in "$PEM_PATH" -passout env:APPLE_CERT_PASSWORD -macalg sha1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES >/dev/null 2>&1; then echo "Apple signing certificate could not be re-exported into a macOS-compatible PKCS#12." >&2 exit 1 fi if [[ ! -s "$CERT_PATH" ]]; then echo "Normalized Apple signing certificate file is empty." >&2 exit 1 fi if ! security create-keychain -p "$PRECHECK_KEYCHAIN_PASSWORD" "$PRECHECK_KEYCHAIN_PATH" >/dev/null 2>&1; then echo "Unable to create the macOS signing precheck keychain." >&2 exit 1 fi if ! security unlock-keychain -p "$PRECHECK_KEYCHAIN_PASSWORD" "$PRECHECK_KEYCHAIN_PATH" >/dev/null 2>&1; then echo "Unable to unlock the macOS signing precheck keychain." >&2 exit 1 fi if ! security import "$CERT_PATH" -k "$PRECHECK_KEYCHAIN_PATH" -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign >/dev/null 2>&1; then echo "Normalized Apple signing certificate is still not importable by macOS security." >&2 exit 1 fi rm -f "$SOURCE_CERT_PATH" "$PEM_PATH" cleanup_precheck_keychain trap - EXIT write_github_env "CSC_LINK" "$CERT_PATH" write_github_env "CSC_KEY_PASSWORD" "$APPLE_CERT_PASSWORD" write_github_env "APPLE_API_KEY" "$API_KEY_PATH" write_github_env "APPLE_API_KEY_ID" "$APPLE_API_KEY_ID" write_github_env "APPLE_API_ISSUER" "$APPLE_API_ISSUER" write_github_env "APPLE_TEAM_ID" "$APPLE_TEAM_ID" - name: Build and publish release artifacts env: GH_TOKEN: ${{ github.token }} run: bun run publish-release