From adc5b4d6deb1d21027c618776ad20fef5accef78 Mon Sep 17 00:00:00 2001 From: Luke van Enkhuizen <78032955+SheetMetalConnect@users.noreply.github.com> Date: Sun, 24 May 2026 21:27:35 +0200 Subject: [PATCH 1/2] ci(native): add macOS native build smoke lane + AS bootstrap repair (ERY-99) Adds a repo-owned GitHub Actions lane (macos-15) that runs ios:init, builds the iOS simulator target, and assembles Android debug+release APKs so the ERY-71 native packaging changes get real build evidence. Hardens scripts/ios-init.sh to restore the darwin-arm64 Rollup/SWC bindings npm ci skips on Apple Silicon. Validation doc updated with the lane decision and local host evidence. Co-Authored-By: Paperclip --- .github/workflows/native-build-smoke.yml | 103 ++++++++++++++++++ ...5-24-ery-71-native-packaging-validation.md | 98 +++++------------ scripts/ios-init.sh | 28 +++++ 3 files changed, 156 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/native-build-smoke.yml diff --git a/.github/workflows/native-build-smoke.yml b/.github/workflows/native-build-smoke.yml new file mode 100644 index 00000000..61e60882 --- /dev/null +++ b/.github/workflows/native-build-smoke.yml @@ -0,0 +1,103 @@ +name: Native Build Smoke + +on: + workflow_dispatch: + pull_request: + paths: + - .github/workflows/native-build-smoke.yml + - android/** + - capacitor.config.ts + - package.json + - package-lock.json + - public/** + - scripts/ios-init.sh + - src/native/** + - src/pages/mobile/** + - src/styles/mobile.css + - src/styles/mobile-ios.css + +jobs: + native-build-smoke: + name: iOS bootstrap + Android smoke + runs-on: macos-15 + timeout-minutes: 60 + permissions: + contents: read + + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + cache: npm + + - name: Setup Java 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + + - name: Capture native toolchain versions + shell: bash + run: | + set -euo pipefail + mkdir -p artifacts + { + echo "Node $(node -v)" + echo "npm $(npm -v)" + xcodebuild -version + echo "CocoaPods $(pod --version)" + java -version + echo "ANDROID_HOME=${ANDROID_HOME:-unset}" + ls "$ANDROID_HOME/platforms" | grep "android-35" + ls "$ANDROID_HOME/build-tools" | grep "35.0" + } 2>&1 | tee artifacts/toolchain.log + + - name: Bootstrap iOS workspace + shell: bash + run: | + set -euo pipefail + mkdir -p artifacts + npm run ios:init 2>&1 | tee artifacts/ios-init.log + + - name: Build iOS simulator target + shell: bash + run: | + set -euo pipefail + mkdir -p artifacts + xcodebuild \ + -workspace ios/App/App.xcworkspace \ + -scheme App \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination "generic/platform=iOS Simulator" \ + -derivedDataPath ios/App/build \ + CODE_SIGNING_ALLOWED=NO \ + build 2>&1 | tee artifacts/ios-build.log | xcbeautify + + - name: Assemble Android debug APK + shell: bash + run: | + set -euo pipefail + mkdir -p artifacts + npm run android:assemble:debug 2>&1 | tee artifacts/android-debug.log + + - name: Assemble Android release APK + shell: bash + run: | + set -euo pipefail + mkdir -p artifacts + npm run android:assemble:release 2>&1 | tee artifacts/android-release.log + + - name: Upload native smoke artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: native-build-smoke-${{ github.run_id }} + if-no-files-found: warn + path: | + artifacts/*.log + android/app/build/outputs/** + ios/App/build/** diff --git a/docs/2026-05-24-ery-71-native-packaging-validation.md b/docs/2026-05-24-ery-71-native-packaging-validation.md index 2f3caaed..3a1586b0 100644 --- a/docs/2026-05-24-ery-71-native-packaging-validation.md +++ b/docs/2026-05-24-ery-71-native-packaging-validation.md @@ -1,81 +1,33 @@ -# ERY-71 — Native Packaging & Self-Host Distribution Validation Note +# ERY-71 Native Packaging Validation -Date: 2026-05-24 -Owner: Engineer -Issue: [ERY-71](/ERY/issues/ERY-71) -Parent: [ERY-55](/ERY/issues/ERY-55) -Branch: `engineer/ery-71-native-packaging` (worktree off `17575bb`) +## Lane decision -This note replaces reliance on the old umbrella PR description as the validation -record for the native branch. It lists exactly what changed, what was verified -here, and what is explicitly blocked on build tooling. +- Selected validation lane: GitHub Actions macOS 15 (`.github/workflows/native-build-smoke.yml`) +- Why: the current validation host only has Command Line Tools; it does not have `Xcode.app`, CocoaPods, Java, `sdkmanager`, or `adb`, so it cannot satisfy the iOS/Android acceptance checks locally. +- Scope of the lane: pin Node 20 + Java 17 on GitHub's macOS 15 runner, run `npm run ios:init`, build the generated iOS workspace for the simulator, then run `npm run android:assemble:debug` and `npm run android:assemble:release`, and upload logs/artifacts for review. -## Changes in this branch +## Local host evidence captured on 2026-05-24 -| Deliverable | Change | File(s) | -|---|---|---| -| ML Kit offline barcode | Wired the previously-dead `mlkitBarcodeDependencies` placeholder into a real `com.google.mlkit.vision.DEPENDENCIES` meta-data so Google Play fetches the model at install time, not first scan. Documented the GMS caveat + bundled-model path for air-gapped fleets (claim narrowed honestly). | `android/app/src/main/AndroidManifest.xml`, `android/app/build.gradle`, `docs/ANDROID.md` | -| LAN self-host distribution | Removed the fabricated sample-IP RFC1918 enumeration (false coverage, no CIDR match). Release builds are HTTPS-only; dev/live-reload cleartext is confined to `debug-overrides`; plain-HTTP LAN hosts are supported per-host via a documented custom-build template or TLS fronting (least privilege). | `android/app/src/main/res/xml/network_security_config.xml`, `docs/ANDROID.md`, `docs/IOS.md` | -| Version alignment | `android/app/build.gradle` versionName `0.5.1 → 0.5.2`, README badge + status line `0.5.1 → 0.5.2`. Now consistent with `package.json` (0.5.2) and `CHANGELOG` (0.5.2). | `android/app/build.gradle`, `README.md` | +| Check | Result | Evidence | +| --- | --- | --- | +| `xcodebuild -version` | FAIL | active developer dir is `/Library/Developer/CommandLineTools`; `Xcode.app` is not installed | +| `pod --version` | FAIL | `pod: command not found` | +| `java -version` | FAIL | no Java runtime present | +| `sdkmanager --version` | FAIL | `sdkmanager: command not found` | +| `adb version` | FAIL | `adb: command not found` | +| `npm run build` | PASS | build succeeds after explicitly restoring the missing darwin-arm64 Rollup + SWC native bindings that `npm ci` skipped on this Apple Silicon host | +| `npm run ios:init` on this host | FAIL | after the Rollup/SWC repair, the script reaches `npx cap add ios` and then stops at the real blocker: `CocoaPods is not installed` | +| `ios/` workspace produced locally | FAIL | `npx cap add ios` aborted before creating `ios/` because CocoaPods is absent on this host | -Note on target version: surfaces are aligned to the current canonical `0.5.2`. -The coordinated bump to `0.6.0` (package.json + gradle versionName + versionCode -increment + README + a new CHANGELOG section) is a release-tagging step under -[ERY-55](/ERY/issues/ERY-55) and should land atomically at the release cut, not -piecemeal here. Flagged for CTO decision. +## Native validation status -## What was verified here (pass) +| Check | Status | Evidence path | +| --- | --- | --- | +| `npm run ios:init` in a native-capable lane | PENDING | first run of `.github/workflows/native-build-smoke.yml` | +| iOS simulator build of `ios/App/App.xcworkspace` | PENDING | first run of `.github/workflows/native-build-smoke.yml` | +| `npm run android:assemble:debug` | PENDING | first run of `.github/workflows/native-build-smoke.yml` | +| `npm run android:assemble:release` | PENDING | first run of `.github/workflows/native-build-smoke.yml` | -- **XML well-formedness** — `network_security_config.xml` and - `AndroidManifest.xml` parse cleanly (`python3 xml.dom.minidom`). PASS. -- **Placeholder wiring** — `mlkitBarcodeDependencies` is now both defined - (build.gradle) and consumed (`${mlkitBarcodeDependencies}` in the manifest - meta-data). Confirmed by grep. PASS. -- **Version consistency** — package.json / build.gradle / README / CHANGELOG all - report `0.5.2`. PASS. -- **Least-privilege review** — release artifacts now have zero blanket cleartext - exception; cleartext is debug-only or one explicit operator-chosen host. PASS. +## Remaining Apple-specific gaps -## What is BLOCKED on build tooling (fail / not-run) - -This host has **Command Line Tools only (no Xcode.app), no CocoaPods, no JDK, no -Android SDK**. Probe output: - -``` -$ xcode-select -p -> /Library/Developer/CommandLineTools -$ xcodebuild -version -> error: requires Xcode (only CLT present) -$ pod --version -> pod: command not found -$ java -version -> Unable to locate a Java Runtime -$ echo $ANDROID_HOME -> (empty) -``` - -Consequently the following could not be executed and remain unproven: - -1. **iOS workspace bootstrap** — `npm run ios:init` (`npx cap add ios` + `pod - install`) needs Xcode 15+ and CocoaPods. The `ios/` workspace therefore still - does not exist as a checked-in/validated artifact. NOT RUN — blocked on - toolchain. -2. **Android build smoke** — `npm run android:assemble:debug` / `:release` - (gradle) needs JDK 17 + Android SDK (API 35). The ML Kit meta-data and the new - network-security config are correct by inspection and parse cleanly, but have - not been proven to compile/package/merge in a real build. NOT RUN — blocked on - toolchain. - -## Remaining Apple-specific blockers for TestFlight/App Store - -Already enumerated in `docs/IOS.md` ("Remaining iOS-only gaps") and unchanged by -this branch — they require a Mac with Xcode + an Apple Developer account: - -1. APNs auth key + `PushNotifications.register()` wiring. -2. Universal Links / Associated Domains (`applinks:app.eryxon.eu` entitlement + - `/.well-known/apple-app-site-association`). -3. Splash screen assets + 1024×1024 app icon set. -4. `Info.plist` localizations (`CFBundleLocalizations = [en, nl, de]`). - -## Recommended next steps - -- CTO: approve commit+push of `engineer/ery-71-native-packaging`. -- Provision a native-build lane (local Mac with Xcode 15+/CocoaPods and JDK 17 + - Android SDK, or a CI macOS runner) so iOS bootstrap and the Android build smoke - can produce pass/fail evidence. Tracked as the ERY-71 build-validation follow-up. -- At the v0.6 release cut, perform the coordinated `0.6.0` version bump atomically. +- The pre-TestFlight items remain explicitly tracked in [docs/IOS.md](/Users/vanenkhuizen/Documents/GitHub/products/eryxon-flow/docs/IOS.md): APNs wiring, Associated Domains / Universal Links, splash/icon assets, and App Store metadata polish. diff --git a/scripts/ios-init.sh b/scripts/ios-init.sh index f3385249..e368d6a9 100755 --- a/scripts/ios-init.sh +++ b/scripts/ios-init.sh @@ -14,6 +14,33 @@ if ! command -v node >/dev/null 2>&1; then exit 1 fi +ensure_native_build_bindings() { + require_binding() { + local package_name="$1" + node -e "require(require.resolve(process.argv[1]));" "$package_name" + } + + # npm ci intermittently skips native optional packages on Apple Silicon. + # Vite then fails before Capacitor even runs because Rollup and SWC cannot + # load their darwin-arm64 bindings. Repair them once here so native + # bootstrap stays deterministic on fresh machines and CI runners. + if [[ "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ]]; then + if ! require_binding "@rollup/rollup-darwin-arm64" >/dev/null 2>&1 \ + || ! require_binding "@swc/core-darwin-arm64" >/dev/null 2>&1; then + local rollup_version + local swc_version + rollup_version="$(node -p "require('./node_modules/rollup/package.json').version")" + swc_version="$(node -p "require('./node_modules/@swc/core/package.json').version")" + echo "▶ Repairing darwin-arm64 native bindings for Rollup and SWC..." + npm install --no-save \ + "@rollup/rollup-darwin-arm64@${rollup_version}" \ + "@swc/core-darwin-arm64@${swc_version}" + require_binding "@rollup/rollup-darwin-arm64" >/dev/null + require_binding "@swc/core-darwin-arm64" >/dev/null + fi + fi +} + if [[ "${OSTYPE:-}" != darwin* ]]; then echo "⚠ Capacitor's iOS toolchain only runs on macOS — this script will" >&2 echo " configure the project but you must finish 'cap add ios' on a Mac." >&2 @@ -25,6 +52,7 @@ if [[ -f package-lock.json ]]; then else npm install fi +ensure_native_build_bindings echo "▶ Building production web bundle..." npm run build From 66dcefbbf7c4d62630eefbf5552145e2648f5d74 Mon Sep 17 00:00:00 2001 From: Luke van Enkhuizen <78032955+SheetMetalConnect@users.noreply.github.com> Date: Sun, 24 May 2026 21:30:29 +0200 Subject: [PATCH 2/2] docs(native): record first macOS smoke-lane evidence (ERY-99) Run 26370608416 (macos-15): toolchain provisioned OK, cap add ios + web build OK, pod install FAILED on GoogleMLKit/BarcodeScanning 7.0.0 deployment-target mismatch; Android steps skipped (job halts on first failure). Captures root cause, proposed iOS fix, and a workflow split follow-up so Android evidence is no longer masked by the iOS step. Co-Authored-By: Paperclip --- ...5-24-ery-71-native-packaging-validation.md | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/2026-05-24-ery-71-native-packaging-validation.md b/docs/2026-05-24-ery-71-native-packaging-validation.md index 3a1586b0..cf335e0e 100644 --- a/docs/2026-05-24-ery-71-native-packaging-validation.md +++ b/docs/2026-05-24-ery-71-native-packaging-validation.md @@ -21,12 +21,27 @@ ## Native validation status -| Check | Status | Evidence path | +First macOS lane run: GitHub Actions run [`26370608416`](https://github.com/SheetMetalConnect/eryxon-flow/actions/runs/26370608416) on `macos-15` (PR [#598](https://github.com/SheetMetalConnect/eryxon-flow/pull/598), `pull_request` trigger), 2026-05-24. Conclusion: **failure** at iOS bootstrap; downstream steps skipped. Logs/artifacts: `native-build-smoke-26370608416`. + +| Check | Status | Evidence | | --- | --- | --- | -| `npm run ios:init` in a native-capable lane | PENDING | first run of `.github/workflows/native-build-smoke.yml` | -| iOS simulator build of `ios/App/App.xcworkspace` | PENDING | first run of `.github/workflows/native-build-smoke.yml` | -| `npm run android:assemble:debug` | PENDING | first run of `.github/workflows/native-build-smoke.yml` | -| `npm run android:assemble:release` | PENDING | first run of `.github/workflows/native-build-smoke.yml` | +| macOS toolchain present (Xcode, CocoaPods, Java 17, Android SDK API 35) | PASS | "Capture native toolchain versions" step succeeded — confirms the lane is correctly provisioned | +| `npm run ios:init` (web build + `npx cap add ios`) | PARTIAL | web `vite build` PASS, `npx cap add ios` PASS (Xcode project + assets generated), then `pod install` FAILED | +| iOS `pod install` (CocoaPods dependency resolution) | FAIL | `GoogleMLKit/BarcodeScanning (= 7.0.0)` — pulled transitively by `@capacitor-mlkit/barcode-scanning` 7.5.0 — "required a higher minimum deployment target" than the Capacitor-generated Podfile's iOS target. Root cause below. | +| iOS simulator build of `ios/App/App.xcworkspace` | SKIPPED | iOS step failed before a workspace existed | +| `npm run android:assemble:debug` | SKIPPED | job halts on first failed step; Android never ran this run | +| `npm run android:assemble:release` | SKIPPED | job halts on first failed step; Android never ran this run | + +## Root cause + proposed fix (iOS) + +- `@capacitor-mlkit/barcode-scanning@7.5.0` depends on `GoogleMLKit/BarcodeScanning = 7.0.0`, which requires a higher minimum iOS deployment target than the default Capacitor Podfile emits. +- Fix: pin the generated iOS deployment target high enough for GoogleMLKit 7.0.0 (iOS 15.5+). Because `ios/` is not committed and `npx cap add ios` runs `pod install` during `add`, the target must be injected before pod resolution — e.g. set a Capacitor iOS `platform`/deployment-target config or generate the Podfile with `platform :ios, '15.5'` (+ a `post_install` target override) prior to `cap add ios`. This is a native-packaging change owned by ERY-71 follow-up, not the smoke-lane itself. + +## Workflow follow-up + +- The iOS failure masked Android validation (steps skipped). Split iOS and Android into independent jobs (or `if: always()` gating) so a failure in one still produces evidence for the other on the same run. + +## Remaining Apple-specific gaps ## Remaining Apple-specific gaps