mostly android

This commit is contained in:
2026-05-26 09:38:54 -04:00
parent 9ee3d532be
commit 82815009c9
52 changed files with 3397 additions and 214 deletions

99
iOS/.swiftlint.yml Normal file
View File

@@ -0,0 +1,99 @@
# SwiftLint configuration for Kordant iOS
# NASA Standards: Enforce quality, readability, consistency
included:
- iOS/Kordant
- iOS/KordantTests
- iOS/KordantUITests
excluded:
- iOS/Kordant.xcodeproj
- iOS/Kordant/.swiftpm
# Rule severity
opt_in_rules:
- closure_body_length
- closure_end_indentation
- closure_spacing
- collection_alignment
- contains_over_filter_count
- contains_over_filter_is_empty
- contains_over_first_not_equal
- contains_over_range_nil_comparison
- discouraged_object_literal
- empty_count
- fatal_error_message
- file_header
- force_unwrapping
- implicitly_unwrapped_optional
- large_tuple
- last_enum_element_closing_brace
- legacy_multiple
- legacy_random
- literal_expression_end_indentation
- modifier_order
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- nslocalizedstring_key
- operator_usage_whitespace
- overridden_super_call
- prohibited_enum_element
- prohibited_interface_builder
- prohibited_super_call
- quick_look_alert
- redundant_nil_coalescing
- sorted_first_last
- toggle_all_bool
- trailing_closure
- unneeded_parentheses_in_closure_argument
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- vertical_whitespace_opening_braces
- yoda_condition
disabled_rules:
- todo
# Warning/Error thresholds
line_length:
warning: 120
error: 200
file_length:
warning: 500
error: 1000
type_body_length:
warning: 300
error: 500
function_body_length:
warning: 50
error: 100
closure_body_length:
warning: 20
error: 50
type_name:
min_length: 2
max_length:
warning: 40
error: 60
allowed_symbols: ["_"]
identifier_name:
min_length: 1
excluded:
- i
- id
- x
- y
- width
- height
reporter: "xcode"

1
iOS/Package.swift Normal file
View File

@@ -0,0 +1 @@
// Empty file for Swift package resolution

156
iOS/README.md Normal file
View File

@@ -0,0 +1,156 @@
# Lendair iOS App
Native iOS SwiftUI application for the Lendair peer-to-peer micro lending platform.
## Setup Instructions
### Prerequisites
- macOS with Xcode 15.0+ installed
- Homebrew (for package management)
### Installation
1. **Install XcodeGen** (project generator):
```bash
brew install xcodegen
```
2. **Generate the Xcode project**:
```bash
cd /home/mike/code/lendair/iOS
./generate.sh
```
3. **Open the workspace in Xcode**:
```bash
open Lendair.xcworkspace
```
### Project Structure
```
iOS/
├── project.yml # XcodeGen configuration
├── generate.sh # Project generation script
├── README.md # This file
└── Lendair/
├── Lendair.xcodeproj # Generated Xcode project
├── Lendair/
│ ├── LendairApp.swift # App entry point
│ ├── ContentView.swift # Root view with auth routing
│ ├── Services/ # Business logic layer
│ │ ├── TRPCService.swift # tRPC client
│ │ ├── AuthService.swift # Authentication
│ │ ├── LoanService.swift # Loan operations
│ │ ├── TransactionService.swift
│ │ └── AppState.swift # Global state management
│ ├── Models/ # Data models
│ │ ├── User.swift
│ │ ├── Loan.swift
│ │ └── Transaction.swift
│ ├── Screens/ # Feature screens
│ │ ├── Auth/
│ │ │ ├── LoginView.swift
│ │ │ └── SignupView.swift
│ │ ├── Main/
│ │ │ └── MainTabView.swift
│ │ ├── Home/
│ │ │ └── HomeView.swift
│ │ ├── Loans/
│ │ │ └── LoansTabView.swift
│ │ ├── Activity/
│ │ │ └── ActivityTabView.swift
│ │ └── Profile/
│ │ └── ProfileTabView.swift
│ ├── Components/UI/ # Reusable components
│ │ ├── PrimaryButton.swift
│ │ ├── LendairTextField.swift
│ │ ├── BalanceCard.swift
│ │ ├── LoanCard.swift
│ │ ├── TransactionRow.swift
│ │ ├── StatusBadge.swift
│ │ ├── LoadingView.swift
│ │ ├── ErrorView.swift
│ │ └── EmptyStateView.swift
│ └── Assets.xcassets/ # App icons, colors, images
├── LendairTests/ # Unit tests
└── LendairUITests/ # UI tests
```
### Architecture
- **Pattern**: MVVM with `@Observable` (Swift 5.9+)
- **Navigation**: NavigationStack with programmatic navigation
- **Networking**: tRPC over HTTPS via URLSession
- **State Management**: Singleton `AppState` with `@Observable`
### Dependencies
Managed via Swift Package Manager:
- **swift-collections** (v1.x): OrderedDictionary and other collection types
- **swift-algorithms** (v1.x): Pagination helpers and algorithms
### Building
From Xcode or command line:
```bash
# Debug build
xcodebuild -workspace Lendair.xcworkspace -scheme Lendair -configuration Debug build
# Release build
xcodebuild -workspace Lendair.xcworkspace -scheme Lendair -configuration Release build
```
### Running Tests
```bash
# Run all tests
xcodebuild test -workspace Lendair.xcworkspace -scheme Lendair -configuration Debug
# With coverage
xcodebuild test -workspace Lendair.xcworkspace -scheme Lendair -configuration Debug \
-enableCodeCoverage YES
```
### Environment Configuration
The app supports multiple environments:
| Environment | Base URL |
|-------------|----------|
| Development | https://dev.lendair.local |
| Staging | https://staging.lendair.app |
| Production | https://api.lendair.app |
Configure via `TRPCEndpoint` enum in `TRPCService.swift`.
### API Endpoints
The iOS app communicates with the SolidStart backend via tRPC:
- **Auth**: `/auth/signin`, `/auth/signup`, `/auth/me`
- **Loans**: `/loans/available`, `/loans/my`, `/loans/create`, `/loans/accept`
- **Transactions**: `/transactions/recent`, `/transactions/list`
See [FRE-455](https://git.freno.me/Mike/Lendair/issues/FRE-455) for full API specification.
### Key Features
- ✅ Tab bar navigation (Home, Loans, Activity, Profile)
- ✅ Authentication screens (Login, Signup)
- ✅ Home dashboard with balance card
- ✅ Loan browsing and creation
- ✅ Transaction history
- ✅ Profile management
- ⏳ Create loan form (in progress)
- ⏳ Accept/repay loan flows (pending)
- ⏳ Unit tests at NASA standards (pending)
### References
- **Parent Task**: [FRE-457](https://git.freno.me/Mike/Lendair/issues/FRE-457)
- **Design Spec**: [FRE-452](https://git.freno.me/Mike/Lendair/issues/FRE-452)
- **Tech Plan**: [FRE-450](https://git.freno.me/Mike/Lendair/issues/FRE-450)

19
iOS/buildServer.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "xcode build server",
"version": "1.3.0",
"bspVersion": "2.2.0",
"languages": [
"c",
"cpp",
"objective-c",
"objective-cpp",
"swift"
],
"argv": [
"/opt/homebrew/bin/xcode-build-server"
],
"workspace": "/Users/mike/Code/Kordant/iOS/Kordant.xcodeproj/project.xcworkspace",
"build_root": "/Users/mike/Library/Developer/Xcode/DerivedData/Kordant-gkpnetnuxdeqhzegbngesmnbzwud",
"scheme": "Kordant",
"kind": "xcode"
}

106
iOS/project.yml Normal file
View File

@@ -0,0 +1,106 @@
# XcodeGen Configuration for Kordant iOS App
name: Kordant
options:
xcodeIndentationWidth: 4
tabWidth: 4
usesTabs: false
bundleIdPrefix: com.frenocorp
deploymentTarget:
iOS: "17.0"
settings:
base:
MARKETING_VERSION: 1.0.0
CURRENT_PROJECT_VERSION: 1
SWIFT_VERSION: "5.9"
ENABLE_PREVIEWS: YES
AUTOMATIC_SIGNING: NO
TARGETED_DEVICE_FAMILY: "1,2"
packages:
Collections:
url: https://github.com/apple/swift-collections
from: "1.0.0"
Algorithms:
url: https://github.com/apple/swift-algorithms
from: "1.0.0"
targets:
Kordant:
type: application
platform: iOS
deploymentTarget: "17.0"
sources:
- path: Kordant
excludes:
- "**/*.xcodeproj"
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.frenocorp.kordant
PRODUCT_NAME: Kordant
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: YES
INFOPLIST_FILE: Kordant/Info.plist
dependencies:
- package: Collections
product: Collections
- package: Algorithms
product: Algorithms
preBuildScripts:
- name: SwiftLint
script: |
if which swiftlint >/dev/null 2>&1; then
swiftlint lint --quiet || true
else
echo "warning: SwiftLint not installed, run 'brew install swiftlint' to enable linting"
fi
showEnvVarsInLog: false
basedOnDependencyAnalysis: false
KordantTests:
type: bundle.unit-test
platform: iOS
deploymentTarget: "17.0"
sources:
- path: KordantTests
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.frenocorp.KordantTests
GENERATE_INFOPLIST_FILE: YES
dependencies:
- target: Kordant
KordantUITests:
type: bundle.ui-testing
platform: iOS
deploymentTarget: "17.0"
sources:
- path: KordantUITests
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.frenocorp.KordantUITests
GENERATE_INFOPLIST_FILE: YES
dependencies:
- target: Kordant
schemes:
Kordant:
build:
targets:
Kordant: all
KordantTests: [test]
KordantUITests: [test]
run:
config: Debug
test:
config: Debug
targets:
- KordantTests
- KordantUITests
profile:
config: Release
analyze:
config: Debug
archive:
config: Release

357
iOS/run Executable file
View File

@@ -0,0 +1,357 @@
#!/bin/bash
# Build and run Kordant application
# Usage: ./run [build|test|run|lsp] [-v|--verbose] [-c|--coverage] [-p|--performance] [-o|--output <file>] [--no-lsp]
# Note: Default action (./run with no args) runs the app with verbose logging enabled
set -o pipefail
readonly PROJECT="Kordant.xcodeproj"
readonly SCHEME="Kordant"
readonly CONFIGURATION="Debug"
readonly APP_SUBSYSTEM="com.frenocorp.Kordant"
readonly BUNDLE_ID="com.frenocorp.lendair"
VERBOSE=false
OUTPUT_FILE=""
SKIP_LSP=false
PERFORMANCE=false
COVERAGE=false
build_xcodebuild_command() {
local action="$1"
local destination="${2:-generic/platform=iOS}"
local extra_flags="${3:-}"
local cmd="xcodebuild -project $PROJECT -scheme $SCHEME -configuration $CONFIGURATION -destination '$destination' $extra_flags $action"
if [ "$PERFORMANCE" = true ]; then
cmd="$cmd -enablePerformanceTestsDiagnostics YES"
fi
if [ "$COVERAGE" = true ]; then
cmd="$cmd -enableCodeCoverage YES"
fi
echo "$cmd"
}
kill_existing_lendair_processes() {
echo "Checking for existing Kordant processes..."
local pids
pids=$(pgrep -f "Kordant.app")
if [ -n "$pids" ]; then
echo "Killing existing Kordant processes (PID(s): $pids)..."
kill $pids 2>/dev/null
sleep 1
else
echo "No existing Kordant processes found"
fi
}
update_lsp_config() {
echo "Updating LSP configuration..."
if command -v xcode-build-server &> /dev/null; then
local build_root
build_root=$(ls -td "$HOME/Library/Developer/Xcode/DerivedData/${SCHEME}-"*/Build 2>/dev/null | head -1)
local exit_code
if [ -n "$build_root" ]; then
xcode-build-server config -project "$PROJECT" -scheme "$SCHEME" --build_root "$build_root" > /dev/null 2>&1
exit_code=$?
else
xcode-build-server config -project "$PROJECT" -scheme "$SCHEME" > /dev/null 2>&1
exit_code=$?
fi
if [ $exit_code -eq 0 ]; then
echo "LSP configuration updated (buildServer.json created)"
else
echo "Could not update LSP configuration"
fi
else
echo "xcode-build-server not found. Install with: brew install xcode-build-server"
echo " This helps Neovim's LSP recognize your Swift modules"
fi
}
get_build_directory() {
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration "$CONFIGURATION" \
-showBuildSettings 2>/dev/null | \
grep -m 1 "BUILT_PRODUCTS_DIR" | \
sed 's/.*= //'
}
handle_build_success() {
echo "Build succeeded!"
if [ "$SKIP_LSP" != true ]; then
update_lsp_config
fi
}
print_errors() {
local output="$1"
local action_type="$2"
echo ""
echo "${action_type} Errors:"
echo "================================================================================"
local errors
errors=$(echo "$output" | grep -E "error:|Error |failed|FAIL" | sed 's/^/ /')
if [ -n "$errors" ]; then
echo "$errors"
else
echo " No specific error messages found. See full output above."
fi
echo "================================================================================"
}
print_warnings() {
local output="$1"
echo ""
echo "Diagnostic Warnings:"
echo "================================================================================"
local warnings
warnings=$(echo "$output" | grep -E "\.swift:[0-9]+:[0-9]+: warning:" | sed 's/^/ /')
if [ -n "$warnings" ]; then
local count
count=$(echo "$warnings" | wc -l | tr -d ' ')
echo " Found $count warning(s):"
echo ""
echo "$warnings"
else
echo " No warnings found."
fi
echo "================================================================================"
}
get_booted_simulator() {
xcrun simctl list devices booted 2>/dev/null | grep -oE "[A-F0-9-]{36}" | head -1
}
ensure_simulator_booted() {
local simulator
simulator=$(get_booted_simulator)
if [ -z "$simulator" ]; then
echo "No booted simulator found, booting first available iPhone..." >&2
simulator=$(xcrun simctl list devices available 2>/dev/null | grep -i "iPhone" | grep -oE "[A-F0-9-]{36}" | head -1)
if [ -n "$simulator" ]; then
echo "Booting simulator $simulator..." >&2
xcrun simctl boot "$simulator"
sleep 5
open -a Simulator 2>/dev/null || true
fi
fi
echo "$simulator"
}
launch_app() {
local build_dir app_path simulator
build_dir=$(get_build_directory)
app_path="${build_dir}/Kordant.app"
simulator=$(ensure_simulator_booted)
if [ -z "$simulator" ]; then
echo "Error: No iOS simulator available. Boot one with: open -a Simulator"
exit 1
fi
if [ -d "$app_path" ]; then
echo "Installing on simulator $simulator..."
xcrun simctl install "$simulator" "$app_path"
echo "Launching app..."
xcrun simctl launch "$simulator" "$BUNDLE_ID"
sleep 2
echo "Streaming simulator logs (Ctrl+C to stop - app keeps running)..."
echo "================================================================"
xcrun simctl spawn "$simulator" log stream --level debug \
--predicate "subsystem contains \"$APP_SUBSYSTEM\"" \
--style compact 2>/dev/null
else
echo "App not found at expected location, trying fallback..."
local fallback
fallback=$(ls -dt "$HOME/Library/Developer/Xcode/DerivedData/Kordant-"*/Build/Products/Debug-iphonesimulator/Lendair.app 2>/dev/null | head -1)
if [ -d "$fallback" ]; then
echo "Found at: $fallback"
xcrun simctl install "$simulator" "$fallback"
xcrun simctl launch "$simulator" "$BUNDLE_ID"
sleep 2
xcrun simctl spawn "$simulator" log stream --level debug \
--predicate "subsystem contains \"$APP_SUBSYSTEM\"" \
--style compact 2>/dev/null
else
echo "No app bundle found"
exit 1
fi
fi
}
run_with_output() {
local cmd="$1"
local exit_code
if [ "$VERBOSE" = true ] && [ -n "$OUTPUT_FILE" ]; then
COMMAND_OUTPUT=$(eval "$cmd" 2>&1 | tee "$OUTPUT_FILE")
exit_code=${PIPESTATUS[0]}
elif [ "$VERBOSE" = true ]; then
if [ -t 1 ]; then
COMMAND_OUTPUT=$(eval "$cmd" 2>&1 | tee /dev/tty)
exit_code=${PIPESTATUS[0]}
else
COMMAND_OUTPUT=$(eval "$cmd" 2>&1)
exit_code=$?
echo "$COMMAND_OUTPUT"
fi
elif [ -n "$OUTPUT_FILE" ]; then
COMMAND_OUTPUT=$(eval "$cmd" 2>&1 | tee "$OUTPUT_FILE")
exit_code=${PIPESTATUS[0]}
else
COMMAND_OUTPUT=$(eval "$cmd" 2>&1)
exit_code=$?
fi
return $exit_code
}
show_usage() {
echo "Usage: $0 [build|test|run|lsp] [-v|--verbose] [-o|--output <file_name>] [--no-lsp] [-p|--performance] [-c|--coverage]"
echo ""
echo "Commands:"
echo " build - Build the application"
echo " test - Run unit tests"
echo " run - Build and run the application on simulator with logging (default)"
echo " launch - Launch last-built app on booted simulator"
echo " lsp - Update LSP configuration only (buildServer.json)"
echo ""
echo "Options:"
echo " -v, --verbose - Show output in stdout"
echo " -o, --output - Write output to log file"
echo " --no-lsp - Skip LSP configuration update"
echo " -p, --performance - Run tests with performance profiling"
echo " -c, --coverage - Run tests with code coverage analysis"
echo ""
echo "Note: Running './run' with no arguments defaults to 'run' action with verbose logging."
echo " Press Ctrl+C to stop log capture and keep the app running."
}
ACTION=""
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output)
OUTPUT_FILE="$2"
VERBOSE=true
shift 2
;;
--no-lsp)
SKIP_LSP=true
shift
;;
-p|--performance)
PERFORMANCE=true
shift
;;
-c|--coverage)
COVERAGE=true
shift
;;
*)
if [ -z "$ACTION" ]; then
ACTION="$1"
fi
shift
;;
esac
done
if [ -z "$ACTION" ]; then
ACTION="run"
fi
echo "=== Kordant Application Script ==="
case "$ACTION" in
build)
echo "Building Kordant project..."
run_with_output "$(build_xcodebuild_command build)"
if [ $? -eq 0 ]; then
handle_build_success
echo "The app is located at: $(get_build_directory)/Kordant.app"
if [ "$VERBOSE" = true ]; then
print_warnings "$COMMAND_OUTPUT"
fi
else
echo "Build failed!"
print_errors "$COMMAND_OUTPUT" "Build"
exit 1
fi
;;
test)
echo "Running unit tests (parallel)..."
run_with_output "$(build_xcodebuild_command test "platform=iOS Simulator,name=iPhone 16" "-parallel-testing-enabled YES")"
if [ $? -eq 0 ]; then
echo "Tests passed!"
if [ "$VERBOSE" = true ]; then
print_warnings "$COMMAND_OUTPUT"
fi
else
echo "Tests failed!"
print_errors "$COMMAND_OUTPUT" "Test"
exit 1
fi
;;
run)
echo "Building and running Kordant application..."
kill_existing_lendair_processes
run_with_output "$(build_xcodebuild_command build "generic/platform=iOS Simulator")"
if [ $? -eq 0 ]; then
handle_build_success
if [ "$VERBOSE" = true ]; then
print_warnings "$COMMAND_OUTPUT"
fi
launch_app
else
echo "Build failed!"
print_errors "$COMMAND_OUTPUT" "Build"
exit 1
fi
;;
lsp)
echo "Updating LSP configuration only..."
update_lsp_config
;;
launch)
echo "Launching last-built app on simulator..."
launch_app
;;
*)
show_usage
exit 1
;;
esac

84
iOS/scripts/create_test_token Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Generate a JWT token for testing Lendair API calls.
# Usage: ./scripts/create_test_token <user-id> [secret-env-var]
#
# Reads the JWT secret from the environment (default: CLERK_SECRET_KEY).
# Falls back to .env file in the project root.
#
# Example:
# CLERK_SECRET_KEY=sk_test_xxx ./scripts/create_test_token user_123
# ./scripts/create_test_token user_123 CLERK_SECRET_KEY
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $(basename "$0") <user-id> [secret-env-var]" >&2
echo "" >&2
echo "Generates a JWT token with the given user-id as subject." >&2
echo "The secret is read from the environment variable (default: CLERK_SECRET_KEY)" >&2
echo "or from a .env file in the project root." >&2
exit 1
fi
USER_ID="$1"
SECRET_VAR="${2:-CLERK_SECRET_KEY}"
SECRET="${!SECRET_VAR:-}"
# Fallback: try loading from .env in project root
if [ -z "$SECRET" ]; then
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
ENV_FILE="$PROJECT_DIR/../.env"
if [ -f "$ENV_FILE" ]; then
set -a
source "$ENV_FILE" 2>/dev/null || true
set +a
SECRET="${!SECRET_VAR:-}"
fi
fi
if [ -z "$SECRET" ]; then
echo "Error: $SECRET_VAR is not set and no .env file found" >&2
echo "" >&2
echo "Set it inline:" >&2
echo " $SECRET_VAR=sk_test_xxx $(basename "$0") $USER_ID" >&2
echo "Or add to .env in the repo root:" >&2
echo " $SECRET_VAR=sk_test_xxx" >&2
exit 1
fi
generate_jwt_via_node() {
node --input-type=module - "$1" "$2" <<'JWTSCRIPT' 2>/dev/null
import { createHmac } from 'node:crypto';
const userId = process.argv[1];
const secret = process.argv[2];
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
const now = Math.floor(Date.now() / 1000);
const payload = Buffer.from(JSON.stringify({
sub: userId,
iat: now,
exp: now + 2592000
})).toString('base64url');
const sig = createHmac('sha256', secret).update(header + '.' + payload).digest('base64url');
console.log(header + '.' + payload + '.' + sig);
JWTSCRIPT
}
generate_jwt_via_openssl() {
local now header payload sig
now=$(date +%s)
header=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | tr '+/' '-_' | tr -d '=')
payload=$(echo -n "{\"sub\":\"$USER_ID\",\"iat\":$now,\"exp\":$((now + 2592000))}" | base64 | tr '+/' '-_' | tr -d '=')
sig=$(echo -n "$header.$payload" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64 | tr '+/' '-_' | tr -d '=')
echo "$header.$payload.$sig"
}
if command -v node &>/dev/null; then
generate_jwt_via_node "$USER_ID" "$SECRET"
elif command -v openssl &>/dev/null; then
generate_jwt_via_openssl
else
echo "Error: need either node or openssl to generate JWT" >&2
exit 1
fi

41
iOS/scripts/get_coverage Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Generate code coverage report for Lendair iOS project.
# Finds the most recent xcresult file and produces a JSON report.
#
# Usage: ./scripts/get_coverage
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
REPORTS_DIR="$PROJECT_DIR/reports"
XCRESULT=$(find ~/Library/Developer/Xcode/DerivedData -name "*Lendair*" -path "*/Test/*.xcresult" -type d 2>/dev/null | sort -r | head -1)
if [ -z "$XCRESULT" ]; then
echo "Error: No xcresult file found for Lendair project" >&2
echo "" >&2
echo "Make sure you've run tests with coverage enabled:" >&2
echo " ./run test -c" >&2
exit 1
fi
echo "Using xcresult: $XCRESULT"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
mkdir -p "$REPORTS_DIR/$TIMESTAMP"
xcrun xccov view --report "$XCRESULT" --json > "$REPORTS_DIR/$TIMESTAMP/code_coverage.json"
echo ""
echo "Code coverage report generated:"
echo " $REPORTS_DIR/$TIMESTAMP/code_coverage.json"
# Also symlink latest
ln -sf "$TIMESTAMP" "$REPORTS_DIR/latest" 2>/dev/null || true
cp "$REPORTS_DIR/$TIMESTAMP/code_coverage.json" "$REPORTS_DIR/code_coverage.json" 2>/dev/null || true
# Print a quick summary
echo ""
echo "=== Coverage Summary ==="
xcrun xccov view --report "$XCRESULT" 2>/dev/null | head -30 || true

59
iOS/scripts/typecheck Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# typecheck - Run a fast Swift typecheck on the Lendair iOS project via remote Mac build host.
#
# Usage (from any machine with SSH access to the build host):
# ./scripts/typecheck
#
# What it does:
# 1. SSHes to the build host (configurable via REMOTE_HOST env var)
# 2. Pulls latest code on the Mac
# 3. Runs xcodebuild build with output filtered to errors/warnings only
# 4. Exits 0 on clean typecheck, 1 on errors
#
# Configuration:
# REMOTE_HOST - SSH hostname (default: hermes)
# REMOTE_REPO - Path to repo on the Mac (default: ~/code/lendair)
# PROJECT_PATH - Project path relative to repo root (default: iOS/Lendair/Lendair.xcodeproj)
set -euo pipefail
REMOTE_HOST="${REMOTE_HOST:-hermes}"
REMOTE_REPO="${REMOTE_REPO:-$HOME/code/lendair}"
PROJECT_PATH="${PROJECT_PATH:-iOS/Lendair/Lendair.xcodeproj}"
SCHEME="Lendair"
echo "=== Typecheck: connecting to $REMOTE_HOST ==="
ssh "$REMOTE_HOST" bash <<REMOTE
set -euo pipefail
cd "$REMOTE_REPO"
echo "--- Pulling latest ---"
git stash --include-untracked -q 2>/dev/null || true
git pull --rebase origin master 2>&1 | tail -3 || echo "Already up to date or pull failed"
git stash pop -q 2>/dev/null || true
echo "--- Running typecheck ---"
set +e +o pipefail
BUILD_LOG=\$(mktemp)
xcodebuild \
-project "$PROJECT_PATH" \
-scheme "$SCHEME" \
-configuration Debug \
-destination "generic/platform=iOS Simulator" \
-jobs 4 \
CODE_SIGNING_ALLOWED=NO \
build > "\$BUILD_LOG" 2>&1
BUILD_EXIT=\$?
set -e -o pipefail
grep -E "\.swift:[0-9]+:[0-9]+: (error|warning):|^\*\* BUILD (SUCCEEDED|FAILED)" "\$BUILD_LOG" \
| sed "s|$REMOTE_REPO/||g" \
|| true
rm -f "\$BUILD_LOG"
if [ "\$BUILD_EXIT" = "0" ]; then
echo "=== PASSED ==="
else
echo "=== FAILED ==="
fi
exit \$BUILD_EXIT
REMOTE