From 2508c77f175de67ac64cc371d1db1da84d49babd Mon Sep 17 00:00:00 2001 From: SuperDooper86 Date: Wed, 9 Sep 2026 23:45:46 +0200 Subject: [PATCH] ci: build and smoke test main on the Gitea Mac runner --- .gitea/workflows/build.yml | 49 ++++++++++++++++++++++++++++++++++++++ scripts/smoke_test_api.py | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 scripts/smoke_test_api.py diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..c7d2281 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,49 @@ +name: Build + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +jobs: + macos: + runs-on: macos-arm64 + steps: + - uses: actions/checkout@v4 + - name: Build Meetingnotes + run: >- + xcodebuild + -project Meetingnotes.xcodeproj + -scheme meetingnotes + -configuration Release + -destination 'generic/platform=macOS' + -derivedDataPath "$RUNNER_TEMP/DerivedData" + ARCHS="arm64 x86_64" + ONLY_ACTIVE_ARCH=NO + PRODUCT_BUNDLE_IDENTIFIER=net.jamesbone.meetingnotes.ci + CODE_SIGNING_ALLOWED=NO + build + - name: Sign test build + run: | + app_path="$RUNNER_TEMP/DerivedData/Build/Products/Release/Meetingnotes.app" + codesign --force --deep --sign - \ + --entitlements meetingnotes/meetingnotes.entitlements \ + "$app_path" + codesign --verify --deep --strict "$app_path" + codesign -d --entitlements :- "$app_path" 2>&1 \ + | grep -q 'com.apple.security.network.server' + - name: Smoke test local API + run: python3 scripts/smoke_test_api.py "$RUNNER_TEMP/DerivedData/Build/Products/Release/Meetingnotes.app" + - name: Package test build + run: | + app_path="$RUNNER_TEMP/DerivedData/Build/Products/Release/Meetingnotes.app" + ditto -c -k --sequesterRsrc --keepParent \ + "$app_path" "$RUNNER_TEMP/Meetingnotes-macOS.zip" + - name: Upload test build + uses: actions/upload-artifact@v4 + with: + name: Meetingnotes-macOS-${{ github.sha }} + path: ${{ runner.temp }}/Meetingnotes-macOS.zip + retention-days: 30 diff --git a/scripts/smoke_test_api.py b/scripts/smoke_test_api.py new file mode 100644 index 0000000..6ec44c4 --- /dev/null +++ b/scripts/smoke_test_api.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Check the CI app's unauthenticated API without changing saved preferences.""" +import json +import socket +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.request +from pathlib import Path + +app = Path(sys.argv[1]) / "Contents/MacOS/Meetingnotes" +with socket.socket() as listener: + listener.bind(("127.0.0.1", 0)) + port = listener.getsockname()[1] +with tempfile.TemporaryFile() as log: + process = subprocess.Popen( + [str(app), "-muteDeckAPIEnabled", "YES", "-muteDeckAPIPort", str(port)], + stdout=log, stderr=subprocess.STDOUT, + ) + try: + for attempt in range(30): + if process.poll() is not None: + raise RuntimeError("CI app exited before the API became ready") + try: + with urllib.request.urlopen(f"http://127.0.0.1:{port}/api/info", timeout=2) as response: + info = json.load(response) + break + except (urllib.error.URLError, TimeoutError): + time.sleep(1) + else: + raise RuntimeError("CI API did not become ready") + assert info["name"] == "MeetingDebrief", "Unexpected API identity" + try: + urllib.request.urlopen(f"http://127.0.0.1:{port}/api/recording/status", timeout=2) + except urllib.error.HTTPError as error: + assert error.code == 401, f"Unexpected status: {error.code}" + else: + raise RuntimeError("Recording status allowed an unauthenticated request") + print("Local API readiness and authentication checks passed") + finally: + if process.poll() is None: + process.terminate() + try: + process.wait(timeout=10) + except subprocess.TimeoutExpired: + process.kill() + process.wait()