From 96f8d7b29490f68f7bd289617c4411dbc5a68e4f Mon Sep 17 00:00:00 2001 From: DocNR Date: Thu, 30 Apr 2026 09:53:37 -0400 Subject: [PATCH 1/4] ci: add iOS Build + Test GitHub Actions workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs xcodebuild test on every PR and on main pushes. Catches the kind of failures that aren't visible from local Xcode alone: - Missing pbxproj entries for new Swift files (almost shipped a broken PR #19 due to this — the new ActivitySummary.swift / Nip19.swift needed manual entries in 5 places across the pbxproj). - Test regressions before merge. - Build breakage from Swift / Xcode version drift on the runner image. Workflow notes: - Picks the first available iPhone simulator at runtime instead of pinning to a specific name — runner images bump iPhone generations periodically and a hardcoded name (e.g. iPhone 17) breaks the workflow on rollover. - `concurrency` cancels stale runs on the same ref so we don't burn macOS minutes on superseded commits. - Uploads the xcresult bundle as an artifact only on failure (14-day retention) so test failures can be opened in Xcode locally. - Disables code signing in CI — we're only running unit tests on the simulator, no archive / distribution. Once this lands and runs green at least once, set as a required status check on `main` via: gh api repos/DocNR/clave/branches/main/protection/required_status_checks \ --method PATCH -f strict=true -F 'contexts[]=Build + unit tests' Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ios.yml | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/ios.yml diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml new file mode 100644 index 0000000..f08d523 --- /dev/null +++ b/.github/workflows/ios.yml @@ -0,0 +1,70 @@ +name: iOS Build + Test + +on: + pull_request: + push: + branches: [main] + +# Cancel in-flight runs on a branch when a new commit lands — we only care +# about the latest revision of any given PR / push. +concurrency: + group: ios-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Build + unit tests + # Use the latest macOS image available; xcodebuild + iOS Simulator + # destinations are pre-installed on GitHub Actions macOS runners. + runs-on: macos-latest + timeout-minutes: 25 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Show selected Xcode + available simulators + # Capture environment up-front so a runner-image change that breaks + # the build is easy to diagnose from CI logs. + run: | + sudo xcode-select -p + xcodebuild -version + xcrun simctl list devices available | head -40 + + - name: Resolve a usable iOS Simulator destination + id: pick-sim + # `name=iPhone 17` is fragile — the runner image may be on iPhone 16 + # or earlier. Pick the first available iPhone simulator at runtime + # so the workflow doesn't break every Xcode-image bump. + run: | + DEVICE=$(xcrun simctl list devices available --json \ + | python3 -c 'import json,sys; d=json.load(sys.stdin)["devices"]; + for runtime, devs in d.items(): + if "iOS" not in runtime: continue + for dev in devs: + if dev.get("isAvailable") and "iPhone" in dev["name"]: + print(dev["name"]); sys.exit(0) + sys.exit(1)') + echo "Using simulator: $DEVICE" + echo "device=$DEVICE" >> "$GITHUB_OUTPUT" + + - name: xcodebuild test + env: + SIM_DEVICE: ${{ steps.pick-sim.outputs.device }} + run: | + xcodebuild test \ + -project Clave.xcodeproj \ + -scheme Clave \ + -sdk iphonesimulator \ + -destination "platform=iOS Simulator,name=$SIM_DEVICE,OS=latest" \ + -skip-testing:ClaveUITests \ + -resultBundlePath TestResults.xcresult \ + CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO + + - name: Upload xcresult bundle on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: TestResults-${{ github.run_id }} + path: TestResults.xcresult + retention-days: 14 From 868c670aa0e9db29d39aefd427f023b97c350a9c Mon Sep 17 00:00:00 2001 From: DocNR Date: Thu, 30 Apr 2026 09:57:33 -0400 Subject: [PATCH 2/4] =?UTF-8?q?fix(ci):=20lower=20ClaveTests=20deployment?= =?UTF-8?q?=20target=2026.4=20=E2=86=92=2017.6=20to=20match=20main=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ClaveTests inherited the project-level baseline of iOS 26.4. The main app and NSE both override to 17.6. CI runner (macos-latest, Xcode 16.4, iOS Simulator 26.2) failed: Cannot test target "ClaveTests" on "iPhone 16 Pro": iPhone 16 Pro's iOS Simulator 26.2 doesn't match ClaveTests's iOS Simulator 26.4 deployment target. Tests should match the main app's deployment target so they exercise the same constraints as production. The previous 26.4 was likely an auto-bump artifact when the project was opened in a newer Xcode. Co-Authored-By: Claude Opus 4.7 (1M context) --- Clave.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Clave.xcodeproj/project.pbxproj b/Clave.xcodeproj/project.pbxproj index 22b7996..c5a5e69 100644 --- a/Clave.xcodeproj/project.pbxproj +++ b/Clave.xcodeproj/project.pbxproj @@ -708,7 +708,7 @@ CURRENT_PROJECT_VERSION = 31; DEVELOPMENT_TEAM = 944AF56S27; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; MARKETING_VERSION = 0.1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.nostr.ClaveTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -730,7 +730,7 @@ CURRENT_PROJECT_VERSION = 31; DEVELOPMENT_TEAM = 944AF56S27; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; MARKETING_VERSION = 0.1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.nostr.ClaveTests; PRODUCT_NAME = "$(TARGET_NAME)"; From 45bf3d29e4bde1f28b23e126f6f5552ad325af9b Mon Sep 17 00:00:00 2001 From: DocNR Date: Thu, 30 Apr 2026 09:59:40 -0400 Subject: [PATCH 3/4] fix(ci): add -skipPackagePluginValidation + -skipMacroValidation flags Xcode 14+ requires explicit user approval for SwiftPM plugins (security feature). Local devs click 'Trust & Enable' once; CI needs the explicit flags to skip the prompt. Affects swift-secp256k1's SharedSourcesPlugin. --- .github/workflows/ios.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index f08d523..76ea023 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -58,6 +58,8 @@ jobs: -sdk iphonesimulator \ -destination "platform=iOS Simulator,name=$SIM_DEVICE,OS=latest" \ -skip-testing:ClaveUITests \ + -skipPackagePluginValidation \ + -skipMacroValidation \ -resultBundlePath TestResults.xcresult \ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO From d9b9a2665006ffd8e21bafc896ba2500115581a7 Mon Sep 17 00:00:00 2001 From: DocNR Date: Thu, 30 Apr 2026 10:03:18 -0400 Subject: [PATCH 4/4] fix(ci): pre-trust SwiftPM plugin fingerprints + resolve packages explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The failure mode was sneaky: -skipPackagePluginValidation lets the xcodebuild step proceed without the trust prompt, but swift-secp256k1's SharedSourcesPlugin still silently degrades — libsecp256k1.o builds without ECDH/extrakeys/recovery modules, so symbols like _secp256k1_ecdh are undefined at link time when ClaveNSE/Clave try to use P256K. Setting IDESkipPackagePluginFingerprintValidatation + the SCM defaults BEFORE resolving packages allows plugins to run with full define expansion. Then we resolve packages in a separate step so any plugin errors surface explicitly instead of getting buried in the test step. --- .github/workflows/ios.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 76ea023..fcc17c5 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -48,6 +48,26 @@ jobs: echo "Using simulator: $DEVICE" echo "device=$DEVICE" >> "$GITHUB_OUTPUT" + - name: Pre-trust Swift package plugins + # Without trusted fingerprints, `swift-secp256k1`'s SharedSourcesPlugin + # silently fails to enable optional libsecp256k1 modules (ECDH, + # extrakeys, recovery, schnorrsig) — local-only symbols like + # `_secp256k1_ecdh` end up undefined at link time. Setting these + # defaults BEFORE resolving packages allows plugins to run with full + # define expansion. `-skipPackagePluginValidation` alone is not enough. + run: | + defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES + defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES + defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES + + - name: Resolve packages + run: | + xcodebuild -resolvePackageDependencies \ + -project Clave.xcodeproj \ + -scheme Clave \ + -skipPackagePluginValidation \ + -skipMacroValidation + - name: xcodebuild test env: SIM_DEVICE: ${{ steps.pick-sim.outputs.device }}