# Building TimeSafari

This guide explains how to build TimeSafari for different platforms.

## Prerequisites

For a quick dev environment setup, use [pkgx](https://pkgx.dev).

- Node.js (LTS version recommended)
- npm (comes with Node.js)
- Git
- For Android builds: Android Studio with SDK installed
- For iOS builds: macOS with Xcode and ruby gems & bundle
  - `pkgx +rubygems.org sh`

  - ... and you may have to fix these, especially with pkgx

   ```bash
   gem_path=$(which gem)
   shortened_path="${gem_path:h:h}"
   export GEM_HOME=$shortened_path
   export GEM_PATH=$shortened_path
   ```

- For desktop builds: Additional build tools based on your OS

## 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
   ```

## Web Dev Locally

   ```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:

   You'll likely want to use test locations for the Endorser & image & partner servers; see "DEFAULT_ENDORSER_API_SERVER" & "DEFAULT_IMAGE_API_SERVER" & "DEFAULT_PARTNER_API_SERVER" below.

   ```bash
   npm run serve
   ```

### Compile and minify for test & production

* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.

* `npx prettier --write ./sw_scripts/`

* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.

* Commit everything (since the commit hash is used the app).

* Put the commit hash in the changelog (which will help you remember to bump the version later).

* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.

* For test, build the app (because test server is not yet set up to build):

```bash
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build
```

   ... and transfer to the test server:

   ```bash
   rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
   ```

(Let's replace that with a .env.development or .env.staging file.)

(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)

* For prod, get on the server and run the correct build:

  ... and log onto the server:

  * `pkgx +npm sh`

  * `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 0.3.55 && npm install && npm run build && cd -`

  (The plain `npm run build` uses the .env.production file.)

* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/`

* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.

## 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 <container_id>`
   - 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

## Desktop Build (Electron)

### Linux Build

1. Build the electron app in production mode:

   ```bash
   npm run build:electron-prod
   ```

2. Package the Electron app for Linux:

   ```bash
   # For AppImage (recommended)
   npm run electron:build-linux

   # For .deb package
   npm run electron:build-linux-deb
   ```

3. The packaged applications will be in `dist-electron-packages/`:
   - AppImage: `dist-electron-packages/TimeSafari-x.x.x.AppImage`
   - DEB: `dist-electron-packages/timesafari_x.x.x_amd64.deb`

### macOS Build

1. Build the electron app in production mode:

   ```bash
   npm run build:web
   npm run build:electron
   npm run electron:build-mac
   ```

2. Package the Electron app for macOS:

   ```bash
   # For Intel Macs
   npm run electron:build-mac

   # For Universal build (Intel + Apple Silicon)
   npm run electron:build-mac-universal
   ```

3. The packaged applications will be in `dist-electron-packages/`:
   - `.app` bundle: `TimeSafari.app`
   - `.dmg` installer: `TimeSafari-x.x.x.dmg`
   - `.zip` archive: `TimeSafari-x.x.x-mac.zip`

### Code Signing and Notarization (macOS)

For public distribution on macOS, you need to code sign and notarize your app:

1. Set up environment variables:
   ```bash
   export CSC_LINK=/path/to/your/certificate.p12
   export CSC_KEY_PASSWORD=your_certificate_password
   export APPLE_ID=your_apple_id
   export APPLE_ID_PASSWORD=your_app_specific_password
   ```

2. Build with signing:
   ```bash
   npm run electron:build-mac
   ```

### Running the Packaged App

- **Linux**:
  - AppImage: Make executable and run
    ```bash
    chmod +x dist-electron-packages/TimeSafari-*.AppImage
    ./dist-electron-packages/TimeSafari-*.AppImage
    ```
  - DEB: Install and run
    ```bash
    sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
    timesafari
    ```

- **macOS**:
  - `.app` bundle: Double-click `TimeSafari.app` in Finder
  - `.dmg` installer:
    1. Double-click the `.dmg` file
    2. Drag the app to your Applications folder
    3. Launch from Applications
  - `.zip` archive:
    1. Extract the `.zip` file
    2. Move `TimeSafari.app` to your Applications folder
    3. Launch from Applications

  Note: If you get a security warning when running the app:
  1. Right-click the app
  2. Select "Open"
  3. Click "Open" in the security dialog

### Development Testing

For testing the Electron build before packaging:

```bash
# Build and run in development mode (includes DevTools)
npm run electron:dev

# Build in production mode and test
npm run build:electron-prod && npm run electron:start
```

## Mobile Builds (Capacitor)

### iOS Build

Prerequisites: macOS with Xcode installed

1. Build the web assets:

   ```bash
   rm -rf dist
   npm run build:web
   npm run build:capacitor
   ```

2. Update iOS project with latest build:

   ```bash
   npx cap sync ios
   ```

  - If that fails with "Could not find..." then look at the "gem_path" instructions above.

3. Copy the assets:

   ```bash
   mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset
   npx capacitor-assets generate --ios
   ```

4. Bump the version to match Android:

   ```
   cd ios/App
   xcrun agvtool new-version 21
   # Unfortunately this edits Info.plist directly.
   #xcrun agvtool new-marketing-version 0.4.5
   cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.4.7;/g" > temp
   mv temp App.xcodeproj/project.pbxproj
   cd -
   ```

5. Open the project in Xcode:

   ```bash
   npx cap open ios
   ```

6. Use Xcode to build and run on simulator or device.

  * Select Product -> Destination with some Simulator version. Then click the run arrow.

7. Release

  * Under "General" renamed a bunch of things to "Time Safari"
  * Choose Product -> Destination -> Any iOS Device
  * Choose Product -> Archive
    * This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
    * If it fails with `building for 'iOS', but linking in dylib (.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run XCode outside that terminal (ie. not with `npx cap open ios`).
    * Click Distribute -> App Store Connect
  * In AppStoreConnect, add the build to the distribution: remove the current build with the "-" when you hover over it, then "Add Build" with the new build.
    * May have to go to App Review, click Submission, then hover over the build and click "-".
    * It can take 15 minutes for the build to show up in the list of builds.
    * You'll probably have to "Manage" something about encryption, disallowed in France.
    * Then "Save" and "Add to Review" and "Resubmit to App Review".

#### First-time iOS Configuration

- Generate certificates inside XCode.

- Right-click on App and under Signing & Capabilities set the Team.

### Android Build

Prerequisites: Android Studio with SDK installed

1. Build the web assets:

   ```bash
   rm -rf dist
   npm run build:web
   npm run build:capacitor
   ```

2. Update Android project with latest build:

   ```bash
   npx cap sync android
   ```

3. Copy the assets

   ```bash
   npx capacitor-assets generate --android
   ```

4. Bump version to match iOS: android/app/build.gradle

5. Open the project in Android Studio:

   ```bash
   npx cap open android
   ```

6. Use Android Studio to build and run on emulator or device.

## Android Build from the console

   ```bash
   cd android
   ./gradlew clean
   ./gradlew build -Dlint.baselines.continue=true
   cd -
   npx cap run android
   ```

... or, to create the `aab` file, `bundle` instead of `build`:

   ```bash
   ./gradlew bundleDebug -Dlint.baselines.continue=true
   ```

... or, to create a signed release:

  * Setup by adding the app/gradle.properties.secrets file (see properties at top of app/build.gradle) and the app/time-safari-upload-key-pkcs12.jks file
  * In app/build.gradle, bump the versionCode and maybe the versionName
  * Then `bundleRelease`:

   ```bash
   ./gradlew bundleRelease -Dlint.baselines.continue=true
   ```

  ... and find your `aab` file at app/build/outputs/bundle/release

At play.google.com/console:

- Go to the Testing Track (eg. Closed).
- Click "Create new release".
- Upload the `aab` file.
- Hit "Next".
- Save, go to "Publishing Overview" as prompted, and click "Send changes for review".


## First-time Android Configuration for deep links

You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file:

   ```xml
               <intent-filter android:autoVerify="true">
                  <action android:name="android.intent.action.VIEW" />
                  <category android:name="android.intent.category.DEFAULT" />
                  <category android:name="android.intent.category.BROWSABLE" />
                  <data android:scheme="timesafari" />
               </intent-filter>
   ```