mostly android
This commit is contained in:
99
iOS/.swiftlint.yml
Normal file
99
iOS/.swiftlint.yml
Normal 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"
|
||||
Binary file not shown.
1
iOS/Package.swift
Normal file
1
iOS/Package.swift
Normal file
@@ -0,0 +1 @@
|
||||
// Empty file for Swift package resolution
|
||||
156
iOS/README.md
Normal file
156
iOS/README.md
Normal 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
19
iOS/buildServer.json
Normal 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
106
iOS/project.yml
Normal 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
357
iOS/run
Executable 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
84
iOS/scripts/create_test_token
Executable 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
41
iOS/scripts/get_coverage
Executable 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
59
iOS/scripts/typecheck
Executable 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
|
||||
Reference in New Issue
Block a user