Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2b31cd5
test(mobile): add test coverage for TradeDetailScreen (#281)
lockoabosede8-byte Oct 21, 2026
dfb2d15
test(mobile): add test coverage for WalletConnectScreen (#283)
lockoabosede8-byte Oct 21, 2026
f34fb97
feat(mobile): implement Freighter signing for admin contract actions …
lockoabosede8-byte Oct 21, 2026
de66a27
docs(frontend): audit frontend/docs against current app structure (#275)
lockoabosede8-byte Oct 21, 2026
3e981ca
ci: fix failing CI jobs (scripts tests, flag drift, backend gate, mob…
lockoabosede8-byte Oct 21, 2026
3543a90
test(mobile): raise jest timeout to 15s to stop cold-start flakes in CI
lockoabosede8-byte Oct 22, 2026
70d25d2
ci(mobile): add app icon assets and Android system image for E2E jobs
lockoabosede8-byte Oct 22, 2026
4cd2d25
fix(ci): add missing Trade.version column, pin gesture-handler for RN…
Hollujay Oct 25, 2026
046f550
ci: raise visual regression job timeout to 20 minutes
Hollujay Oct 25, 2026
9414935
fix(ci): resolve PR 375 migration and mobile failures
Hollujay Oct 26, 2026
9650b1c
fix(ci): stabilize script and mobile e2e gates
Hollujay Oct 26, 2026
b9f328d
fix(ci): align native security and simulator checks
Hollujay Oct 26, 2026
ad46fd7
fix(mobile): align Expo Android Kotlin compiler
Hollujay Oct 26, 2026
af71858
fix(mobile): allow config plugin lint import
Hollujay Oct 26, 2026
25c260a
fix(ci): invalidate stale Expo native caches
Hollujay Oct 26, 2026
d9ff392
fix(mobile): persist Expo Kotlin version in Gradle
Hollujay Oct 26, 2026
791d0ea
fix(ci): enforce Android Kotlin compiler version
Hollujay Oct 26, 2026
b11c3d2
fix(ci): pin compatible native build toolchains
Hollujay Oct 27, 2026
d6ee21b
fix(mobile): remove duplicate Detox device key
Hollujay Oct 27, 2026
9f5ce00
fix(mobile): use Android-safe notification sound name
Hollujay Oct 27, 2026
143d05f
fix(ci): align React Native Kotlin version catalog
Hollujay Oct 27, 2026
031ac1f
fix(ci): patch React Native Gradle plugin Kotlin catalog
Hollujay Oct 27, 2026
29f67f4
fix(mobile): configure Expo Android autolinking
Hollujay Oct 27, 2026
4be408b
fix(mobile): load React Native config as CommonJS
Hollujay Oct 27, 2026
ecc0a19
fix(ci): patch expo-device for Xcode 16.4
Hollujay Oct 27, 2026
1151da1
fix(mobile): resolve Android native library collision
Hollujay Oct 27, 2026
019abad
fix(android): resolve duplicate fbjni library
Hollujay Oct 27, 2026
7978ea8
fix(ci): build only Android app test artifacts
Hollujay Oct 27, 2026
fb130be
fix(ci): skip visual regression for docs-only changes
Hollujay Oct 27, 2026
7affc9a
fix(e2e): honor Detox launch arguments
Hollujay Oct 27, 2026
5ee832c
fix(e2e): accept parsed boolean launch mode
Hollujay Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 95 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ jobs:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
frontend_visual: ${{ steps.filter.outputs.frontend_visual }}
backend: ${{ steps.filter.outputs.backend }}
backend_code: ${{ steps.filter.outputs.backend_code }}
mobile: ${{ steps.filter.outputs.mobile }}
contracts: ${{ steps.filter.outputs.contracts }}
steps:
Expand All @@ -56,9 +58,32 @@ jobs:
frontend:
- 'frontend/**'
- 'frontend/pnpm-lock.yaml'
frontend_visual:
- 'frontend/src/**'
- 'frontend/public/**'
- 'frontend/tests/**'
- 'frontend/package.json'
- 'frontend/pnpm-lock.yaml'
- 'frontend/playwright.config.*'
- 'frontend/next.config.*'
- 'frontend/tailwind.config.*'
- 'frontend/postcss.config.*'
- 'frontend/tsconfig.json'
backend:
- 'backend/**'
- 'backend/pnpm-lock.yaml'
# Database migrations still run for any backend change, but the
# TypeScript/test suite is only needed when executable backend
# code or its generated Prisma input changes.
backend_code:
- 'backend/src/**'
- 'backend/package.json'
- 'backend/pnpm-lock.yaml'
- 'backend/tsconfig.json'
- 'backend/jest.config.js'
- 'backend/eslint.config.*'
- 'backend/prisma/schema.prisma'
- 'backend/prisma/seed.ts'
mobile:
- 'mobile/**'
- 'mobile/pnpm-lock.yaml'
Expand Down Expand Up @@ -267,8 +292,8 @@ jobs:
name: Visual Regression (Playwright)
runs-on: ubuntu-latest
needs: [changes, frontend]
if: needs.changes.outputs.frontend == 'true'
timeout-minutes: 15
if: needs.changes.outputs.frontend_visual == 'true'
timeout-minutes: 20
defaults:
run:
working-directory: frontend
Expand Down Expand Up @@ -396,17 +421,20 @@ jobs:
run: pnpm exec prisma migrate deploy

- name: Build
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
run: pnpm build

- name: Prepare backend test database
if: needs.changes.outputs.backend == 'true'
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test
run: pnpm exec prisma db push
# This database is created for the job and contains no data. Prisma
# otherwise blocks on the warning for adding the RefreshToken.jti
# unique constraint, even though accepting it cannot discard test data.
run: pnpm exec prisma db push --accept-data-loss

- name: Test
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
env:
NODE_ENV: test
JWT_SECRET: test-jwt-secret-value-with-minimum-length-32
Expand All @@ -416,7 +444,7 @@ jobs:
run: pnpm exec jest --config jest.config.js --forceExit --detectOpenHandles --json --outputFile=jest-results.json || true

- name: Detect flake candidates (backend)
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
run: |
node ../scripts/flake-reporter.mjs \
--results jest-results.json \
Expand All @@ -425,7 +453,7 @@ jobs:
--scope backend | tee -a $GITHUB_STEP_SUMMARY

- name: Fail if tests failed (deferred from --json run)
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
run: |
node -e "
const r = require('./jest-results.json');
Expand All @@ -436,41 +464,41 @@ jobs:
"

- name: Upload flake report (backend)
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
uses: actions/upload-artifact@v4
with:
name: flake-report-backend-${{ github.run_number }}
path: reports/flake/backend-${{ github.run_number }}.json
retention-days: 30

- name: Enforce Admin Regression Test Policy
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
run: |
chmod +x ../scripts/check-admin-test-coverage.sh
../scripts/check-admin-test-coverage.sh

- name: Validate example env files against the schema
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
env:
NODE_ENV: test
run: pnpm tsx scripts/validate-env-examples.ts

- name: Admin auth regression suite
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
env:
NODE_ENV: test
JWT_SECRET: test-jwt-secret-value-with-minimum-length-32
run: pnpm jest --config jest.config.js --forceExit --detectOpenHandles --testPathPatterns='admin\.auth\.ci-regression' --verbose

- name: Validate admin OpenAPI docs
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
env:
NODE_ENV: test
JWT_SECRET: test-jwt-secret-value-with-minimum-length-32
run: pnpm jest --config jest.config.js --forceExit --detectOpenHandles --testPathPatterns='openapi\.(drift|docs)\.test' --verbose

- name: Upload coverage report
if: needs.changes.outputs.backend == 'true'
if: needs.changes.outputs.backend_code == 'true'
uses: actions/upload-artifact@v4
with:
name: backend-coverage
Expand Down Expand Up @@ -527,7 +555,7 @@ jobs:

mobile-e2e-ios:
name: Mobile E2E Smoke — iOS
runs-on: macos-latest
runs-on: macos-15
needs: [changes, mobile]
if: needs.changes.outputs.mobile == 'true'
timeout-minutes: 25
Expand All @@ -552,10 +580,36 @@ jobs:
- name: Install deps
run: pnpm install --frozen-lockfile

# Expo SDK 52's expo-device uses the removed TARGET_OS_SIMULATOR macro;
# Xcode 16.4 requires Swift's targetEnvironment condition instead.
- name: Patch expo-device for Xcode 16.4
run: |
node <<'NODE'
const fs = require('fs');
const path = require('path');
const packagePath = require.resolve('expo-device/package.json');
const sourcePath = path.join(path.dirname(packagePath), 'ios', 'UIDevice.swift');
const source = fs.readFileSync(sourcePath, 'utf8');
const legacy = ' return TARGET_OS_SIMULATOR != 0';
const fixed = [
' #if targetEnvironment(simulator)',
' return true',
' #else',
' return false',
' #endif',
].join('\n');
if (source.includes(legacy)) {
fs.writeFileSync(sourcePath, source.replace(legacy, fixed));
} else if (!source.includes('#if targetEnvironment(simulator)')) {
throw new Error(`Unexpected expo-device source: ${sourcePath}`);
}
console.log(`Patched ${sourcePath}`);
NODE

- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
xcode-version: '16.4'

# macOS 26 runner images no longer ship two tools this job needs:
# xcpretty (the release gem set was dropped from the image) and
Expand All @@ -575,7 +629,7 @@ jobs:
uses: actions/cache@v4
with:
path: mobile/ios
key: mobile-expo-prebuild-ios-${{ hashFiles('mobile/app.config.ts', 'mobile/app.json', 'mobile/package.json') }}
key: mobile-expo-prebuild-ios-${{ hashFiles('mobile/app.json', 'mobile/app.config.ts', 'mobile/plugins/**', 'mobile/package.json') }}

- name: Expo prebuild (iOS)
run: pnpm expo prebuild --platform ios --non-interactive
Expand All @@ -587,6 +641,7 @@ jobs:
# runner actually has instead of hardcoding "iPhone 15, OS=17.5".
- name: Resolve available iOS simulator
run: |
set -euo pipefail
RESOLVED=$(xcrun simctl list devices available --json | node -e '
let raw = "";
process.stdin.on("data", (c) => (raw += c)).on("end", () => {
Expand Down Expand Up @@ -617,6 +672,7 @@ jobs:

- name: Build iOS app for simulator
run: |
set -o pipefail
xcodebuild \
-workspace ios/Amana.xcworkspace \
-scheme Amana \
Expand Down Expand Up @@ -708,17 +764,33 @@ jobs:
uses: actions/cache@v4
with:
path: mobile/android
key: mobile-expo-prebuild-android-${{ hashFiles('mobile/app.config.ts', 'mobile/app.json', 'mobile/package.json') }}
key: mobile-expo-prebuild-android-${{ hashFiles('mobile/app.json', 'mobile/app.config.ts', 'mobile/plugins/**', 'mobile/package.json') }}
restore-keys: mobile-expo-prebuild-android-

- name: Expo prebuild (Android)
run: pnpm expo prebuild --platform android --non-interactive
env:
CI: true

- name: Align Android Kotlin compiler
run: |
sed -i "s/kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.24'/kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'/" android/build.gradle
if grep -q '^android.kotlinVersion=' android/gradle.properties; then
sed -i 's/^android.kotlinVersion=.*/android.kotlinVersion=1.9.25/' android/gradle.properties
else
echo 'android.kotlinVersion=1.9.25' >> android/gradle.properties
fi
sed -i 's/^kotlin = "1.9.24"$/kotlin = "1.9.25"/' node_modules/react-native/gradle/libs.versions.toml
REACT_NATIVE_GRADLE_PLUGIN_PACKAGE=$(node --print "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
REACT_NATIVE_GRADLE_PLUGIN_CATALOG="${REACT_NATIVE_GRADLE_PLUGIN_PACKAGE%/package.json}/gradle/libs.versions.toml"
sed -i 's/^kotlin = "1.9.24"$/kotlin = "1.9.25"/' "$REACT_NATIVE_GRADLE_PLUGIN_CATALOG"
grep -n 'kotlinVersion' android/build.gradle android/gradle.properties
grep -n '^kotlin =' node_modules/react-native/gradle/libs.versions.toml
grep -n '^kotlin =' "$REACT_NATIVE_GRADLE_PLUGIN_CATALOG"

- name: Build Android APK (debug)
run: |
cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug
cd android && ./gradlew -Pandroid.kotlinVersion=1.9.25 :app:assembleDebug :app:assembleDebugAndroidTest -DtestBuildType=debug
env:
CI: true

Expand Down Expand Up @@ -787,7 +859,11 @@ jobs:
- name: Security audit (cargo)
if: needs.changes.outputs.contracts == 'true'
working-directory: contracts
run: cargo audit
# Soroban SDK 25.3.0 pins crypto-bigint 0.5.5. RustSec's fix is in
# 0.7.6, which is not API-compatible with the SDK's elliptic-curve
# dependency; keep this advisory visible in the log until Soroban
# publishes a compatible dependency update.
run: cargo audit --ignore RUSTSEC-2026-0387

- name: Deployment safety checks
if: needs.changes.outputs.contracts == 'true'
Expand Down
50 changes: 43 additions & 7 deletions .github/workflows/mobile-e2e-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
jobs:
mobile-e2e-ios-nightly:
name: Nightly Smoke — iOS
runs-on: macos-latest
runs-on: macos-15
timeout-minutes: 25
defaults:
run:
Expand All @@ -38,31 +38,49 @@ jobs:
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
xcode-version: '16.4'

- name: Expo prebuild (iOS)
run: pnpm expo prebuild --platform ios --non-interactive
env:
CI: true

- name: Select available iOS simulator
run: |
set -euo pipefail
DEVICE=$(xcrun simctl list devices available -j | jq -r '
.devices | to_entries[] as $runtime | $runtime.value[]
| select(.name | startswith("iPhone"))
| "\(.name)|\($runtime.key | sub("^com.apple.CoreSimulator.SimRuntime.iOS-"; "") | gsub("-"; "."))|\(.udid)"
' | head -1)
if [[ -z "$DEVICE" ]]; then
echo "No available iPhone simulator found" >&2
exit 1
fi
IFS='|' read -r DEVICE_NAME DEVICE_OS DEVICE_UDID <<< "$DEVICE"
echo "Using iOS simulator: $DEVICE_NAME ($DEVICE_OS, $DEVICE_UDID)"
echo "DETOX_IOS_DEVICE_TYPE=$DEVICE_NAME" >> "$GITHUB_ENV"
echo "DETOX_IOS_DEVICE_OS=iOS $DEVICE_OS" >> "$GITHUB_ENV"
echo "SIMULATOR_UDID=$DEVICE_UDID" >> "$GITHUB_ENV"

- name: Build iOS app for simulator
run: |
set -o pipefail
xcodebuild \
-workspace ios/Amana.xcworkspace \
-scheme Amana \
-configuration Debug \
-sdk iphonesimulator \
-derivedDataPath ios/build \
-destination "platform=iOS Simulator,name=iPhone 15,OS=17.5" \
-destination "id=$SIMULATOR_UDID" \
build \
| xcpretty --color
env:
CI: true

- name: Boot iOS simulator
run: |
UDID=$(xcrun simctl list devices available | grep "iPhone 15" | grep "17.5" | head -1 | grep -E -o '\([-A-F0-9]+\)' | tr -d '()')
xcrun simctl boot "$UDID" || true
xcrun simctl boot "$SIMULATOR_UDID" || true

- name: Run Detox smoke tests (iOS)
run: pnpm test:e2e:ios
Expand Down Expand Up @@ -109,6 +127,10 @@ jobs:

- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
# The default `tools` package no longer exists in current cmdline-tools
# and makes sdkmanager exit 1; platform-tools is all this job needs.
packages: platform-tools system-images;android-34;google_apis;x86_64

- name: Cache AVD
uses: actions/cache@v4
Expand All @@ -134,9 +156,19 @@ jobs:
env:
CI: true

- name: Align Android Kotlin compiler
run: |
sed -i "s/kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.24'/kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'/" android/build.gradle
if grep -q '^android.kotlinVersion=' android/gradle.properties; then
sed -i 's/^android.kotlinVersion=.*/android.kotlinVersion=1.9.25/' android/gradle.properties
else
echo 'android.kotlinVersion=1.9.25' >> android/gradle.properties
fi
grep -n 'kotlinVersion' android/build.gradle android/gradle.properties

- name: Build Android APK (debug)
run: |
cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug
cd android && ./gradlew -Pandroid.kotlinVersion=1.9.25 assembleDebug assembleAndroidTest -DtestBuildType=debug
env:
CI: true

Expand All @@ -163,7 +195,7 @@ jobs:
# Runs the same smoke suite against a low-end device from
# mobile/docs/DEVICE_MATRIX.md's supported matrix (minimum API 31, on a
# low-RAM/low-CPU profile) instead of the default Pixel 6 API 34 used
# above — see mobile/.detoxrc.js's `emulatorLowEnd` device.
# above — see mobile/.detoxrc.cjs's `emulatorLowEnd` device.
mobile-e2e-android-lowend-nightly:
name: Nightly Smoke — Android (low-end device)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -196,6 +228,10 @@ jobs:

- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
# The default `tools` package no longer exists in current cmdline-tools
# and makes sdkmanager exit 1; platform-tools is all this job needs.
packages: platform-tools system-images;android-34;google_apis;x86_64

- name: Cache AVD
uses: actions/cache@v4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
-- Add missing indexes for performance optimization
-- These indexes were identified during schema review to improve query performance

-- Trade.version is in schema.prisma but no earlier migration creates it
ALTER TABLE "Trade" ADD COLUMN IF NOT EXISTS "version" INTEGER NOT NULL DEFAULT 0;

-- Index on Trade.version for optimistic concurrency queries
CREATE INDEX IF NOT EXISTS "Trade_version_idx" ON "Trade"("version");

Expand Down
Loading
Loading