feat(ios): add iOS deployment support and web assets parity
Add comprehensive iOS build and deployment infrastructure with command-line tooling, documentation, and web assets synchronization. Changes: - Update Capacitor dependencies to v6.0 in podspec - Add iOS build support to build-native.sh with NVM integration - Sync iOS web assets to match www/ source directory - Create deployment scripts for both native iOS app and Vue 3 test app - Add comprehensive iOS simulator deployment documentation - Document web assets parity requirements between Android and iOS This enables: - Command-line iOS builds without Xcode UI - Automated deployment to iOS simulators - Consistent web assets across platforms - Clear separation between native iOS app (ios/App) and Vue 3 test app Files modified: - ios/DailyNotificationPlugin.podspec (Capacitor 6.0) - ios/App/App/public/index.html (synced from www/) - scripts/build-native.sh (iOS build support) Files added: - docs/WEB_ASSETS_PARITY.md - docs/standalone-ios-simulator-guide.md - scripts/build-and-deploy-native-ios.sh - test-apps/daily-notification-test/docs/IOS_BUILD_QUICK_REFERENCE.md - test-apps/daily-notification-test/scripts/build-and-deploy-ios.sh
This commit is contained in:
64
docs/WEB_ASSETS_PARITY.md
Normal file
64
docs/WEB_ASSETS_PARITY.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Web Assets Structure - Android and iOS Parity
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: November 4, 2025
|
||||
|
||||
## Source of Truth
|
||||
|
||||
The **`www/`** directory is the source of truth for web assets. Both Android and iOS app directories should match this structure.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
www/ # Source of truth (edit here)
|
||||
├── index.html # Main test interface
|
||||
|
||||
android/app/src/main/assets/ # Android (synced from www/)
|
||||
├── capacitor.plugins.json # Auto-generated by Capacitor
|
||||
└── public/ # Web assets (must match www/)
|
||||
└── index.html # Synced from www/index.html
|
||||
|
||||
ios/App/App/ # iOS (synced from www/)
|
||||
├── capacitor.config.json # Capacitor configuration
|
||||
└── public/ # Web assets (must match www/)
|
||||
└── index.html # Synced from www/index.html
|
||||
```
|
||||
|
||||
## Synchronization
|
||||
|
||||
Both `android/app/src/main/assets/public/` and `ios/App/App/public/` should match `www/` after running:
|
||||
|
||||
```bash
|
||||
# Sync web assets to both platforms
|
||||
npx cap sync
|
||||
|
||||
# Or sync individually
|
||||
npx cap sync android
|
||||
npx cap sync ios
|
||||
```
|
||||
|
||||
## Key Points
|
||||
|
||||
1. **Edit source files in `www/`** - Never edit platform-specific copies directly
|
||||
2. **Both platforms should match** - After sync, `android/.../assets/public/` and `ios/App/App/public/` should be identical
|
||||
3. **Capacitor handles sync** - `npx cap sync` copies files from `www/` to platform directories
|
||||
4. **Auto-generated files** - `capacitor.plugins.json`, `capacitor.js`, etc. are generated by Capacitor
|
||||
|
||||
## Verification
|
||||
|
||||
After syncing, verify both platforms match:
|
||||
|
||||
```bash
|
||||
# Check file sizes match
|
||||
ls -lh www/index.html android/app/src/main/assets/public/index.html ios/App/App/public/index.html
|
||||
|
||||
# Compare contents
|
||||
diff www/index.html android/app/src/main/assets/public/index.html
|
||||
diff www/index.html ios/App/App/public/index.html
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- **Cordova files**: iOS may have empty `cordova.js` and `cordova_plugins.js` files. These are harmless but should be removed if not using Cordova compatibility.
|
||||
- **Capacitor runtime**: Capacitor generates `capacitor.js` and `capacitor_plugins.js` during sync - these are auto-generated and should not be manually edited.
|
||||
|
||||
548
docs/standalone-ios-simulator-guide.md
Normal file
548
docs/standalone-ios-simulator-guide.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Running iOS Apps in Standalone Simulator (Without Xcode UI)
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Last Updated**: 2025-11-04
|
||||
**Version**: 1.0.0
|
||||
|
||||
## Overview
|
||||
|
||||
This guide demonstrates how to run DailyNotification plugin test apps in a standalone iOS Simulator without using Xcode UI. This method is useful for development, CI/CD pipelines, and command-line workflows.
|
||||
|
||||
**There are two different test apps:**
|
||||
1. **Native iOS Development App** (`ios/App`) - Simple Capacitor app for plugin development
|
||||
2. **Vue 3 Test App** (`test-apps/daily-notification-test`) - Full-featured Vue 3 Capacitor app for comprehensive testing
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Software
|
||||
- **Xcode** with command line tools (`xcode-select --install`)
|
||||
- **iOS Simulator** (included with Xcode)
|
||||
- **CocoaPods** (`gem install cocoapods`)
|
||||
- **Node.js** and **npm** (for TypeScript compilation)
|
||||
- **Capacitor CLI** (`npm install -g @capacitor/cli`) - for Vue 3 test app
|
||||
|
||||
### System Requirements
|
||||
- **macOS** (required for iOS development)
|
||||
- **RAM**: 4GB minimum, 8GB recommended
|
||||
- **Storage**: 5GB free space for simulator and dependencies
|
||||
|
||||
---
|
||||
|
||||
## Scenario 1: Native iOS Development App (`ios/App`)
|
||||
|
||||
The `ios/App` directory contains a simple Capacitor-based development app, similar to `android/app`. This is used for quick plugin testing and development.
|
||||
|
||||
### Step-by-Step Process
|
||||
|
||||
#### 1. Check Available Simulators
|
||||
|
||||
```bash
|
||||
# List available iOS simulators
|
||||
xcrun simctl list devices available
|
||||
|
||||
# Example output:
|
||||
# iPhone 15 Pro (iOS 17.0)
|
||||
# iPhone 14 (iOS 16.4)
|
||||
# iPad Pro (12.9-inch) (iOS 17.0)
|
||||
```
|
||||
|
||||
#### 2. Boot a Simulator
|
||||
|
||||
```bash
|
||||
# Boot a specific simulator device
|
||||
xcrun simctl boot "iPhone 15 Pro"
|
||||
|
||||
# Or boot by device ID
|
||||
xcrun simctl boot <DEVICE_ID>
|
||||
|
||||
# Verify simulator is running
|
||||
xcrun simctl list devices | grep Booted
|
||||
```
|
||||
|
||||
**Alternative: Open Simulator UI**
|
||||
```bash
|
||||
# Open Simulator app (allows visual interaction)
|
||||
open -a Simulator
|
||||
```
|
||||
|
||||
#### 3. Build the Plugin
|
||||
|
||||
```bash
|
||||
# Navigate to project directory
|
||||
cd /path/to/daily-notification-plugin
|
||||
|
||||
# Build plugin for iOS
|
||||
./scripts/build-native.sh --platform ios
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- Compiles TypeScript to JavaScript
|
||||
- Builds iOS native code (Swift)
|
||||
- Creates plugin framework
|
||||
- Builds for simulator
|
||||
|
||||
#### 4. Build Native iOS Development App
|
||||
|
||||
```bash
|
||||
# Navigate to iOS directory
|
||||
cd ios
|
||||
|
||||
# Install CocoaPods dependencies
|
||||
pod install
|
||||
|
||||
# Build the development app for simulator
|
||||
cd App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
-derivedDataPath build/derivedData \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
clean build
|
||||
```
|
||||
|
||||
#### 5. Install App on Simulator
|
||||
|
||||
```bash
|
||||
# Find the built app
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d -path "*/Build/Products/*-iphonesimulator/*.app" | head -1)
|
||||
|
||||
# Install app on simulator
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
```
|
||||
|
||||
#### 6. Launch the App
|
||||
|
||||
```bash
|
||||
# Get bundle identifier from Info.plist
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
|
||||
# Launch the app
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
|
||||
# Example:
|
||||
# xcrun simctl launch booted com.timesafari.dailynotification
|
||||
```
|
||||
|
||||
#### 7. Monitor App Logs
|
||||
|
||||
```bash
|
||||
# View all logs
|
||||
xcrun simctl spawn booted log stream
|
||||
|
||||
# Filter for specific processes
|
||||
xcrun simctl spawn booted log stream --predicate 'processImagePath contains "App"'
|
||||
|
||||
# View system logs
|
||||
log stream --predicate 'processImagePath contains "App"' --level debug
|
||||
```
|
||||
|
||||
### Complete Command Sequence for Native iOS App
|
||||
|
||||
```bash
|
||||
# 1. Boot simulator
|
||||
xcrun simctl boot "iPhone 15 Pro" || open -a Simulator
|
||||
|
||||
# 2. Build plugin
|
||||
cd /path/to/daily-notification-plugin
|
||||
./scripts/build-native.sh --platform ios
|
||||
|
||||
# 3. Build native iOS app
|
||||
cd ios
|
||||
pod install
|
||||
cd App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
# 4. Install app
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d | head -1)
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
|
||||
# 5. Launch app
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scenario 2: Vue 3 Test App (`test-apps/daily-notification-test`)
|
||||
|
||||
The `test-apps/daily-notification-test` directory contains a full-featured Vue 3 Capacitor app with comprehensive testing interface, similar to the Android test app.
|
||||
|
||||
### Step-by-Step Process
|
||||
|
||||
#### 1. Check Available Simulators
|
||||
|
||||
```bash
|
||||
# List available iOS simulators
|
||||
xcrun simctl list devices available
|
||||
```
|
||||
|
||||
#### 2. Boot a Simulator
|
||||
|
||||
```bash
|
||||
# Boot a specific simulator device
|
||||
xcrun simctl boot "iPhone 15 Pro"
|
||||
|
||||
# Or open Simulator UI
|
||||
open -a Simulator
|
||||
```
|
||||
|
||||
#### 3. Build the Plugin
|
||||
|
||||
```bash
|
||||
# Navigate to project directory
|
||||
cd /path/to/daily-notification-plugin
|
||||
|
||||
# Build plugin for iOS
|
||||
./scripts/build-native.sh --platform ios
|
||||
```
|
||||
|
||||
#### 4. Set Up Vue 3 Test App iOS Project
|
||||
|
||||
```bash
|
||||
# Navigate to test app
|
||||
cd test-apps/daily-notification-test
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Add iOS platform (if not already added)
|
||||
npx cap add ios
|
||||
|
||||
# Sync web assets with iOS project
|
||||
npx cap sync ios
|
||||
```
|
||||
|
||||
#### 5. Build Vue 3 Test App
|
||||
|
||||
```bash
|
||||
# Build web assets (Vue 3 app)
|
||||
npm run build
|
||||
|
||||
# Sync with iOS project
|
||||
npx cap sync ios
|
||||
|
||||
# Build iOS app for simulator
|
||||
cd ios/App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
-derivedDataPath build/derivedData \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
```
|
||||
|
||||
#### 6. Install App on Simulator
|
||||
|
||||
```bash
|
||||
# Find the built app
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d -path "*/Build/Products/*-iphonesimulator/*.app" | head -1)
|
||||
|
||||
# Install app on simulator
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
```
|
||||
|
||||
#### 7. Launch the App
|
||||
|
||||
```bash
|
||||
# Get bundle identifier
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
|
||||
# Launch the app
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
|
||||
# Example:
|
||||
# xcrun simctl launch booted com.timesafari.dailynotification.test
|
||||
```
|
||||
|
||||
### Complete Command Sequence for Vue 3 Test App
|
||||
|
||||
```bash
|
||||
# 1. Boot simulator
|
||||
xcrun simctl boot "iPhone 15 Pro" || open -a Simulator
|
||||
|
||||
# 2. Build plugin
|
||||
cd /path/to/daily-notification-plugin
|
||||
./scripts/build-native.sh --platform ios
|
||||
|
||||
# 3. Set up Vue 3 test app
|
||||
cd test-apps/daily-notification-test
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# 4. Sync with iOS
|
||||
npx cap sync ios
|
||||
|
||||
# 5. Build iOS app
|
||||
cd ios/App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
# 6. Install app
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d | head -1)
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
|
||||
# 7. Launch app
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternative Methods
|
||||
|
||||
### Method 1: Using Capacitor CLI (Vue 3 Test App Only)
|
||||
|
||||
```bash
|
||||
cd test-apps/daily-notification-test
|
||||
|
||||
# Build and run in one command
|
||||
npx cap run ios
|
||||
|
||||
# This will:
|
||||
# - Build the plugin
|
||||
# - Sync web assets
|
||||
# - Build and install app
|
||||
# - Launch in simulator
|
||||
```
|
||||
|
||||
**Note**: This method only works for the Vue 3 test app, not the native iOS development app.
|
||||
|
||||
### Method 2: Using Automated Scripts
|
||||
|
||||
#### For Native iOS App (`ios/App`)
|
||||
|
||||
Create `scripts/build-and-deploy-native-ios.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🔨 Building plugin..."
|
||||
cd /path/to/daily-notification-plugin
|
||||
./scripts/build-native.sh --platform ios
|
||||
|
||||
echo "📱 Booting simulator..."
|
||||
xcrun simctl boot "iPhone 15 Pro" 2>/dev/null || open -a Simulator
|
||||
|
||||
echo "🏗️ Building native iOS app..."
|
||||
cd ios
|
||||
pod install
|
||||
cd App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d | head -1)
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
```
|
||||
|
||||
#### For Vue 3 Test App
|
||||
|
||||
Use the existing script:
|
||||
```bash
|
||||
cd test-apps/daily-notification-test
|
||||
./scripts/build-and-deploy-ios.sh
|
||||
```
|
||||
|
||||
### Method 3: Manual Xcode Build
|
||||
|
||||
```bash
|
||||
# Open project in Xcode
|
||||
open ios/App/App.xcworkspace # For native app
|
||||
# OR
|
||||
open test-apps/daily-notification-test/ios/App/App.xcworkspace # For Vue 3 app
|
||||
|
||||
# Then:
|
||||
# 1. Select simulator target
|
||||
# 2. Click Run button (⌘R)
|
||||
# 3. App builds and launches automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Simulator Management
|
||||
|
||||
### List Available Devices
|
||||
|
||||
```bash
|
||||
# List all devices
|
||||
xcrun simctl list devices
|
||||
|
||||
# List only available devices
|
||||
xcrun simctl list devices available
|
||||
|
||||
# List booted devices
|
||||
xcrun simctl list devices | grep Booted
|
||||
```
|
||||
|
||||
### Shutdown Simulator
|
||||
|
||||
```bash
|
||||
# Shutdown specific device
|
||||
xcrun simctl shutdown "iPhone 15 Pro"
|
||||
|
||||
# Shutdown all devices
|
||||
xcrun simctl shutdown all
|
||||
```
|
||||
|
||||
### Reset Simulator
|
||||
|
||||
```bash
|
||||
# Erase all content and settings
|
||||
xcrun simctl erase "iPhone 15 Pro"
|
||||
|
||||
# Reset and boot
|
||||
xcrun simctl erase "iPhone 15 Pro" && xcrun simctl boot "iPhone 15 Pro"
|
||||
```
|
||||
|
||||
### Delete App from Simulator
|
||||
|
||||
```bash
|
||||
# Uninstall app (replace with correct bundle ID)
|
||||
xcrun simctl uninstall booted com.timesafari.dailynotification
|
||||
# OR for Vue 3 test app:
|
||||
xcrun simctl uninstall booted com.timesafari.dailynotification.test
|
||||
|
||||
# Or reset entire simulator
|
||||
xcrun simctl erase booted
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comparison: Native iOS App vs Vue 3 Test App
|
||||
|
||||
| Feature | Native iOS App (`ios/App`) | Vue 3 Test App (`test-apps/...`) |
|
||||
|---------|---------------------------|----------------------------------|
|
||||
| **Purpose** | Quick plugin development testing | Comprehensive testing with UI |
|
||||
| **Frontend** | Simple HTML/Capacitor | Vue 3 with full UI |
|
||||
| **Build Steps** | Plugin + iOS build | Plugin + Vue build + iOS build |
|
||||
| **Capacitor Sync** | Not required | Required (`npx cap sync ios`) |
|
||||
| **Best For** | Quick native testing | Full integration testing |
|
||||
| **Bundle ID** | `com.timesafari.dailynotification` | `com.timesafari.dailynotification.test` |
|
||||
|
||||
---
|
||||
|
||||
## Comparison with Android
|
||||
|
||||
| Task | Android Native App | iOS Native App | Vue 3 Test App (iOS) |
|
||||
|------|-------------------|----------------|---------------------|
|
||||
| List devices | `emulator -list-avds` | `xcrun simctl list devices` | `xcrun simctl list devices` |
|
||||
| Boot device | `emulator -avd <name>` | `xcrun simctl boot <name>` | `xcrun simctl boot <name>` |
|
||||
| Install app | `adb install <apk>` | `xcrun simctl install booted <app>` | `xcrun simctl install booted <app>` |
|
||||
| Launch app | `adb shell am start` | `xcrun simctl launch booted <bundle>` | `xcrun simctl launch booted <bundle>` |
|
||||
| View logs | `adb logcat` | `xcrun simctl spawn booted log stream` | `xcrun simctl spawn booted log stream` |
|
||||
| Build command | `./gradlew assembleDebug` | `xcodebuild -workspace ...` | `xcodebuild -workspace ...` |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Simulator Won't Boot
|
||||
|
||||
```bash
|
||||
# Check Xcode command line tools
|
||||
xcode-select -p
|
||||
|
||||
# Reinstall command line tools
|
||||
sudo xcode-select --reset
|
||||
|
||||
# Verify simulator runtime
|
||||
xcrun simctl runtime list
|
||||
```
|
||||
|
||||
### Build Fails
|
||||
|
||||
```bash
|
||||
# Clean build folder
|
||||
cd ios/App # or ios/App for Vue 3 app
|
||||
xcodebuild clean -workspace App.xcworkspace -scheme App
|
||||
|
||||
# Reinstall CocoaPods dependencies
|
||||
cd ../.. # Back to ios/ directory
|
||||
pod install --repo-update
|
||||
|
||||
# Rebuild
|
||||
cd App
|
||||
xcodebuild -workspace App.xcworkspace -scheme App -configuration Debug
|
||||
```
|
||||
|
||||
### App Won't Install
|
||||
|
||||
```bash
|
||||
# Check if simulator is booted
|
||||
xcrun simctl list devices | grep Booted
|
||||
|
||||
# Verify app path exists
|
||||
ls -la ios/App/build/derivedData/Build/Products/Debug-iphonesimulator/
|
||||
|
||||
# Check bundle identifier
|
||||
plutil -extract CFBundleIdentifier raw ios/App/App/Info.plist
|
||||
```
|
||||
|
||||
### Vue 3 App: Web Assets Not Syncing
|
||||
|
||||
```bash
|
||||
# Rebuild web assets
|
||||
cd test-apps/daily-notification-test
|
||||
npm run build
|
||||
|
||||
# Force sync
|
||||
npx cap sync ios --force
|
||||
|
||||
# Verify assets are synced
|
||||
ls -la ios/App/App/public/
|
||||
```
|
||||
|
||||
### Logs Not Showing
|
||||
|
||||
```bash
|
||||
# Use Console.app for better log viewing
|
||||
open -a Console
|
||||
|
||||
# Or use log command with filters
|
||||
log stream --predicate 'processImagePath contains "App"' --level debug --style compact
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Capacitor iOS Documentation](https://capacitorjs.com/docs/ios)
|
||||
- [Xcode Command Line Tools](https://developer.apple.com/xcode/resources/)
|
||||
- [Simulator Documentation](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device)
|
||||
|
||||
---
|
||||
|
||||
**Note**: iOS development requires macOS. This guide assumes you're running on a Mac with Xcode installed.
|
||||
|
||||
**Key Distinction**:
|
||||
- **`ios/App`** = Native iOS development app (simple, for quick testing)
|
||||
- **`test-apps/daily-notification-test`** = Vue 3 test app (full-featured, for comprehensive testing)
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>DailyNotification Plugin Test</title>
|
||||
<style>
|
||||
body {
|
||||
@@ -14,98 +17,627 @@
|
||||
color: white;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
.section {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.section h2 {
|
||||
margin-top: 0;
|
||||
color: #ffd700;
|
||||
}
|
||||
.button {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 15px 30px;
|
||||
margin: 10px;
|
||||
border-radius: 25px;
|
||||
padding: 12px 24px;
|
||||
margin: 8px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-block;
|
||||
}
|
||||
.button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
.status {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.success { color: #4CAF50; }
|
||||
.error { color: #f44336; }
|
||||
.warning { color: #ff9800; }
|
||||
.info { color: #2196F3; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.input-group {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.input-group input, .input-group select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
.input-group input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔔 DailyNotification Plugin Test</h1>
|
||||
<p>Test the DailyNotification plugin functionality</p>
|
||||
|
||||
<button class="button" onclick="testPlugin()">Test Plugin</button>
|
||||
<!-- Plugin Status Section -->
|
||||
<div class="section">
|
||||
<h2>📊 Plugin Status</h2>
|
||||
<div class="grid">
|
||||
<button class="button" onclick="checkPluginAvailability()">Check Availability</button>
|
||||
<button class="button" onclick="getNotificationStatus()">Get Status</button>
|
||||
<button class="button" onclick="checkPermissions()">Check Permissions</button>
|
||||
<button class="button" onclick="getBatteryStatus()">Battery Status</button>
|
||||
</div>
|
||||
<div id="status" class="status">Ready to test...</div>
|
||||
</div>
|
||||
|
||||
<!-- Permission Management Section -->
|
||||
<div class="section">
|
||||
<h2>🔐 Permission Management</h2>
|
||||
<div class="grid">
|
||||
<button class="button" onclick="requestPermissions()">Request Permissions</button>
|
||||
<button class="button" onclick="requestExactAlarmPermission()">Request Exact Alarm</button>
|
||||
<button class="button" onclick="openExactAlarmSettings()">Open Settings</button>
|
||||
<button class="button" onclick="requestBatteryOptimizationExemption()">Battery Exemption</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Scheduling Section -->
|
||||
<div class="section">
|
||||
<h2>⏰ Notification Scheduling</h2>
|
||||
<div class="input-group">
|
||||
<label for="notificationUrl">Content URL:</label>
|
||||
<input type="text" id="notificationUrl" placeholder="https://api.example.com/daily-content" value="https://api.example.com/daily-content">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="notificationTime">Schedule Time:</label>
|
||||
<input type="time" id="notificationTime" value="09:00">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="notificationTitle">Title:</label>
|
||||
<input type="text" id="notificationTitle" placeholder="Daily Notification" value="Daily Notification">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="notificationBody">Body:</label>
|
||||
<input type="text" id="notificationBody" placeholder="Your daily content is ready!" value="Your daily content is ready!">
|
||||
</div>
|
||||
<div class="grid">
|
||||
<button class="button" onclick="scheduleNotification()">Schedule Notification</button>
|
||||
<button class="button" onclick="cancelAllNotifications()">Cancel All</button>
|
||||
<button class="button" onclick="getLastNotification()">Get Last</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Section -->
|
||||
<div class="section">
|
||||
<h2>⚙️ Plugin Configuration</h2>
|
||||
<div class="input-group">
|
||||
<label for="configUrl">Fetch URL:</label>
|
||||
<input type="text" id="configUrl" placeholder="https://api.example.com/content" value="https://api.example.com/content">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="configTime">Schedule Time:</label>
|
||||
<input type="time" id="configTime" value="09:00">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="configRetryCount">Retry Count:</label>
|
||||
<input type="number" id="configRetryCount" value="3" min="0" max="10">
|
||||
</div>
|
||||
<div class="grid">
|
||||
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
|
||||
<button class="button" onclick="checkStatus()">Check Status</button>
|
||||
|
||||
<div id="status" class="status">
|
||||
Ready to test...
|
||||
<button class="button" onclick="updateSettings()">Update Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Features Section -->
|
||||
<div class="section">
|
||||
<h2>🚀 Advanced Features</h2>
|
||||
<div class="grid">
|
||||
<button class="button" onclick="getExactAlarmStatus()">Exact Alarm Status</button>
|
||||
<button class="button" onclick="getRebootRecoveryStatus()">Reboot Recovery</button>
|
||||
<button class="button" onclick="getRollingWindowStats()">Rolling Window</button>
|
||||
<button class="button" onclick="maintainRollingWindow()">Maintain Window</button>
|
||||
<button class="button" onclick="getContentCache()">Content Cache</button>
|
||||
<button class="button" onclick="clearContentCache()">Clear Cache</button>
|
||||
<button class="button" onclick="getContentHistory()">Content History</button>
|
||||
<button class="button" onclick="getDualScheduleStatus()">Dual Schedule</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { DailyNotification } from '@timesafari/daily-notification-plugin';
|
||||
|
||||
window.Capacitor = Capacitor;
|
||||
window.DailyNotification = DailyNotification;
|
||||
|
||||
window.testPlugin = async function() {
|
||||
const status = document.getElementById('status');
|
||||
status.innerHTML = 'Testing plugin...';
|
||||
|
||||
<script>
|
||||
console.log('🔔 DailyNotification Plugin Test Interface Loading...');
|
||||
|
||||
// Global variables
|
||||
let plugin = null;
|
||||
let isPluginAvailable = false;
|
||||
|
||||
// Initialize plugin on page load
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
console.log('📱 DOM loaded, initializing plugin...');
|
||||
await initializePlugin();
|
||||
});
|
||||
|
||||
// Initialize the real DailyNotification plugin
|
||||
async function initializePlugin() {
|
||||
try {
|
||||
// Plugin is loaded and ready
|
||||
status.innerHTML = 'Plugin is loaded and ready!';
|
||||
// Try to access the real plugin through Capacitor
|
||||
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DailyNotification) {
|
||||
plugin = window.Capacitor.Plugins.DailyNotification;
|
||||
isPluginAvailable = true;
|
||||
console.log('✅ Real DailyNotification plugin found!');
|
||||
updateStatus('success', '✅ Real DailyNotification plugin loaded successfully!');
|
||||
} else {
|
||||
// Fallback to mock for development
|
||||
console.log('⚠️ Real plugin not available, using mock for development');
|
||||
plugin = createMockPlugin();
|
||||
isPluginAvailable = false;
|
||||
updateStatus('warning', '⚠️ Using mock plugin (real plugin not available)');
|
||||
}
|
||||
} catch (error) {
|
||||
status.innerHTML = `Plugin test failed: ${error.message}`;
|
||||
console.error('❌ Plugin initialization failed:', error);
|
||||
updateStatus('error', `❌ Plugin initialization failed: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
window.configurePlugin = async function() {
|
||||
const status = document.getElementById('status');
|
||||
status.innerHTML = 'Configuring plugin...';
|
||||
|
||||
}
|
||||
|
||||
// Create mock plugin for development/testing
|
||||
function createMockPlugin() {
|
||||
return {
|
||||
configure: async (options) => {
|
||||
console.log('Mock configure called with:', options);
|
||||
return Promise.resolve();
|
||||
},
|
||||
getNotificationStatus: async () => {
|
||||
return Promise.resolve({
|
||||
isEnabled: true,
|
||||
isScheduled: true,
|
||||
lastNotificationTime: Date.now() - 86400000,
|
||||
nextNotificationTime: Date.now() + 3600000,
|
||||
pending: 1,
|
||||
settings: { url: 'https://api.example.com/content', time: '09:00' },
|
||||
error: null
|
||||
});
|
||||
},
|
||||
checkPermissions: async () => {
|
||||
return Promise.resolve({
|
||||
notifications: 'granted',
|
||||
backgroundRefresh: 'granted',
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true
|
||||
});
|
||||
},
|
||||
requestPermissions: async () => {
|
||||
return Promise.resolve({
|
||||
notifications: 'granted',
|
||||
backgroundRefresh: 'granted',
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true
|
||||
});
|
||||
},
|
||||
scheduleDailyNotification: async (options) => {
|
||||
console.log('Mock scheduleDailyNotification called with:', options);
|
||||
return Promise.resolve();
|
||||
},
|
||||
cancelAllNotifications: async () => {
|
||||
console.log('Mock cancelAllNotifications called');
|
||||
return Promise.resolve();
|
||||
},
|
||||
getLastNotification: async () => {
|
||||
return Promise.resolve({
|
||||
id: 'mock-123',
|
||||
title: 'Mock Notification',
|
||||
body: 'This is a mock notification',
|
||||
timestamp: Date.now() - 3600000,
|
||||
url: 'https://example.com'
|
||||
});
|
||||
},
|
||||
getBatteryStatus: async () => {
|
||||
return Promise.resolve({
|
||||
level: 85,
|
||||
isCharging: false,
|
||||
powerState: 1,
|
||||
isOptimizationExempt: false
|
||||
});
|
||||
},
|
||||
getExactAlarmStatus: async () => {
|
||||
return Promise.resolve({
|
||||
supported: true,
|
||||
enabled: true,
|
||||
canSchedule: true,
|
||||
fallbackWindow: '±15 minutes'
|
||||
});
|
||||
},
|
||||
requestExactAlarmPermission: async () => {
|
||||
console.log('Mock requestExactAlarmPermission called');
|
||||
return Promise.resolve();
|
||||
},
|
||||
openExactAlarmSettings: async () => {
|
||||
console.log('Mock openExactAlarmSettings called');
|
||||
return Promise.resolve();
|
||||
},
|
||||
requestBatteryOptimizationExemption: async () => {
|
||||
console.log('Mock requestBatteryOptimizationExemption called');
|
||||
return Promise.resolve();
|
||||
},
|
||||
getRebootRecoveryStatus: async () => {
|
||||
return Promise.resolve({
|
||||
inProgress: false,
|
||||
lastRecoveryTime: Date.now() - 86400000,
|
||||
timeSinceLastRecovery: 86400000,
|
||||
recoveryNeeded: false
|
||||
});
|
||||
},
|
||||
getRollingWindowStats: async () => {
|
||||
return Promise.resolve({
|
||||
stats: 'Window: 7 days, Notifications: 5, Success rate: 100%',
|
||||
maintenanceNeeded: false,
|
||||
timeUntilNextMaintenance: 3600000
|
||||
});
|
||||
},
|
||||
maintainRollingWindow: async () => {
|
||||
console.log('Mock maintainRollingWindow called');
|
||||
return Promise.resolve();
|
||||
},
|
||||
getContentCache: async () => {
|
||||
return Promise.resolve({
|
||||
'cache-key-1': { content: 'Mock cached content', timestamp: Date.now() },
|
||||
'cache-key-2': { content: 'Another mock item', timestamp: Date.now() - 3600000 }
|
||||
});
|
||||
},
|
||||
clearContentCache: async () => {
|
||||
console.log('Mock clearContentCache called');
|
||||
return Promise.resolve();
|
||||
},
|
||||
getContentHistory: async () => {
|
||||
return Promise.resolve([
|
||||
{ id: '1', timestamp: Date.now() - 86400000, success: true, content: 'Mock content 1' },
|
||||
{ id: '2', timestamp: Date.now() - 172800000, success: true, content: 'Mock content 2' }
|
||||
]);
|
||||
},
|
||||
getDualScheduleStatus: async () => {
|
||||
return Promise.resolve({
|
||||
isActive: true,
|
||||
contentSchedule: { nextRun: Date.now() + 3600000, isEnabled: true },
|
||||
userSchedule: { nextRun: Date.now() + 7200000, isEnabled: true },
|
||||
lastContentFetch: Date.now() - 3600000,
|
||||
lastUserNotification: Date.now() - 7200000
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Utility function to update status display
|
||||
function updateStatus(type, message) {
|
||||
const statusEl = document.getElementById('status');
|
||||
statusEl.className = `status ${type}`;
|
||||
statusEl.textContent = message;
|
||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||||
}
|
||||
|
||||
// Plugin availability check
|
||||
async function checkPluginAvailability() {
|
||||
updateStatus('info', '🔍 Checking plugin availability...');
|
||||
try {
|
||||
await DailyNotification.configure({
|
||||
fetchUrl: 'https://api.example.com/daily-content',
|
||||
scheduleTime: '09:00',
|
||||
enableNotifications: true
|
||||
});
|
||||
status.innerHTML = 'Plugin configured successfully!';
|
||||
if (plugin) {
|
||||
updateStatus('success', `✅ Plugin available: ${isPluginAvailable ? 'Real plugin' : 'Mock plugin'}`);
|
||||
} else {
|
||||
updateStatus('error', '❌ Plugin not available');
|
||||
}
|
||||
} catch (error) {
|
||||
status.innerHTML = `Configuration failed: ${error.message}`;
|
||||
updateStatus('error', `❌ Availability check failed: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
window.checkStatus = async function() {
|
||||
const status = document.getElementById('status');
|
||||
status.innerHTML = 'Checking plugin status...';
|
||||
|
||||
}
|
||||
|
||||
// Get notification status
|
||||
async function getNotificationStatus() {
|
||||
updateStatus('info', '📊 Getting notification status...');
|
||||
try {
|
||||
const result = await DailyNotification.getStatus();
|
||||
status.innerHTML = `Plugin status: ${JSON.stringify(result, null, 2)}`;
|
||||
const status = await plugin.getNotificationStatus();
|
||||
updateStatus('success', `📊 Status: ${JSON.stringify(status, null, 2)}`);
|
||||
} catch (error) {
|
||||
status.innerHTML = `Status check failed: ${error.message}`;
|
||||
updateStatus('error', `❌ Status check failed: ${error.message}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
async function checkPermissions() {
|
||||
updateStatus('info', '🔐 Checking permissions...');
|
||||
try {
|
||||
const permissions = await plugin.checkPermissions();
|
||||
updateStatus('success', `🔐 Permissions: ${JSON.stringify(permissions, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Permission check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Request permissions
|
||||
async function requestPermissions() {
|
||||
updateStatus('info', '🔐 Requesting permissions...');
|
||||
try {
|
||||
const result = await plugin.requestPermissions();
|
||||
updateStatus('success', `🔐 Permission result: ${JSON.stringify(result, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Permission request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get battery status
|
||||
async function getBatteryStatus() {
|
||||
updateStatus('info', '🔋 Getting battery status...');
|
||||
try {
|
||||
const battery = await plugin.getBatteryStatus();
|
||||
updateStatus('success', `🔋 Battery: ${JSON.stringify(battery, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Battery check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule notification
|
||||
async function scheduleNotification() {
|
||||
updateStatus('info', '⏰ Scheduling notification...');
|
||||
try {
|
||||
const timeInput = document.getElementById('notificationTime').value;
|
||||
const [hours, minutes] = timeInput.split(':');
|
||||
const now = new Date();
|
||||
const scheduledTime = new Date();
|
||||
scheduledTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
||||
|
||||
// If scheduled time is in the past, schedule for tomorrow
|
||||
if (scheduledTime <= now) {
|
||||
scheduledTime.setDate(scheduledTime.getDate() + 1);
|
||||
}
|
||||
|
||||
// Calculate prefetch time (5 minutes before notification)
|
||||
const prefetchTime = new Date(scheduledTime.getTime() - 300000); // 5 minutes
|
||||
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
||||
const notificationTimeReadable = scheduledTime.toLocaleTimeString();
|
||||
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
|
||||
prefetchTime.getMinutes().toString().padStart(2, '0');
|
||||
const notificationTimeString = scheduledTime.getHours().toString().padStart(2, '0') + ':' +
|
||||
scheduledTime.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
const options = {
|
||||
url: document.getElementById('notificationUrl').value,
|
||||
time: timeInput,
|
||||
title: document.getElementById('notificationTitle').value,
|
||||
body: document.getElementById('notificationBody').value,
|
||||
sound: true,
|
||||
priority: 'high'
|
||||
};
|
||||
await plugin.scheduleDailyNotification(options);
|
||||
updateStatus('success', `✅ Notification scheduled!<br>` +
|
||||
`📥 Prefetch: ${prefetchTimeReadable} (${prefetchTimeString})<br>` +
|
||||
`🔔 Notification: ${notificationTimeReadable} (${notificationTimeString})`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Scheduling failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel all notifications
|
||||
async function cancelAllNotifications() {
|
||||
updateStatus('info', '❌ Cancelling all notifications...');
|
||||
try {
|
||||
await plugin.cancelAllNotifications();
|
||||
updateStatus('success', '❌ All notifications cancelled');
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Cancel failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get last notification
|
||||
async function getLastNotification() {
|
||||
updateStatus('info', '📱 Getting last notification...');
|
||||
try {
|
||||
const notification = await plugin.getLastNotification();
|
||||
updateStatus('success', `📱 Last notification: ${JSON.stringify(notification, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Get last notification failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure plugin
|
||||
async function configurePlugin() {
|
||||
updateStatus('info', '⚙️ Configuring plugin...');
|
||||
try {
|
||||
const config = {
|
||||
fetchUrl: document.getElementById('configUrl').value,
|
||||
scheduleTime: document.getElementById('configTime').value,
|
||||
retryCount: parseInt(document.getElementById('configRetryCount').value),
|
||||
enableNotifications: true,
|
||||
offlineFallback: true
|
||||
};
|
||||
await plugin.configure(config);
|
||||
updateStatus('success', `⚙️ Plugin configured: ${JSON.stringify(config, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Configuration failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update settings
|
||||
async function updateSettings() {
|
||||
updateStatus('info', '⚙️ Updating settings...');
|
||||
try {
|
||||
const settings = {
|
||||
url: document.getElementById('configUrl').value,
|
||||
time: document.getElementById('configTime').value,
|
||||
retryCount: parseInt(document.getElementById('configRetryCount').value),
|
||||
sound: true,
|
||||
priority: 'high'
|
||||
};
|
||||
await plugin.updateSettings(settings);
|
||||
updateStatus('success', `⚙️ Settings updated: ${JSON.stringify(settings, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Settings update failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get exact alarm status
|
||||
async function getExactAlarmStatus() {
|
||||
updateStatus('info', '⏰ Getting exact alarm status...');
|
||||
try {
|
||||
const status = await plugin.getExactAlarmStatus();
|
||||
updateStatus('success', `⏰ Exact alarm status: ${JSON.stringify(status, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Exact alarm check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Request exact alarm permission
|
||||
async function requestExactAlarmPermission() {
|
||||
updateStatus('info', '⏰ Requesting exact alarm permission...');
|
||||
try {
|
||||
await plugin.requestExactAlarmPermission();
|
||||
updateStatus('success', '⏰ Exact alarm permission requested');
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Exact alarm permission request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Open exact alarm settings
|
||||
async function openExactAlarmSettings() {
|
||||
updateStatus('info', '⚙️ Opening exact alarm settings...');
|
||||
try {
|
||||
await plugin.openExactAlarmSettings();
|
||||
updateStatus('success', '⚙️ Exact alarm settings opened');
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Open settings failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Request battery optimization exemption
|
||||
async function requestBatteryOptimizationExemption() {
|
||||
updateStatus('info', '🔋 Requesting battery optimization exemption...');
|
||||
try {
|
||||
await plugin.requestBatteryOptimizationExemption();
|
||||
updateStatus('success', '🔋 Battery optimization exemption requested');
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Battery exemption request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get reboot recovery status
|
||||
async function getRebootRecoveryStatus() {
|
||||
updateStatus('info', '🔄 Getting reboot recovery status...');
|
||||
try {
|
||||
const status = await plugin.getRebootRecoveryStatus();
|
||||
updateStatus('success', `🔄 Reboot recovery status: ${JSON.stringify(status, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Reboot recovery check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get rolling window stats
|
||||
async function getRollingWindowStats() {
|
||||
updateStatus('info', '📊 Getting rolling window stats...');
|
||||
try {
|
||||
const stats = await plugin.getRollingWindowStats();
|
||||
updateStatus('success', `📊 Rolling window stats: ${JSON.stringify(stats, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Rolling window stats failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Maintain rolling window
|
||||
async function maintainRollingWindow() {
|
||||
updateStatus('info', '🔧 Maintaining rolling window...');
|
||||
try {
|
||||
await plugin.maintainRollingWindow();
|
||||
updateStatus('success', '🔧 Rolling window maintenance completed');
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Rolling window maintenance failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get content cache
|
||||
async function getContentCache() {
|
||||
updateStatus('info', '💾 Getting content cache...');
|
||||
try {
|
||||
const cache = await plugin.getContentCache();
|
||||
updateStatus('success', `💾 Content cache: ${JSON.stringify(cache, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Content cache check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear content cache
|
||||
async function clearContentCache() {
|
||||
updateStatus('info', '🗑️ Clearing content cache...');
|
||||
try {
|
||||
await plugin.clearContentCache();
|
||||
updateStatus('success', '🗑️ Content cache cleared');
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Clear cache failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get content history
|
||||
async function getContentHistory() {
|
||||
updateStatus('info', '📚 Getting content history...');
|
||||
try {
|
||||
const history = await plugin.getContentHistory();
|
||||
updateStatus('success', `📚 Content history: ${JSON.stringify(history, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Content history check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get dual schedule status
|
||||
async function getDualScheduleStatus() {
|
||||
updateStatus('info', '🔄 Getting dual schedule status...');
|
||||
try {
|
||||
const status = await plugin.getDualScheduleStatus();
|
||||
updateStatus('success', `🔄 Dual schedule status: ${JSON.stringify(status, null, 2)}`);
|
||||
} catch (error) {
|
||||
updateStatus('error', `❌ Dual schedule check failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🔔 DailyNotification Plugin Test Interface Loaded Successfully!');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,8 +8,8 @@ Pod::Spec.new do |s|
|
||||
s.source = { :git => 'https://github.com/timesafari/daily-notification-plugin.git', :tag => s.version.to_s }
|
||||
s.source_files = 'Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
||||
s.ios.deployment_target = '13.0'
|
||||
s.dependency 'Capacitor', '~> 5.0.0'
|
||||
s.dependency 'CapacitorCordova', '~> 5.0.0'
|
||||
s.dependency 'Capacitor', '~> 6.0'
|
||||
s.dependency 'CapacitorCordova', '~> 6.0'
|
||||
s.swift_version = '5.1'
|
||||
s.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1' }
|
||||
s.deprecated = false
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
PODS:
|
||||
- Capacitor (5.0.0):
|
||||
- Capacitor (6.2.1):
|
||||
- CapacitorCordova
|
||||
- CapacitorCordova (5.0.0)
|
||||
- CapacitorCordova (6.2.1)
|
||||
- DailyNotificationPlugin (1.0.0):
|
||||
- Capacitor (~> 5.0.0)
|
||||
- CapacitorCordova (~> 5.0.0)
|
||||
- Capacitor (~> 6.0)
|
||||
- CapacitorCordova (~> 6.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- "Capacitor (from `../node_modules/@capacitor/ios`)"
|
||||
@@ -20,9 +20,9 @@ EXTERNAL SOURCES:
|
||||
:path: "."
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Capacitor: ba8cd5cce13c6ab3c4faf7ef98487be481c9c1c8
|
||||
CapacitorCordova: 4ea17670ee562680988a7ce9db68dee5160fe564
|
||||
DailyNotificationPlugin: 745a0606d51baec6fc9a025f1de1ade125ed193a
|
||||
Capacitor: 1e0d0e7330dea9f983b50da737d8918abcf273f8
|
||||
CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff
|
||||
DailyNotificationPlugin: 79f269b45580c89b044ece1cfe09293b7e974d98
|
||||
|
||||
PODFILE CHECKSUM: ac8c229d24347f6f83e67e6b95458e0b81e68f7c
|
||||
|
||||
|
||||
16
nvm-install.sh
Executable file
16
nvm-install.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Quick NVM setup script
|
||||
|
||||
set -e
|
||||
|
||||
echo "Installing NVM (Node Version Manager)..."
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||
|
||||
echo ""
|
||||
echo "NVM installed! Please run:"
|
||||
echo " source ~/.zshrc"
|
||||
echo ""
|
||||
echo "Then install Node.js with:"
|
||||
echo " nvm install --lts"
|
||||
echo " nvm use --lts"
|
||||
echo " nvm alias default node"
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -100,6 +100,7 @@
|
||||
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
@@ -650,6 +651,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
|
||||
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -752,6 +754,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -775,6 +778,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -2926,6 +2930,7 @@
|
||||
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
@@ -3129,6 +3134,7 @@
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -3544,6 +3550,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001688",
|
||||
"electron-to-chromium": "^1.5.73",
|
||||
@@ -4693,6 +4700,7 @@
|
||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -6250,6 +6258,7 @@
|
||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^29.7.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
@@ -7116,6 +7125,7 @@
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -9575,6 +9585,7 @@
|
||||
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
@@ -10527,6 +10538,7 @@
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
161
scripts/build-and-deploy-native-ios.sh
Executable file
161
scripts/build-and-deploy-native-ios.sh
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
# Native iOS Development App Build and Deploy Script
|
||||
# Builds and deploys the ios/App development app to iOS Simulator
|
||||
# Similar to android/app - a simple Capacitor app for plugin development
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Get script directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Check if we're in the project root
|
||||
if [ ! -d "$PROJECT_ROOT/ios/App" ]; then
|
||||
log_error "ios/App directory not found"
|
||||
log_info "This script must be run from the project root directory"
|
||||
log_info "Usage: cd /path/to/daily-notification-plugin && ./scripts/build-and-deploy-native-ios.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check prerequisites
|
||||
log_step "Checking prerequisites..."
|
||||
|
||||
if ! command -v xcodebuild &> /dev/null; then
|
||||
log_error "xcodebuild not found. Install Xcode command line tools:"
|
||||
log_info " xcode-select --install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pod &> /dev/null; then
|
||||
log_error "CocoaPods not found. Install with:"
|
||||
log_info " gem install cocoapods"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get simulator device (default to iPhone 15 Pro)
|
||||
SIMULATOR_DEVICE="${1:-iPhone 15 Pro}"
|
||||
log_info "Using simulator: $SIMULATOR_DEVICE"
|
||||
|
||||
# Boot simulator
|
||||
log_step "Booting simulator..."
|
||||
if xcrun simctl list devices | grep -q "$SIMULATOR_DEVICE.*Booted"; then
|
||||
log_info "Simulator already booted"
|
||||
else
|
||||
# Try to boot the device
|
||||
if xcrun simctl boot "$SIMULATOR_DEVICE" 2>/dev/null; then
|
||||
log_info "✓ Simulator booted"
|
||||
else
|
||||
log_warn "Could not boot simulator automatically"
|
||||
log_info "Opening Simulator app... (you may need to select device manually)"
|
||||
open -a Simulator
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build plugin
|
||||
log_step "Building plugin..."
|
||||
cd "$PROJECT_ROOT"
|
||||
if ! ./scripts/build-native.sh --platform ios; then
|
||||
log_error "Plugin build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install CocoaPods dependencies
|
||||
log_step "Installing CocoaPods dependencies..."
|
||||
cd "$PROJECT_ROOT/ios"
|
||||
if [ ! -f "Podfile.lock" ] || [ "Podfile" -nt "Podfile.lock" ]; then
|
||||
pod install
|
||||
else
|
||||
log_info "CocoaPods dependencies up to date"
|
||||
fi
|
||||
|
||||
# Build iOS app
|
||||
log_step "Building native iOS development app..."
|
||||
cd "$PROJECT_ROOT/ios/App"
|
||||
WORKSPACE="App.xcworkspace"
|
||||
SCHEME="App"
|
||||
CONFIG="Debug"
|
||||
SDK="iphonesimulator"
|
||||
|
||||
if ! xcodebuild -workspace "$WORKSPACE" \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration "$CONFIG" \
|
||||
-sdk "$SDK" \
|
||||
-destination "platform=iOS Simulator,name=$SIMULATOR_DEVICE" \
|
||||
-derivedDataPath build/derivedData \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
clean build; then
|
||||
log_error "iOS app build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find built app
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d -path "*/Build/Products/*-iphonesimulator/*.app" | head -1)
|
||||
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
log_error "Could not find built app"
|
||||
log_info "Searching in: build/derivedData"
|
||||
find build/derivedData -name "*.app" -type d 2>/dev/null | head -5
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Found app: $APP_PATH"
|
||||
|
||||
# Install app on simulator
|
||||
log_step "Installing app on simulator..."
|
||||
if xcrun simctl install booted "$APP_PATH"; then
|
||||
log_info "✓ App installed"
|
||||
else
|
||||
log_error "Failed to install app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get bundle identifier
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist 2>/dev/null || echo "com.timesafari.dailynotification")
|
||||
log_info "Bundle ID: $BUNDLE_ID"
|
||||
|
||||
# Launch app
|
||||
log_step "Launching app..."
|
||||
if xcrun simctl launch booted "$BUNDLE_ID"; then
|
||||
log_info "✓ App launched"
|
||||
else
|
||||
log_warn "App may already be running"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "✅ Build and deploy complete!"
|
||||
log_info ""
|
||||
log_info "To view logs:"
|
||||
log_info " xcrun simctl spawn booted log stream"
|
||||
log_info ""
|
||||
log_info "To uninstall app:"
|
||||
log_info " xcrun simctl uninstall booted $BUNDLE_ID"
|
||||
log_info ""
|
||||
log_info "Note: This is the native iOS development app (ios/App)"
|
||||
log_info "For the Vue 3 test app, use: test-apps/daily-notification-test/scripts/build-and-deploy-ios.sh"
|
||||
|
||||
@@ -7,6 +7,7 @@ set -e
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
@@ -22,6 +23,10 @@ log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Validation functions
|
||||
check_command() {
|
||||
if ! command -v $1 &> /dev/null; then
|
||||
@@ -31,9 +36,68 @@ check_command() {
|
||||
}
|
||||
|
||||
check_environment() {
|
||||
# Check for required tools
|
||||
local platform=$1
|
||||
|
||||
# Initialize NVM if available (for Node.js version management)
|
||||
if [ -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
log_info "Loading NVM..."
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||
[ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"
|
||||
|
||||
# Use default Node.js version if available
|
||||
if [ -f "$NVM_DIR/alias/default" ]; then
|
||||
DEFAULT_NODE=$(cat "$NVM_DIR/alias/default")
|
||||
if [ -n "$DEFAULT_NODE" ]; then
|
||||
nvm use default >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Use latest LTS if no default
|
||||
if ! command -v node &> /dev/null; then
|
||||
log_info "No default Node.js version set, using latest LTS..."
|
||||
nvm use --lts >/dev/null 2>&1 || nvm install --lts >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Common checks
|
||||
check_command "node"
|
||||
check_command "npm"
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node -v | cut -d. -f1 | tr -d 'v')
|
||||
if [ -z "$NODE_VERSION" ] || ! [[ "$NODE_VERSION" =~ ^[0-9]+$ ]]; then
|
||||
log_error "Could not determine Node.js version"
|
||||
log_error "Please install Node.js: nvm install --lts (if using NVM)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$NODE_VERSION" -lt 14 ]; then
|
||||
log_error "Node.js version 14 or higher is required (found: $NODE_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Platform-specific checks
|
||||
case $platform in
|
||||
"android")
|
||||
check_environment_android
|
||||
;;
|
||||
"ios")
|
||||
check_environment_ios
|
||||
;;
|
||||
"all")
|
||||
check_environment_android
|
||||
check_environment_ios
|
||||
;;
|
||||
*)
|
||||
log_error "Invalid platform: $platform"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_environment_android() {
|
||||
log_step "Checking Android environment..."
|
||||
|
||||
check_command "java"
|
||||
|
||||
# Check for Gradle Wrapper instead of system gradle
|
||||
@@ -41,31 +105,98 @@ check_environment() {
|
||||
log_error "Gradle wrapper not found at android/gradlew"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node -v | cut -d. -f1 | tr -d 'v')
|
||||
if [ "$NODE_VERSION" -lt 14 ]; then
|
||||
log_error "Node.js version 14 or higher is required"
|
||||
|
||||
# Check Java version (more robust parsing)
|
||||
JAVA_VERSION_OUTPUT=$(java -version 2>&1 | head -n 1)
|
||||
if [ -z "$JAVA_VERSION_OUTPUT" ]; then
|
||||
log_error "Could not determine Java version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Java version
|
||||
JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d. -f1)
|
||||
|
||||
# Try multiple parsing methods for different Java output formats
|
||||
JAVA_VERSION=$(echo "$JAVA_VERSION_OUTPUT" | grep -oE 'version "([0-9]+)' | grep -oE '[0-9]+' | head -1)
|
||||
|
||||
# Fallback: try to extract from "openjdk version" or "java version" format
|
||||
if [ -z "$JAVA_VERSION" ]; then
|
||||
JAVA_VERSION=$(echo "$JAVA_VERSION_OUTPUT" | sed -E 's/.*version "([0-9]+).*/\1/' | head -1)
|
||||
fi
|
||||
|
||||
# Validate we got a number
|
||||
if [ -z "$JAVA_VERSION" ] || ! [[ "$JAVA_VERSION" =~ ^[0-9]+$ ]]; then
|
||||
log_error "Could not parse Java version from: $JAVA_VERSION_OUTPUT"
|
||||
log_error "Please ensure Java is installed correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$JAVA_VERSION" -lt 11 ]; then
|
||||
log_error "Java version 11 or higher is required"
|
||||
log_error "Java version 11 or higher is required (found: $JAVA_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Check for Android SDK
|
||||
if [ -z "$ANDROID_HOME" ]; then
|
||||
log_error "ANDROID_HOME environment variable is not set"
|
||||
log_error "Set it with: export ANDROID_HOME=/path/to/android/sdk"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "✓ Android environment OK (Java $JAVA_VERSION)"
|
||||
}
|
||||
|
||||
check_environment_ios() {
|
||||
log_step "Checking iOS environment..."
|
||||
|
||||
# Check for Xcode command line tools
|
||||
if ! command -v xcodebuild &> /dev/null; then
|
||||
log_error "xcodebuild not found. Install Xcode Command Line Tools:"
|
||||
log_error " xcode-select --install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for CocoaPods
|
||||
if ! command -v pod &> /dev/null; then
|
||||
log_error "CocoaPods not found. Install with:"
|
||||
log_error " sudo gem install cocoapods"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for Swift
|
||||
if ! command -v swift &> /dev/null; then
|
||||
log_error "Swift compiler not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify workspace exists
|
||||
if [ ! -d "ios/DailyNotificationPlugin.xcworkspace" ]; then
|
||||
log_error "iOS workspace not found: ios/DailyNotificationPlugin.xcworkspace"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "✓ iOS environment OK"
|
||||
}
|
||||
|
||||
# Build functions
|
||||
build_typescript() {
|
||||
log_info "Building TypeScript..."
|
||||
|
||||
# Ensure npm dependencies are installed
|
||||
if [ ! -d "node_modules" ]; then
|
||||
log_step "Installing npm dependencies..."
|
||||
if ! npm install; then
|
||||
log_error "Failed to install npm dependencies"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Check if package.json changed (compare with package-lock.json)
|
||||
if [ -f "package-lock.json" ] && [ "package.json" -nt "package-lock.json" ]; then
|
||||
log_step "package.json changed, updating dependencies..."
|
||||
if ! npm install; then
|
||||
log_error "Failed to update npm dependencies"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
npm run clean
|
||||
if ! npm run build; then
|
||||
log_error "TypeScript build failed"
|
||||
@@ -149,33 +280,6 @@ build_android() {
|
||||
# =============================================================================
|
||||
# AUTOMATIC FIX: capacitor.build.gradle for Plugin Development Projects
|
||||
# =============================================================================
|
||||
#
|
||||
# PROBLEM: The capacitor.build.gradle file is auto-generated by Capacitor CLI
|
||||
# and includes a line that tries to load a file that doesn't exist in plugin
|
||||
# development projects:
|
||||
# apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
#
|
||||
# WHY THIS HAPPENS:
|
||||
# - This file is generated by 'npx cap sync', 'npx cap update', etc.
|
||||
# - It assumes a full Capacitor app with proper plugin integration
|
||||
# - Plugin development projects don't have the full Capacitor setup
|
||||
#
|
||||
# THE FIX:
|
||||
# - Comment out the problematic line to prevent build failures
|
||||
# - Add explanatory comment about why it's commented out
|
||||
# - This fix gets applied automatically every time the build script runs
|
||||
#
|
||||
# WHEN THIS FIX GETS OVERWRITTEN:
|
||||
# - Running 'npx cap sync' will regenerate the file and remove our fix
|
||||
# - Running 'npx cap update' will regenerate the file and remove our fix
|
||||
# - Running 'npx cap add android' will regenerate the file and remove our fix
|
||||
#
|
||||
# HOW TO RESTORE THE FIX:
|
||||
# - Run this build script again (it will reapply the fix automatically)
|
||||
# - Or run: ./scripts/fix-capacitor-build.sh
|
||||
# - Or manually comment out the problematic line
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
if [ -f "app/capacitor.build.gradle" ]; then
|
||||
if grep -q "^apply from: \"../capacitor-cordova-android-plugins/cordova.variables.gradle\"" "app/capacitor.build.gradle"; then
|
||||
@@ -237,6 +341,185 @@ build_android() {
|
||||
cd ..
|
||||
}
|
||||
|
||||
build_ios() {
|
||||
log_info "Building iOS..."
|
||||
|
||||
cd ios || exit 1
|
||||
|
||||
# Build configuration (define early for validation)
|
||||
SCHEME="DailyNotificationPlugin"
|
||||
CONFIG="Release"
|
||||
WORKSPACE="DailyNotificationPlugin.xcworkspace"
|
||||
|
||||
# Install CocoaPods dependencies
|
||||
log_step "Installing CocoaPods dependencies..."
|
||||
if [ ! -f "Podfile.lock" ] || [ "Podfile" -nt "Podfile.lock" ] || [ ! -d "Pods" ] || [ ! -d "Pods/Target Support Files" ]; then
|
||||
log_info "Podfile changed, Podfile.lock missing, or Pods incomplete - running pod install..."
|
||||
if ! pod install --repo-update; then
|
||||
log_error "Failed to install CocoaPods dependencies"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "Podfile.lock is up to date and Pods directory exists, skipping pod install"
|
||||
fi
|
||||
|
||||
# Quick Swift syntax validation (full validation happens during build)
|
||||
log_step "Validating Swift syntax..."
|
||||
cd Plugin
|
||||
SWIFT_FILES=$(find . -name "*.swift" -type f 2>/dev/null)
|
||||
if [ -z "$SWIFT_FILES" ]; then
|
||||
log_warn "No Swift files found in Plugin directory"
|
||||
else
|
||||
# Use swiftc with iOS SDK for basic syntax check
|
||||
IOS_SDK=$(xcrun --show-sdk-path --sdk iphoneos 2>/dev/null)
|
||||
if [ -n "$IOS_SDK" ]; then
|
||||
for swift_file in $SWIFT_FILES; do
|
||||
# Quick syntax check without full compilation
|
||||
if ! swiftc -sdk "$IOS_SDK" -target arm64-apple-ios16.0 -parse "$swift_file" 2>&1 | grep -q "error:"; then
|
||||
continue
|
||||
else
|
||||
log_warn "Syntax check found issues in $swift_file (will be caught during build)"
|
||||
# Don't exit - let xcodebuild catch real errors
|
||||
fi
|
||||
done
|
||||
else
|
||||
log_warn "Could not find iOS SDK, skipping syntax validation"
|
||||
fi
|
||||
fi
|
||||
cd ..
|
||||
|
||||
# Clean build
|
||||
log_step "Cleaning iOS build..."
|
||||
xcodebuild clean \
|
||||
-workspace "$WORKSPACE" \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration "$CONFIG" \
|
||||
-sdk iphoneos \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-quiet || {
|
||||
log_warn "Clean failed or unnecessary, continuing..."
|
||||
}
|
||||
|
||||
# Check if iOS device platform is available
|
||||
log_step "Checking iOS device platform availability..."
|
||||
BUILD_DEVICE=false
|
||||
|
||||
if xcodebuild -showsdks 2>&1 | grep -q "iOS.*iphoneos"; then
|
||||
IOS_SDK_VERSION=$(xcrun --show-sdk-version --sdk iphoneos 2>&1)
|
||||
log_info "Found iOS SDK: $IOS_SDK_VERSION"
|
||||
|
||||
# Check if platform components are installed by trying a dry-run
|
||||
DRY_RUN_OUTPUT=$(xcodebuild -workspace "$WORKSPACE" \
|
||||
-scheme "$SCHEME" \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-dry-run 2>&1)
|
||||
|
||||
if echo "$DRY_RUN_OUTPUT" | grep -q "iOS.*is not installed"; then
|
||||
log_warn "iOS device platform components not installed"
|
||||
log_info "To install iOS device platform components, run:"
|
||||
log_info " xcodebuild -downloadPlatform iOS"
|
||||
log_info "Or via Xcode: Settings > Components > iOS $IOS_SDK_VERSION"
|
||||
log_info ""
|
||||
log_info "Building for iOS Simulator instead (sufficient for plugin development)"
|
||||
else
|
||||
BUILD_DEVICE=true
|
||||
fi
|
||||
else
|
||||
log_warn "iOS SDK not found"
|
||||
fi
|
||||
|
||||
# Build for device (iOS) if available
|
||||
if [ "$BUILD_DEVICE" = true ]; then
|
||||
log_step "Building for iOS device (arm64)..."
|
||||
BUILD_OUTPUT=$(xcodebuild build \
|
||||
-workspace "$WORKSPACE" \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration "$CONFIG" \
|
||||
-sdk iphoneos \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-derivedDataPath build/derivedData \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
2>&1)
|
||||
|
||||
if echo "$BUILD_OUTPUT" | grep -q "error.*iOS.*is not installed"; then
|
||||
log_warn "iOS device build failed - platform components not installed"
|
||||
echo "$BUILD_OUTPUT" > /tmp/xcodebuild_device.log
|
||||
log_info "Check build log: /tmp/xcodebuild_device.log"
|
||||
BUILD_DEVICE=false
|
||||
elif echo "$BUILD_OUTPUT" | grep -q "BUILD SUCCEEDED"; then
|
||||
log_info "✓ iOS device build completed"
|
||||
else
|
||||
log_warn "iOS device build completed with warnings"
|
||||
echo "$BUILD_OUTPUT" > /tmp/xcodebuild_device.log
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build for simulator
|
||||
log_step "Building for iOS simulator..."
|
||||
SIMULATOR_BUILD_OUTPUT=$(xcodebuild build \
|
||||
-workspace "$WORKSPACE" \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration "$CONFIG" \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'generic/platform=iOS Simulator' \
|
||||
-derivedDataPath build/derivedData \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
2>&1)
|
||||
|
||||
if echo "$SIMULATOR_BUILD_OUTPUT" | grep -q "BUILD SUCCEEDED"; then
|
||||
log_info "✓ iOS simulator build completed successfully"
|
||||
elif echo "$SIMULATOR_BUILD_OUTPUT" | grep -q "error:"; then
|
||||
log_error "iOS simulator build failed"
|
||||
echo "$SIMULATOR_BUILD_OUTPUT" | grep -E "(error:|warning:)" | head -20
|
||||
exit 1
|
||||
else
|
||||
log_warn "iOS simulator build completed with warnings"
|
||||
echo "$SIMULATOR_BUILD_OUTPUT" | grep -E "(warning:|error:)" | head -10
|
||||
fi
|
||||
|
||||
# Find built frameworks
|
||||
DEVICE_FRAMEWORK=$(find build/derivedData -path "*/Build/Products/*-iphoneos/DailyNotificationPlugin.framework" -type d | head -1)
|
||||
SIMULATOR_FRAMEWORK=$(find build/derivedData -path "*/Build/Products/*-iphonesimulator/DailyNotificationPlugin.framework" -type d | head -1)
|
||||
|
||||
if [ -n "$DEVICE_FRAMEWORK" ]; then
|
||||
log_info "✓ Device framework: $DEVICE_FRAMEWORK"
|
||||
fi
|
||||
|
||||
if [ -n "$SIMULATOR_FRAMEWORK" ]; then
|
||||
log_info "✓ Simulator framework: $SIMULATOR_FRAMEWORK"
|
||||
fi
|
||||
|
||||
# Create universal framework (optional)
|
||||
if [ -n "$DEVICE_FRAMEWORK" ] && [ -n "$SIMULATOR_FRAMEWORK" ]; then
|
||||
log_step "Creating universal framework..."
|
||||
|
||||
UNIVERSAL_DIR="build/universal"
|
||||
mkdir -p "$UNIVERSAL_DIR"
|
||||
|
||||
# Copy device framework
|
||||
cp -R "$DEVICE_FRAMEWORK" "$UNIVERSAL_DIR/"
|
||||
|
||||
# Create universal binary
|
||||
UNIVERSAL_FRAMEWORK="$UNIVERSAL_DIR/DailyNotificationPlugin.framework/DailyNotificationPlugin"
|
||||
if lipo -create \
|
||||
"$DEVICE_FRAMEWORK/DailyNotificationPlugin" \
|
||||
"$SIMULATOR_FRAMEWORK/DailyNotificationPlugin" \
|
||||
-output "$UNIVERSAL_FRAMEWORK" 2>/dev/null; then
|
||||
log_info "✓ Universal framework: $UNIVERSAL_DIR/DailyNotificationPlugin.framework"
|
||||
else
|
||||
log_warn "Universal framework creation failed (may not be needed)"
|
||||
fi
|
||||
fi
|
||||
|
||||
cd ..
|
||||
|
||||
log_info "iOS build completed successfully!"
|
||||
}
|
||||
|
||||
# Main build process
|
||||
main() {
|
||||
log_info "Starting build process..."
|
||||
@@ -249,35 +532,53 @@ main() {
|
||||
BUILD_PLATFORM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--platform PLATFORM]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --platform PLATFORM Build platform: 'android', 'ios', or 'all' (default: all)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 --platform android # Build Android only"
|
||||
echo " $0 --platform ios # Build iOS only"
|
||||
echo " $0 --platform all # Build both platforms"
|
||||
echo " $0 # Build both platforms (default)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
log_error "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check environment
|
||||
check_environment
|
||||
|
||||
|
||||
# Check environment (platform-specific)
|
||||
check_environment "$BUILD_PLATFORM"
|
||||
|
||||
# Build TypeScript
|
||||
build_typescript
|
||||
|
||||
|
||||
# Build based on platform
|
||||
case $BUILD_PLATFORM in
|
||||
"android")
|
||||
build_android
|
||||
;;
|
||||
"ios")
|
||||
build_ios
|
||||
;;
|
||||
"all")
|
||||
build_android
|
||||
build_ios
|
||||
;;
|
||||
*)
|
||||
log_error "Invalid platform: $BUILD_PLATFORM. Use 'android' or 'all'"
|
||||
log_error "Invalid platform: $BUILD_PLATFORM. Use 'android', 'ios', or 'all'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
log_info "Build completed successfully!"
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
main "$@"
|
||||
250
scripts/setup-ruby.sh
Executable file
250
scripts/setup-ruby.sh
Executable file
@@ -0,0 +1,250 @@
|
||||
#!/bin/bash
|
||||
# Ruby Version Manager (rbenv) Setup Script
|
||||
# Installs rbenv and Ruby 3.1+ for CocoaPods compatibility
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if rbenv is already installed
|
||||
if command -v rbenv &> /dev/null; then
|
||||
log_info "rbenv is already installed"
|
||||
RBENV_INSTALLED=true
|
||||
else
|
||||
log_step "Installing rbenv..."
|
||||
RBENV_INSTALLED=false
|
||||
fi
|
||||
|
||||
# Install rbenv via Homebrew (recommended on macOS)
|
||||
if [ "$RBENV_INSTALLED" = false ]; then
|
||||
if command -v brew &> /dev/null; then
|
||||
log_info "Installing rbenv via Homebrew..."
|
||||
brew install rbenv ruby-build
|
||||
|
||||
# Initialize rbenv in shell
|
||||
if [ -n "$ZSH_VERSION" ]; then
|
||||
SHELL_CONFIG="$HOME/.zshrc"
|
||||
else
|
||||
SHELL_CONFIG="$HOME/.bash_profile"
|
||||
fi
|
||||
|
||||
# Add rbenv initialization to shell config
|
||||
if ! grep -q "rbenv init" "$SHELL_CONFIG" 2>/dev/null; then
|
||||
log_info "Adding rbenv initialization to $SHELL_CONFIG..."
|
||||
echo '' >> "$SHELL_CONFIG"
|
||||
echo '# rbenv initialization' >> "$SHELL_CONFIG"
|
||||
echo 'eval "$(rbenv init - zsh)"' >> "$SHELL_CONFIG"
|
||||
fi
|
||||
|
||||
# Load rbenv in current session
|
||||
eval "$(rbenv init - zsh)"
|
||||
|
||||
log_info "✓ rbenv installed successfully"
|
||||
else
|
||||
log_warn "Homebrew not found. Installing rbenv manually..."
|
||||
|
||||
# Manual installation via git
|
||||
if [ ! -d "$HOME/.rbenv" ]; then
|
||||
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
|
||||
fi
|
||||
|
||||
if [ ! -d "$HOME/.rbenv/plugins/ruby-build" ]; then
|
||||
mkdir -p ~/.rbenv/plugins
|
||||
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
|
||||
fi
|
||||
|
||||
# Add to PATH
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init - zsh)"
|
||||
|
||||
# Add to shell config
|
||||
if [ -n "$ZSH_VERSION" ]; then
|
||||
SHELL_CONFIG="$HOME/.zshrc"
|
||||
else
|
||||
SHELL_CONFIG="$HOME/.bash_profile"
|
||||
fi
|
||||
|
||||
if ! grep -q "rbenv init" "$SHELL_CONFIG" 2>/dev/null; then
|
||||
echo '' >> "$SHELL_CONFIG"
|
||||
echo '# rbenv initialization' >> "$SHELL_CONFIG"
|
||||
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> "$SHELL_CONFIG"
|
||||
echo 'eval "$(rbenv init - zsh)"' >> "$SHELL_CONFIG"
|
||||
fi
|
||||
|
||||
log_info "✓ rbenv installed manually"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Reload shell config
|
||||
log_step "Reloading shell configuration..."
|
||||
if [ -n "$ZSH_VERSION" ]; then
|
||||
source ~/.zshrc 2>/dev/null || true
|
||||
else
|
||||
source ~/.bash_profile 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Ensure rbenv is in PATH
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init - zsh)" 2>/dev/null || eval "$(rbenv init - bash)" 2>/dev/null || true
|
||||
|
||||
# Check current Ruby version
|
||||
log_step "Checking current Ruby version..."
|
||||
CURRENT_RUBY=$(ruby -v 2>/dev/null | cut -d' ' -f2 | cut -d. -f1,2) || CURRENT_RUBY="unknown"
|
||||
|
||||
if [ "$CURRENT_RUBY" != "unknown" ]; then
|
||||
RUBY_MAJOR=$(echo "$CURRENT_RUBY" | cut -d. -f1)
|
||||
RUBY_MINOR=$(echo "$CURRENT_RUBY" | cut -d. -f2)
|
||||
|
||||
if [ "$RUBY_MAJOR" -ge 3 ] && [ "$RUBY_MINOR" -ge 1 ]; then
|
||||
log_info "✓ Ruby version $CURRENT_RUBY is already >= 3.1.0"
|
||||
log_info "You can proceed with CocoaPods installation"
|
||||
exit 0
|
||||
else
|
||||
log_warn "Current Ruby version: $CURRENT_RUBY (needs 3.1.0+)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if rbenv has a suitable Ruby version already installed
|
||||
log_step "Checking installed Ruby versions..."
|
||||
if rbenv versions | grep -qE "3\.(1|2|3|4)\."; then
|
||||
INSTALLED_RUBY=$(rbenv versions | grep -E "3\.(1|2|3|4)\." | tail -1 | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
|
||||
log_info "Found installed Ruby version: $INSTALLED_RUBY"
|
||||
|
||||
# Set as global if not already set
|
||||
CURRENT_GLOBAL=$(rbenv global)
|
||||
if [ "$CURRENT_GLOBAL" != "$INSTALLED_RUBY" ]; then
|
||||
log_info "Setting $INSTALLED_RUBY as default..."
|
||||
rbenv global "$INSTALLED_RUBY"
|
||||
rbenv rehash
|
||||
fi
|
||||
|
||||
# Verify it works
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init - zsh)" 2>/dev/null || eval "$(rbenv init - bash)" 2>/dev/null || true
|
||||
|
||||
if ruby -rpsych -e "true" 2>/dev/null; then
|
||||
VERIFIED_RUBY=$(ruby -v)
|
||||
log_info "✓ Ruby $VERIFIED_RUBY is working correctly"
|
||||
log_info ""
|
||||
log_info "Ruby setup complete!"
|
||||
log_info ""
|
||||
log_info "Next steps:"
|
||||
log_info " 1. Reload your shell: source ~/.zshrc"
|
||||
log_info " 2. Verify Ruby: ruby -v"
|
||||
log_info " 3. Install CocoaPods: gem install cocoapods"
|
||||
exit 0
|
||||
else
|
||||
log_warn "Installed Ruby $INSTALLED_RUBY found but psych extension not working"
|
||||
log_warn "May need to reinstall Ruby or install libyaml dependencies"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for libyaml dependency (required for psych extension)
|
||||
log_step "Checking for libyaml dependency..."
|
||||
LIBYAML_FOUND=false
|
||||
if command -v brew &> /dev/null; then
|
||||
if brew list libyaml &> /dev/null; then
|
||||
LIBYAML_FOUND=true
|
||||
log_info "✓ libyaml found via Homebrew"
|
||||
else
|
||||
log_warn "libyaml not installed. Installing via Homebrew..."
|
||||
if brew install libyaml; then
|
||||
LIBYAML_FOUND=true
|
||||
log_info "✓ libyaml installed successfully"
|
||||
else
|
||||
log_error "Failed to install libyaml via Homebrew"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Check if libyaml headers exist in system locations
|
||||
if find /usr/local /opt /Library -name "yaml.h" 2>/dev/null | grep -q yaml.h; then
|
||||
LIBYAML_FOUND=true
|
||||
log_info "✓ libyaml headers found in system"
|
||||
else
|
||||
log_warn "libyaml not found. Ruby installation may fail."
|
||||
log_warn "Install libyaml via Homebrew: brew install libyaml"
|
||||
log_warn "Or install via MacPorts: sudo port install libyaml"
|
||||
fi
|
||||
fi
|
||||
|
||||
# List available Ruby versions
|
||||
log_step "Fetching available Ruby versions..."
|
||||
rbenv install --list | grep -E "^[[:space:]]*3\.[1-9]" | tail -5 || log_warn "Could not fetch Ruby versions list"
|
||||
|
||||
# Install Ruby 3.1.0 (preferred for compatibility)
|
||||
log_step "Installing Ruby 3.1.0..."
|
||||
if rbenv install 3.1.0; then
|
||||
log_info "✓ Ruby 3.1.0 installed successfully"
|
||||
|
||||
# Set as global default
|
||||
log_step "Setting Ruby 3.1.0 as default..."
|
||||
rbenv global 3.1.0
|
||||
|
||||
# Verify installation
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init - zsh)" 2>/dev/null || eval "$(rbenv init - bash)" 2>/dev/null || true
|
||||
|
||||
NEW_RUBY=$(ruby -v)
|
||||
log_info "✓ Current Ruby version: $NEW_RUBY"
|
||||
|
||||
# Verify psych extension works
|
||||
if ruby -rpsych -e "true" 2>/dev/null; then
|
||||
log_info "✓ psych extension verified"
|
||||
else
|
||||
log_warn "psych extension may not be working correctly"
|
||||
log_warn "This may affect CocoaPods installation"
|
||||
fi
|
||||
|
||||
# Rehash to make Ruby available
|
||||
rbenv rehash
|
||||
|
||||
log_info ""
|
||||
log_info "Ruby setup complete!"
|
||||
log_info ""
|
||||
log_info "Next steps:"
|
||||
log_info " 1. Reload your shell: source ~/.zshrc"
|
||||
log_info " 2. Verify Ruby: ruby -v"
|
||||
log_info " 3. Install CocoaPods: gem install cocoapods"
|
||||
|
||||
else
|
||||
log_error "Failed to install Ruby 3.1.0"
|
||||
|
||||
if [ "$LIBYAML_FOUND" = false ]; then
|
||||
log_error ""
|
||||
log_error "Installation failed. This is likely due to missing libyaml dependency."
|
||||
log_error ""
|
||||
log_error "To fix:"
|
||||
if command -v brew &> /dev/null; then
|
||||
log_error " brew install libyaml"
|
||||
else
|
||||
log_error " Install Homebrew: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
||||
log_error " Then: brew install libyaml"
|
||||
fi
|
||||
log_error ""
|
||||
log_error "After installing libyaml, run this script again."
|
||||
else
|
||||
log_error "Installation failed. Please check your internet connection and try again."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,183 @@
|
||||
# iOS Build Process Quick Reference
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: November 4, 2025
|
||||
|
||||
## Two Different Test Apps
|
||||
|
||||
**Important**: There are two different iOS test apps:
|
||||
|
||||
1. **Native iOS Development App** (`ios/App`) - Simple Capacitor app for quick plugin testing
|
||||
2. **Vue 3 Test App** (`test-apps/daily-notification-test`) - Full-featured Vue 3 Capacitor app
|
||||
|
||||
---
|
||||
|
||||
## Vue 3 Test App (`test-apps/daily-notification-test`)
|
||||
|
||||
### 🚨 Critical Build Steps
|
||||
|
||||
```bash
|
||||
# 1. Build web assets
|
||||
npm run build
|
||||
|
||||
# 2. Sync with iOS project
|
||||
npx cap sync ios
|
||||
|
||||
# 3. Build iOS app
|
||||
cd ios/App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
# 4. Install and launch
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d | head -1)
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
```
|
||||
|
||||
### 🔄 Using Capacitor CLI (Simplest Method)
|
||||
|
||||
```bash
|
||||
cd test-apps/daily-notification-test
|
||||
|
||||
# Build and run in one command
|
||||
npx cap run ios
|
||||
|
||||
# This handles:
|
||||
# - Building web assets
|
||||
# - Syncing with iOS
|
||||
# - Building app
|
||||
# - Installing on simulator
|
||||
# - Launching app
|
||||
```
|
||||
|
||||
### 🛠️ Automated Build Script
|
||||
|
||||
```bash
|
||||
cd test-apps/daily-notification-test
|
||||
./scripts/build-and-deploy-ios.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Native iOS Development App (`ios/App`)
|
||||
|
||||
### 🚨 Critical Build Steps
|
||||
|
||||
```bash
|
||||
# 1. Build plugin
|
||||
cd /path/to/daily-notification-plugin
|
||||
./scripts/build-native.sh --platform ios
|
||||
|
||||
# 2. Install CocoaPods dependencies
|
||||
cd ios
|
||||
pod install
|
||||
|
||||
# 3. Build iOS app
|
||||
cd App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
# 4. Install and launch
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d | head -1)
|
||||
xcrun simctl install booted "$APP_PATH"
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist)
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
```
|
||||
|
||||
### 🛠️ Automated Build Script
|
||||
|
||||
```bash
|
||||
cd /path/to/daily-notification-plugin
|
||||
./scripts/build-and-deploy-native-ios.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ iOS-Specific Requirements
|
||||
|
||||
**Prerequisites:**
|
||||
- macOS (required for iOS development)
|
||||
- Xcode installed (`xcode-select --install`)
|
||||
- CocoaPods installed (`gem install cocoapods`)
|
||||
- iOS Simulator runtime installed
|
||||
|
||||
**Common Issues:**
|
||||
- Simulator not booted: `xcrun simctl boot "iPhone 15 Pro"`
|
||||
- CocoaPods not installed: `sudo gem install cocoapods`
|
||||
- Platform components missing: `xcodebuild -downloadPlatform iOS`
|
||||
|
||||
## 🔍 Verification Checklist
|
||||
|
||||
After build, verify:
|
||||
|
||||
### For Vue 3 Test App:
|
||||
- [ ] Simulator is booted (`xcrun simctl list devices | grep Booted`)
|
||||
- [ ] CocoaPods dependencies installed (`cd ios && pod install`)
|
||||
- [ ] Web assets synced (`npx cap sync ios`)
|
||||
- [ ] App builds successfully (`xcodebuild ...`)
|
||||
- [ ] App installs on simulator (`xcrun simctl install`)
|
||||
- [ ] App launches (`xcrun simctl launch`)
|
||||
|
||||
### For Native iOS App:
|
||||
- [ ] Simulator is booted (`xcrun simctl list devices | grep Booted`)
|
||||
- [ ] Plugin built (`./scripts/build-native.sh --platform ios`)
|
||||
- [ ] CocoaPods dependencies installed (`cd ios && pod install`)
|
||||
- [ ] App builds successfully (`xcodebuild ...`)
|
||||
- [ ] App installs on simulator (`xcrun simctl install`)
|
||||
- [ ] App launches (`xcrun simctl launch`)
|
||||
|
||||
## 📱 Testing Commands
|
||||
|
||||
```bash
|
||||
# List available simulators
|
||||
xcrun simctl list devices available
|
||||
|
||||
# Boot simulator
|
||||
xcrun simctl boot "iPhone 15 Pro"
|
||||
|
||||
# Check if booted
|
||||
xcrun simctl list devices | grep Booted
|
||||
|
||||
# View logs
|
||||
xcrun simctl spawn booted log stream
|
||||
|
||||
# Uninstall app
|
||||
xcrun simctl uninstall booted com.timesafari.dailynotification.test # Vue 3 app
|
||||
xcrun simctl uninstall booted com.timesafari.dailynotification # Native app
|
||||
|
||||
# Reset simulator
|
||||
xcrun simctl erase booted
|
||||
```
|
||||
|
||||
## 🐛 Common Issues
|
||||
|
||||
| Issue | Symptom | Solution |
|
||||
|-------|---------|----------|
|
||||
| Simulator not found | `Unable to find destination` | Run `xcrun simctl list devices` to see available devices |
|
||||
| CocoaPods error | `pod: command not found` | Install CocoaPods: `gem install cocoapods` |
|
||||
| Build fails | `No such file or directory` | Run `pod install` in `ios/` directory |
|
||||
| Signing error | `Code signing required` | Add `CODE_SIGNING_REQUIRED=NO` to xcodebuild command |
|
||||
| App won't install | `Could not find application` | Verify app path exists and simulator is booted |
|
||||
| Vue app: assets not syncing | App shows blank screen | Run `npm run build && npx cap sync ios` |
|
||||
|
||||
---
|
||||
|
||||
**Remember**:
|
||||
- **Native iOS App** (`ios/App`) = Quick plugin testing, no web build needed
|
||||
- **Vue 3 Test App** (`test-apps/...`) = Full testing with UI, requires `npm run build`
|
||||
|
||||
Use `npx cap run ios` in the Vue 3 test app directory for the simplest workflow!
|
||||
|
||||
150
test-apps/daily-notification-test/scripts/build-and-deploy-ios.sh
Executable file
150
test-apps/daily-notification-test/scripts/build-and-deploy-ios.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
# iOS Test App Build and Deploy Script
|
||||
# Builds and deploys the DailyNotification test app to iOS Simulator
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if we're in the test app directory
|
||||
if [ ! -f "package.json" ] || [ ! -d "ios" ]; then
|
||||
log_error "This script must be run from test-apps/daily-notification-test directory"
|
||||
log_info "Usage: cd test-apps/daily-notification-test && ./scripts/build-and-deploy-ios.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check prerequisites
|
||||
log_step "Checking prerequisites..."
|
||||
|
||||
if ! command -v xcodebuild &> /dev/null; then
|
||||
log_error "xcodebuild not found. Install Xcode command line tools:"
|
||||
log_info " xcode-select --install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pod &> /dev/null; then
|
||||
log_error "CocoaPods not found. Install with:"
|
||||
log_info " gem install cocoapods"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get simulator device (default to iPhone 15 Pro)
|
||||
SIMULATOR_DEVICE="${1:-iPhone 15 Pro}"
|
||||
log_info "Using simulator: $SIMULATOR_DEVICE"
|
||||
|
||||
# Boot simulator
|
||||
log_step "Booting simulator..."
|
||||
if xcrun simctl list devices | grep -q "$SIMULATOR_DEVICE.*Booted"; then
|
||||
log_info "Simulator already booted"
|
||||
else
|
||||
# Try to boot the device
|
||||
if xcrun simctl boot "$SIMULATOR_DEVICE" 2>/dev/null; then
|
||||
log_info "✓ Simulator booted"
|
||||
else
|
||||
log_warn "Could not boot simulator automatically"
|
||||
log_info "Opening Simulator app... (you may need to select device manually)"
|
||||
open -a Simulator
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build web assets
|
||||
log_step "Building web assets..."
|
||||
npm run build
|
||||
|
||||
# Sync with iOS
|
||||
log_step "Syncing with iOS project..."
|
||||
if ! npx cap sync ios; then
|
||||
log_error "Failed to sync with iOS project"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install CocoaPods dependencies
|
||||
log_step "Installing CocoaPods dependencies..."
|
||||
cd ios/App
|
||||
if [ ! -f "Podfile.lock" ] || [ "Podfile" -nt "Podfile.lock" ]; then
|
||||
pod install
|
||||
else
|
||||
log_info "CocoaPods dependencies up to date"
|
||||
fi
|
||||
|
||||
# Build iOS app
|
||||
log_step "Building iOS app..."
|
||||
WORKSPACE="App.xcworkspace"
|
||||
SCHEME="App"
|
||||
CONFIG="Debug"
|
||||
SDK="iphonesimulator"
|
||||
|
||||
xcodebuild -workspace "$WORKSPACE" \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration "$CONFIG" \
|
||||
-sdk "$SDK" \
|
||||
-destination "platform=iOS Simulator,name=$SIMULATOR_DEVICE" \
|
||||
-derivedDataPath build/derivedData \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
clean build
|
||||
|
||||
# Find built app
|
||||
APP_PATH=$(find build/derivedData -name "*.app" -type d -path "*/Build/Products/*-iphonesimulator/*.app" | head -1)
|
||||
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
log_error "Could not find built app"
|
||||
log_info "Searching in: build/derivedData"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Found app: $APP_PATH"
|
||||
|
||||
# Install app on simulator
|
||||
log_step "Installing app on simulator..."
|
||||
if xcrun simctl install booted "$APP_PATH"; then
|
||||
log_info "✓ App installed"
|
||||
else
|
||||
log_error "Failed to install app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get bundle identifier
|
||||
BUNDLE_ID=$(plutil -extract CFBundleIdentifier raw App/Info.plist 2>/dev/null || echo "com.timesafari.dailynotification.test")
|
||||
log_info "Bundle ID: $BUNDLE_ID"
|
||||
|
||||
# Launch app
|
||||
log_step "Launching app..."
|
||||
if xcrun simctl launch booted "$BUNDLE_ID"; then
|
||||
log_info "✓ App launched"
|
||||
else
|
||||
log_warn "App may already be running"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "✅ Build and deploy complete!"
|
||||
log_info ""
|
||||
log_info "To view logs:"
|
||||
log_info " xcrun simctl spawn booted log stream"
|
||||
log_info ""
|
||||
log_info "To uninstall app:"
|
||||
log_info " xcrun simctl uninstall booted $BUNDLE_ID"
|
||||
|
||||
Reference in New Issue
Block a user