# Building TimeSafari This guide explains how to build TimeSafari for different platforms using our unified build scripts. ## Prerequisites For a quick dev environment setup, use [pkgx](https://pkgx.dev). - Node.js (LTS version recommended) - npm (comes with Node.js) - Git - For mobile builds: Android Studio (Android) or Xcode (iOS) - For desktop builds: Capacitor Electron platform ## Unified Build Scripts TimeSafari now uses unified build scripts that automatically handle environment variables, logging, error handling, and timing. All scripts are located in the `scripts/` directory and use a common utilities library. ### Script Features - **Automatic Environment Setup**: Each script sets the correct environment variables for its build type - **Rich Logging**: Colored, timestamped output with different log levels - **Error Handling**: Proper exit codes and graceful failure recovery - **Timing**: Automatic execution time tracking for each step - **Validation**: Checks for required dependencies and files - **CLI Options**: `--help`, `--verbose`, `--env` flags for all scripts ### Available Scripts | Script | Purpose | Command | |--------|---------|---------| | `capacitor-dev.sh` | Capacitor development | `./scripts/capacitor-dev.sh` | | `capacitor-build.sh` | Capacitor build | `./scripts/build-capacitor.sh` | | `web-dev.sh` | Web development | `./scripts/web-dev.sh` | | `web-build.sh` | Web build | `./scripts/build-web.sh` | ### Environment Variables All scripts automatically set the correct environment variables for their build type: | Build Type | VITE_PLATFORM | VITE_PWA_ENABLED | VITE_DISABLE_PWA | NODE_ENV | |------------|---------------|------------------|------------------|----------| | `capacitor` | capacitor | false | true | - | | `web` | web | true | false | - | ### CLI Options All scripts support these options: ```bash # Show help ./scripts/build-capacitor.sh --help # Enable verbose logging ./scripts/build-capacitor.sh --verbose # Show environment variables ./scripts/build-capacitor.sh --env ``` ## Forks If you have forked this to make your own app, you'll want to customize the iOS & Android files. You can either edit existing ones, or you can remove the `ios` and `android` directories and regenerate them before the `npx cap sync` step in each setup. ```bash npx cap add android npx cap add ios ``` You'll also want to edit the deep link configuration (see below). ## Initial Setup Install dependencies: ```bash npm install ``` ## Package Management TimeSafari uses a mixed package management approach, combining npm and JSR (JavaScript Registry) for optimal dependency management. ### JSR Integration Some packages are installed via JSR for better ESM support and modern TypeScript compatibility: ```bash # Install JSR packages npx jsr add @nostr/tools ``` ### Package Migration History #### nostr-tools → @nostr/tools **Date**: June 2025 **Reason**: Resolved Vite/Rollup build issues with deep imports **Before** (npm): ```typescript import { finalizeEvent } from "nostr-tools/lib/cjs/index.js"; import { accountFromExtendedKey } from "nostr-tools/lib/cjs/nip06.js"; ``` **After** (JSR): ```typescript import { finalizeEvent } from "@nostr/tools"; import { accountFromExtendedKey } from "@nostr/tools/nip06"; ``` **Benefits**: - ✅ Proper ESM support - ✅ No deep import issues with Vite/Rollup - ✅ Better TypeScript compatibility - ✅ Modern package structure ### Current Package Strategy - **npm**: Primary package manager for most dependencies - **JSR**: Used for packages with better ESM support or modern alternatives - **Mixed approach**: Allows using the best package for each dependency ### When to Use JSR Consider using JSR for: - Packages with ESM/CJS compatibility issues in npm - Modern TypeScript-first packages - Packages that work better with modern bundlers - New dependencies where JSR has a better alternative ### Vite Configuration The build system is configured to handle both npm and JSR packages: ```typescript // vite.config.common.mts resolve: { alias: { '@nostr/tools': path.resolve(__dirname, 'node_modules/@nostr/tools'), '@nostr/tools/nip06': path.resolve(__dirname, 'node_modules/@nostr/tools/nip06'), } } ``` ### Troubleshooting Package Issues 1. **Build failures with deep imports** - Check if package has ESM/CJS compatibility issues - Consider JSR alternative if available - Update Vite configuration if needed 2. **TypeScript errors** - Ensure proper type definitions are available - Check package exports in package.json - Verify import paths match package structure 3. **Mixed package manager issues** - Keep package.json and node_modules in sync - Use `npm install` after JSR package additions - Check for conflicting package versions ## Web Development ### Local Development ```bash npm run dev ``` ### Web Build for Server 1. Run the production build: ```bash npm run build:web ``` The built files will be in the `dist` directory. 2. To test the production build locally: ```bash npm run serve ``` ### Environment Configuration For different environments, create `.env` files: ```bash # .env.development VITE_APP_SERVER=https://dev.timesafari.app VITE_DEFAULT_ENDORSER_API_SERVER=https://dev-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://dev-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://dev-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://dev.timesafari.app VITE_PASSKEYS_ENABLED=true # .env.production VITE_APP_SERVER=https://timesafari.app VITE_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://timesafari.app VITE_PASSKEYS_ENABLED=true ``` ## Desktop Build (Capacitor Electron) ### Prerequisites 1. Install Capacitor CLI: ```bash npm install -g @capacitor/cli ``` 2. Add Electron platform: ```bash npx cap add electron ``` ### Development For development with automatic environment setup: ```bash # Build web assets npm run build:capacitor # Sync with Capacitor npx cap sync electron # Open in Electron npx cap open electron ``` ### Production Build For production builds: ```bash # Build web assets npm run build:capacitor # Sync with Capacitor npx cap sync electron # Build Electron app npx cap build electron ``` ### Packaging Capacitor Electron uses electron-builder for packaging. Configure the build in `capacitor.config.json`: ```json { "plugins": { "ElectronBuilder": { "buildOptions": { "appId": "app.timesafari.app", "productName": "TimeSafari", "directories": { "output": "dist-electron-packages" }, "files": [ "dist/**/*", "electron/**/*" ], "linux": { "target": ["AppImage", "deb"], "category": "Office" }, "mac": { "target": ["dmg", "zip"], "category": "public.app-category.productivity" }, "win": { "target": ["nsis", "portable"] } } } } } ``` ### Running the Packaged App - **Linux**: AppImage files are self-contained executables - **macOS**: `.app` bundles can be dragged to Applications folder - **Windows**: `.exe` installers or portable executables ## Mobile Builds (Capacitor) ### Android Build Prerequisites: Android Studio with Java SDK installed #### Complete Build Process Use the unified Android build script: ```bash ./scripts/build-android.sh ``` This script automatically: 1. Sets up environment variables for Capacitor 2. Cleans previous builds 3. Builds web assets 4. Builds Capacitor version 5. Cleans and builds Gradle project 6. Syncs with Capacitor 7. Generates assets 8. Opens Android Studio #### Manual Steps (if needed) If you need to run individual steps: 1. Build the web assets: ```bash npm run build:web npm run build:capacitor ``` 2. Update Android project: ```bash npx cap sync android ``` 3. Generate assets: ```bash npx capacitor-assets generate --android ``` 4. Open in Android Studio: ```bash npx cap open android ``` #### Console Build For building from the console: ```bash cd android ./gradlew clean ./gradlew build -Dlint.baselines.continue=true cd - ``` For creating an `aab` file: ```bash cd android ./gradlew bundleDebug -Dlint.baselines.continue=true cd - ``` For creating a signed release: 1. Setup signing configuration in `app/gradle.properties.secrets` 2. Add signing key file `app/time-safari-upload-key-pkcs12.jks` 3. Update version in `app/build.gradle` 4. Build release: ```bash cd android ./gradlew bundleRelease -Dlint.baselines.continue=true cd - ``` The `aab` file will be at `app/build/outputs/bundle/release`. ### iOS Build Prerequisites: macOS with Xcode installed #### First-time iOS Configuration - Generate certificates inside Xcode - Right-click on App and under Signing & Capabilities set the Team #### Build Process 1. Build the web assets & update iOS: ```bash npm run build:web npm run build:capacitor npx cap sync ios ``` 2. Generate assets: ```bash # Create required directories mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset echo '{"images":[]}' > ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json mkdir -p ios/App/App/Assets.xcassets/Splash.imageset echo '{"images":[]}' > ios/App/App/Assets.xcassets/Splash.imageset/Contents.json # Generate assets npx capacitor-assets generate --ios ``` 3. Update version to match Android & package.json: ```bash cd ios/App xcrun agvtool new-version 35 perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.2;/g" App.xcodeproj/project.pbxproj cd - ``` 4. Open in Xcode: ```bash npx cap open ios ``` 5. Build and run on simulator or device using Xcode #### Release Process 1. Choose Product -> Destination -> Any iOS Device 2. Choose Product -> Archive 3. Click Distribute -> App Store Connect 4. In App Store Connect, add the build to the distribution ## Testing ### Complete Test Suite Run all tests with automatic environment setup: ```bash ./scripts/test-all.sh ``` ### Mobile Tests Run mobile-specific tests: ```bash ./scripts/test-mobile.sh ``` ### Environment Testing Test environment variable handling: ```bash ./scripts/test-env.sh ``` ## Docker Deployment The application can be containerized using Docker for consistent deployment across environments. ### Prerequisites - Docker installed on your system - Docker Compose (optional, for multi-container setups) ### Building the Docker Image 1. Build the Docker image: ```bash docker build -t timesafari:latest . ``` 2. For development builds with specific environment variables: ```bash docker build --build-arg NODE_ENV=development -t timesafari:dev . ``` ### Running the Container 1. Run the container: ```bash docker run -d -p 80:80 timesafari:latest ``` 2. For development with hot-reloading: ```bash docker run -d -p 80:80 -v $(pwd):/app timesafari:dev ``` ### Using Docker Compose Create a `docker-compose.yml` file: ```yaml version: '3.8' services: timesafari: build: . ports: - "80:80" environment: - NODE_ENV=production restart: unless-stopped ``` Run with Docker Compose: ```bash docker-compose up -d ``` ### Production Deployment For production deployment, consider the following: 1. Use specific version tags instead of 'latest' 2. Implement health checks 3. Configure proper logging 4. Set up reverse proxy with SSL termination 5. Use Docker secrets for sensitive data Example production deployment: ```bash # Build with specific version docker build -t timesafari:1.0.0 . # Run with production settings docker run -d \ --name timesafari \ -p 80:80 \ --restart unless-stopped \ -e NODE_ENV=production \ timesafari:1.0.0 ``` ### Troubleshooting Docker 1. **Container fails to start** - Check logs: `docker logs ` - Verify port availability - Check environment variables 2. **Build fails** - Ensure all dependencies are in package.json - Check Dockerfile syntax - Verify build context 3. **Performance issues** - Monitor container resources: `docker stats` - Check nginx configuration - Verify caching settings ## Configuration ### Deep Links #### Android Configuration You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file: ```xml ``` Note: When using `timesafari://` scheme, you may encounter build errors about missing http(s) scheme and host attributes. This is expected for custom URL schemes. #### iOS Configuration For iOS deep links, configure the URL scheme in Xcode: 1. Open the project in Xcode 2. Select your app target 3. Go to Info tab 4. Add URL Types with scheme `timesafari` ## Troubleshooting ### Common Issues 1. **Environment Variables Not Set** - Use `--env` flag to check current environment: `./scripts/build-capacitor.sh --env` - Verify `.env` file exists and is properly formatted - Check script output for environment setup messages 2. **Build Failures** - Use `--verbose` flag for detailed logging: `./scripts/build-capacitor.sh --verbose` - Check prerequisites are installed - Verify all dependencies are installed: `npm install` 3. **Permission Issues** - Make scripts executable: `chmod +x scripts/*.sh` - Check file permissions on build directories 4. **Platform-Specific Issues** - **Android**: Verify Android Studio and SDK are properly configured - **iOS**: Ensure Xcode and certificates are set up correctly - **Electron**: Check Capacitor Electron platform installation ### Getting Help - Check script help: `./scripts/build-capacitor.sh --help` - Review script documentation in `scripts/README.md` - Test environment setup: `./scripts/test-env.sh` - Test common utilities: `./scripts/test-common.sh` ### Platform Support Matrix | Platform | Mode | PWA Enabled | Native Features | Notes | |----------|------|-------------|-----------------|-------| | `web` | web | true | false | Standard web browser | | `capacitor` | capacitor | false | true | Mobile app (iOS/Android) | | `electron` | capacitor | false | true | Desktop app (via Capacitor Electron) | ## Platform Service Architecture TimeSafari uses a unified platform service architecture that works across all platforms: ### Platform Detection The `CapacitorPlatformService` automatically detects the platform and adjusts capabilities: ```typescript getCapabilities(): PlatformCapabilities { const platform = Capacitor.getPlatform(); const isElectron = platform === "electron"; return { hasFileSystem: true, hasCamera: true, isMobile: !isElectron, // false for Electron, true for mobile isIOS: platform === "ios", hasFileDownload: isElectron, // Electron can download files directly needsFileHandlingInstructions: !isElectron, // Mobile needs instructions isNativeApp: true, }; } ``` ### Unified Database Layer All platforms use the same SQLite database through Capacitor plugins: - **Mobile**: `@capacitor-community/sqlite` plugin - **Desktop**: Same plugin via Capacitor Electron - **Web**: IndexedDB fallback with absurd-sql ### Feature Parity The same Capacitor plugins work across all platforms: - File system operations - Camera access - SQLite database - Deep linking - Sharing functionality