Browse Source

migration: move to bash based build scripts

streamline-attempt
Matthew Raymer 2 weeks ago
parent
commit
25974cae22
  1. 25
      .cursor/rules/architectural_decision_record.mdc
  2. 171
      .dockerignore
  3. 594
      BUILDING.md
  4. 201
      Dockerfile
  5. 2
      android/build.gradle
  6. 210
      docker-compose.yml
  7. 509
      docker/README.md
  8. 110
      docker/default.conf
  9. 72
      docker/nginx.conf
  10. 272
      docker/run.sh
  11. 110
      docker/staging.conf
  12. 155
      experiment.sh
  13. 5
      index.html
  14. 26
      package.json
  15. 6
      requirements.txt
  16. 285
      scripts/README.md
  17. 71
      scripts/build-android.sh
  18. 82
      scripts/build-electron-linux.sh
  19. 74
      scripts/build-electron-mac.sh
  20. 53
      scripts/build-electron.sh
  21. 331
      scripts/common.sh
  22. 44
      scripts/electron-dev.sh
  23. 44
      scripts/test-all.sh
  24. 74
      scripts/test-common.sh
  25. 59
      scripts/test-env.sh
  26. 40
      scripts/test-mobile.sh
  27. 4
      src/main.pywebview.ts
  28. 59
      src/pywebview/main.py
  29. 5
      src/services/PlatformServiceFactory.ts
  30. 135
      src/services/platforms/PyWebViewPlatformService.ts
  31. 8
      src/services/platforms/WebPlatformService.ts
  32. 3
      src/views/HomeView.vue
  33. 6
      vite.config.common.mts
  34. 4
      vite.config.pywebview.mts

25
.cursor/rules/architectural_decision_record.mdc

@ -7,13 +7,13 @@ alwaysApply: true
## 1. Platform Support Matrix
| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) | PyWebView (Desktop) |
|---------|-----------|-------------------|-------------------|-------------------|
| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented | Not Implemented |
| Deep Linking | URL Parameters | App URL Open Events | Not Implemented | Not Implemented |
| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs | PyWebView Python Bridge |
| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented | Not Implemented |
| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks | process.env checks |
| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) |
|---------|-----------|-------------------|-------------------|
| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented |
| Deep Linking | URL Parameters | App URL Open Events | Not Implemented |
| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs |
| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented |
| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks |
## 2. Project Structure
@ -42,7 +42,6 @@ src/
├── main.common.ts # Shared initialization
├── main.capacitor.ts # Mobile entry
├── main.electron.ts # Electron entry
├── main.pywebview.ts # PyWebView entry
└── main.web.ts # Web/PWA entry
```
@ -52,9 +51,7 @@ root/
├── vite.config.common.mts # Shared config
├── vite.config.capacitor.mts # Mobile build
├── vite.config.electron.mts # Electron build
├── vite.config.pywebview.mts # PyWebView build
├── vite.config.web.mts # Web/PWA build
└── vite.config.utils.mts # Build utilities
└── vite.config.web.mts # Web/PWA build
```
## 3. Service Architecture
@ -68,8 +65,7 @@ services/
├── platforms/ # Platform-specific services
│ ├── WebPlatformService.ts
│ ├── CapacitorPlatformService.ts
│ ├── ElectronPlatformService.ts
│ └── PyWebViewPlatformService.ts
│ └── ElectronPlatformService.ts
└── factory/ # Service factories
└── PlatformServiceFactory.ts
```
@ -167,8 +163,7 @@ export function createBuildConfig(mode: string) {
# Build commands from package.json
"build:web": "vite build --config vite.config.web.mts",
"build:capacitor": "vite build --config vite.config.capacitor.mts",
"build:electron": "vite build --config vite.config.electron.mts",
"build:pywebview": "vite build --config vite.config.pywebview.mts"
"build:electron": "vite build --config vite.config.electron.mts"
```
## 6. Testing Strategy

171
.dockerignore

@ -0,0 +1,171 @@
# TimeSafari Docker Ignore File
# Author: Matthew Raymer
# Description: Excludes unnecessary files from Docker build context
#
# Benefits:
# - Faster build times
# - Smaller build context
# - Reduced image size
# - Better security (excludes sensitive files)
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist
dist-*
build
*.tsbuildinfo
# Development files
.git
.gitignore
README.md
CHANGELOG.md
CONTRIBUTING.md
BUILDING.md
LICENSE
# IDE and editor files
.vscode
.idea
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# Test files
test-playwright
test-playwright-results
test-results
test-scripts
# Documentation
doc
# Scripts (keep only what's needed for build)
scripts/test-*.sh
scripts/*.js
scripts/README.md
# Platform-specific files
android
ios
electron
# Docker files (avoid recursive copying)
Dockerfile*
docker-compose*
.dockerignore
# CI/CD files
.github
.gitlab-ci.yml
.travis.yml
.circleci
# Temporary files
tmp
temp
# Backup files
*.bak
*.backup
# Archive files
*.tar
*.tar.gz
*.zip
*.rar
# Certificate files
*.pem
*.key
*.crt
*.p12
# Configuration files that might contain secrets
*.secrets
secrets.json
config.local.json

594
BUILDING.md

@ -1,6 +1,6 @@
# Building TimeSafari
This guide explains how to build TimeSafari for different platforms.
This guide explains how to build TimeSafari for different platforms using our unified build scripts.
## Prerequisites
@ -11,6 +11,57 @@ For a quick dev environment setup, use [pkgx](https://pkgx.dev).
- Git
- For desktop builds: Additional build tools based on your OS
## 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 |
|--------|---------|---------|
| `electron-dev.sh` | Electron development | `./scripts/electron-dev.sh` |
| `electron-build.sh` | Electron build | `./scripts/build-electron.sh` |
| `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 |
|------------|---------------|------------------|------------------|----------|
| `electron` | electron | false | true | production* |
| `capacitor` | capacitor | false | true | - |
| `web` | web | true | false | - |
*NODE_ENV=production only set when production mode is enabled
### CLI Options
All scripts support these options:
```bash
# Show help
./scripts/build-electron.sh --help
# Enable verbose logging
./scripts/build-electron.sh --verbose
# Show environment variables
./scripts/build-electron.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.
@ -30,18 +81,19 @@ Install dependencies:
npm install
```
## Web Dev Locally
## Web Development
### Local Development
```bash
npm run dev
```
## Web Build for Server
### Web Build for Server
1. Run the production build:
```bash
rm -rf dist
npm run build:web
```
@ -49,204 +101,78 @@ Install dependencies:
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.
### Environment Configuration
* `npx prettier --write ./sw_scripts/`
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
* Run a build to make sure package-lock version is updated, linting works, etc: `npm install && npm run build`
* 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 in the step later).
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 1.0.2 && git push origin 1.0.2`.
* For test, build the app (because test server is not yet set up to build):
For different environments, create `.env` files:
```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:web
```
... and transfer to the test server:
# .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
```bash
rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
# .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
```
(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 1.0.2 && npm install && npm run build:web && cd -`
(The plain `npm run build:web` uses the .env.production file.)
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev-2 && 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`, commit, and push. 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:
## Desktop Build (Electron)
```yaml
version: '3.8'
services:
timesafari:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=production
restart: unless-stopped
```
### Development
Run with Docker Compose:
For development with automatic environment setup:
```bash
docker-compose up -d
./scripts/electron-dev.sh
```
### Production Deployment
### Production Build
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:
For production builds with automatic environment setup:
```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
./scripts/build-electron.sh
```
### 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:
### Linux Packaging
```bash
npm run build:electron-prod
```
# Build AppImage (recommended)
./scripts/build-electron-linux.sh
2. Package the Electron app for Linux:
```bash
# For AppImage (recommended)
npm run electron:build-linux
# Build .deb package
./scripts/build-electron-linux.sh deb
# For .deb package
npm run electron:build-linux-deb
# Build production AppImage
./scripts/build-electron-linux.sh prod
```
3. The packaged applications will be in `dist-electron-packages/`:
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:
### macOS Packaging
```bash
# For Intel Macs
npm run electron:build-mac
# Build standard Mac package
./scripts/build-electron-mac.sh
# For Universal build (Intel + Apple Silicon)
npm run electron:build-mac-universal
# Build universal package (Intel + Apple Silicon)
./scripts/build-electron-mac.sh universal
```
3. The packaged applications will be in `dist-electron-packages/`:
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`
@ -255,17 +181,17 @@ docker run -d \
For public distribution on macOS, you need to code sign and notarize your app:
1. Set up environment variables:
1. Set up environment variables in `.env` file:
```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
CSC_LINK=/path/to/your/certificate.p12
CSC_KEY_PASSWORD=your_certificate_password
APPLE_ID=your_apple_id
APPLE_ID_PASSWORD=your_app_specific_password
```
2. Build with signing:
```bash
npm run electron:build-mac
./scripts/build-electron-mac.sh
```
### Running the Packaged App
@ -298,174 +224,272 @@ For public distribution on macOS, you need to code sign and notarize your app:
2. Select "Open"
3. Click "Open" in the security dialog
### Development Testing
## Mobile Builds (Capacitor)
For testing the Electron build before packaging:
### Android Build
```bash
# Build and run in development mode (includes DevTools)
npm run electron:dev
Prerequisites: Android Studio with Java SDK installed
# Build in production mode and test
npm run build:electron-prod && npm run electron:start
#### Complete Build Process
Use the unified Android build script:
```bash
./scripts/build-android.sh
```
## Mobile Builds (Capacitor)
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
### iOS Build
#### Manual Steps (if needed)
Prerequisites: macOS with Xcode installed
If you need to run individual steps:
#### First-time iOS Configuration
1. Build the web assets:
```bash
npm run build:web
npm run build:capacitor
```
2. Update Android project:
```bash
npx cap sync android
```
- Generate certificates inside XCode.
3. Generate assets:
```bash
npx capacitor-assets generate --android
```
- Right-click on App and under Signing & Capabilities set the Team.
4. Open in Android Studio:
```bash
npx cap open android
```
#### Each Release
#### Console Build
0. First time (or if dependencies change):
For building from the console:
- `pkgx +rubygems.org sh`
```bash
cd android
./gradlew clean
./gradlew build -Dlint.baselines.continue=true
cd -
```
- ... and you may have to fix these, especially with pkgx:
For creating an `aab` file:
```bash
gem_path=$(which gem)
shortened_path="${gem_path:h:h}"
export GEM_HOME=$shortened_path
export GEM_PATH=$shortened_path
cd android
./gradlew bundleDebug -Dlint.baselines.continue=true
cd -
```
1. Build the web assets & update ios:
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
rm -rf dist
npm run build:web
npm run build:capacitor
npx cap sync ios
```
- If that fails with "Could not find..." then look at the "gem_path" instructions above.
3. Copy the assets:
2. Generate assets:
```bash
# It makes no sense why capacitor-assets will not run without these but it actually changes the contents.
# 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
```
4. Bump the version to match Android & package.json:
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
```
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 -
# Unfortunately this edits Info.plist directly.
#xcrun agvtool new-marketing-version 0.4.5
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
```
5. Open the project in Xcode:
### Mobile Tests
Run mobile-specific tests:
```bash
npx cap open ios
./scripts/test-mobile.sh
```
6. Use Xcode to build and run on simulator or device.
### Environment Testing
Test environment variable handling:
```bash
./scripts/test-env.sh
```
* Select Product -> Destination with some Simulator version. Then click the run arrow.
## Docker Deployment
7. Release
The application can be containerized using Docker for consistent deployment across environments.
* Someday: Under "General" we want to rename 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".
### Prerequisites
### Android Build
- Docker installed on your system
- Docker Compose (optional, for multi-container setups)
Prerequisites: Android Studio with Java SDK installed
### Building the Docker Image
1. Build the web assets:
1. Build the Docker image:
```bash
rm -rf dist
npm run build:web
npm run build:capacitor
docker build -t timesafari:latest .
```
2. Update Android project with latest build:
2. For development builds with specific environment variables:
```bash
npx cap sync android
docker build --build-arg NODE_ENV=development -t timesafari:dev .
```
3. Copy the assets
### Running the Container
1. Run the container:
```bash
npx capacitor-assets generate --android
docker run -d -p 80:80 timesafari:latest
```
4. Bump version to match iOS & package.json: android/app/build.gradle
5. Open the project in Android Studio:
2. For development with hot-reloading:
```bash
npx cap open android
docker run -d -p 80:80 -v $(pwd):/app timesafari:dev
```
6. Use Android Studio to build and run on emulator or device.
### Using Docker Compose
## Android Build from the console
Create a `docker-compose.yml` file:
```bash
cd android
./gradlew clean
./gradlew build -Dlint.baselines.continue=true
cd -
```yaml
version: '3.8'
services:
timesafari:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=production
restart: unless-stopped
```
... or, to create the `aab` file, `bundle` instead of `build`:
Run with Docker Compose:
```bash
./gradlew bundleDebug -Dlint.baselines.continue=true
docker-compose up -d
```
... or, to create a signed release:
### Production Deployment
* 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`:
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
cd android
./gradlew bundleRelease -Dlint.baselines.continue=true
cd -
# 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
```
... and find your `aab` file at app/build/outputs/bundle/release
### Troubleshooting Docker
At play.google.com/console:
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
- 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".
3. **Performance issues**
- Monitor container resources: `docker stats`
- Check nginx configuration
- Verify caching settings
- Note that if you add testers, you have to go to "Publishing Overview" and send those changes or your (closed) testers won't see it.
## Configuration
### Deep Links
## Android Configuration for deep links
#### Android Configuration
You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file:
@ -478,4 +502,52 @@ You must add the following intent filter to the `android/app/src/main/AndroidMan
</intent-filter>
```
... though when we tried that most recently it failed to 'build' the APK with: http(s) scheme and host attribute are missing, but are required for Android App Links [AppLinkUrlError]
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-electron.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-electron.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**
- **Linux**: Ensure AppImage dependencies are installed
- **macOS**: Check code signing certificates and entitlements
- **Android**: Verify Android Studio and SDK are properly configured
- **iOS**: Ensure Xcode and certificates are set up correctly
### Getting Help
- Check script help: `./scripts/build-electron.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` | electron | false | true | Desktop app (Windows/macOS/Linux) |

201
Dockerfile

@ -1,36 +1,209 @@
# Build stage
FROM node:22-alpine3.20 AS builder
# TimeSafari Docker Build
# Author: Matthew Raymer
# Description: Multi-stage Docker build for TimeSafari web application
#
# Build Process:
# 1. Base stage: Node.js with build dependencies
# 2. Builder stage: Compile web assets with Vite
# 3. Production stage: Nginx server with optimized assets
#
# Security Features:
# - Non-root user execution
# - Minimal attack surface with Alpine Linux
# - Multi-stage build to reduce image size
# - No build dependencies in final image
#
# Usage:
# Production: docker build -t timesafari:latest .
# Staging: docker build --build-arg BUILD_MODE=staging -t timesafari:staging .
# Development: docker build --build-arg BUILD_MODE=development -t timesafari:dev .
#
# Build Arguments:
# BUILD_MODE: development, staging, or production (default: production)
# NODE_ENV: node environment (default: production)
# VITE_PLATFORM: vite platform (default: web)
# VITE_PWA_ENABLED: enable PWA (default: true)
# VITE_DISABLE_PWA: disable PWA (default: false)
#
# Environment Variables:
# NODE_ENV: Build environment (development/production)
# VITE_APP_SERVER: Application server URL
# VITE_DEFAULT_ENDORSER_API_SERVER: Endorser API server URL
# VITE_DEFAULT_IMAGE_API_SERVER: Image API server URL
# VITE_DEFAULT_PARTNER_API_SERVER: Partner API server URL
# VITE_DEFAULT_PUSH_SERVER: Push notification server URL
# VITE_PASSKEYS_ENABLED: Enable passkeys feature
# Install build dependencies
# =============================================================================
# BASE STAGE - Common dependencies and setup
# =============================================================================
FROM node:22-alpine3.20 AS base
RUN apk add --no-cache bash git python3 py3-pip py3-setuptools make g++ gcc
# Install system dependencies for build process
RUN apk add --no-cache \
bash \
git \
python3 \
py3-pip \
py3-setuptools \
make \
g++ \
gcc \
&& rm -rf /var/cache/apk/*
# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# Set working directory
WORKDIR /app
# Copy package files
# Copy package files for dependency installation
COPY package*.json ./
# Install dependencies
RUN npm ci
# Install dependencies with security audit
RUN npm ci --only=production --audit --fund=false && \
npm audit fix --audit-level=moderate || true
# =============================================================================
# BUILDER STAGE - Compile web assets
# =============================================================================
FROM base AS builder
# Define build arguments with defaults
ARG BUILD_MODE=production
ARG NODE_ENV=production
ARG VITE_PLATFORM=web
ARG VITE_PWA_ENABLED=true
ARG VITE_DISABLE_PWA=false
# Set environment variables from build arguments
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
ENV VITE_PLATFORM=${VITE_PLATFORM}
ENV VITE_PWA_ENABLED=${VITE_PWA_ENABLED}
ENV VITE_DISABLE_PWA=${VITE_DISABLE_PWA}
# Install all dependencies (including dev dependencies)
RUN npm ci --audit --fund=false && \
npm audit fix --audit-level=moderate || true
# Copy source code
COPY . .
# Build the application
RUN npm run build:web
# Build the application with proper error handling
RUN echo "Building TimeSafari in ${BUILD_MODE} mode..." && \
npm run build:web || (echo "Build failed. Check the logs above." && exit 1)
# Verify build output exists
RUN ls -la dist/ || (echo "Build output not found in dist/ directory" && exit 1)
# =============================================================================
# PRODUCTION STAGE - Nginx server
# =============================================================================
FROM nginx:alpine AS production
# Define build arguments for production stage
ARG BUILD_MODE=production
ARG NODE_ENV=production
# Set environment variables
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
# Production stage
FROM nginx:alpine
# Install security updates and clean cache
RUN apk update && \
apk upgrade && \
apk add --no-cache \
curl \
&& rm -rf /var/cache/apk/*
# Create non-root user for nginx
RUN addgroup -g 1001 -S nginx && \
adduser -S nginx -u 1001 -G nginx
# Copy appropriate nginx configuration based on build mode
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/default.conf /etc/nginx/conf.d/default.conf
# Copy staging configuration if needed
COPY docker/staging.conf /etc/nginx/conf.d/staging.conf
# Copy built assets from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html
COPY --from=builder --chown=nginx:nginx /app/dist /usr/share/nginx/html
# Create necessary directories with proper permissions
RUN mkdir -p /var/cache/nginx /var/log/nginx /var/run && \
chown -R nginx:nginx /var/cache/nginx /var/log/nginx /var/run && \
chown -R nginx:nginx /usr/share/nginx/html
# Copy nginx configuration if needed
# COPY nginx.conf /etc/nginx/conf.d/default.conf
# Switch to non-root user
USER nginx
# Expose port 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# Start nginx with proper signal handling
CMD ["nginx", "-g", "daemon off;"]
# =============================================================================
# DEVELOPMENT STAGE - For development with hot reloading
# =============================================================================
FROM base AS development
# Define build arguments for development stage
ARG BUILD_MODE=development
ARG NODE_ENV=development
ARG VITE_PLATFORM=web
ARG VITE_PWA_ENABLED=true
ARG VITE_DISABLE_PWA=false
# Set environment variables
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
ENV VITE_PLATFORM=${VITE_PLATFORM}
ENV VITE_PWA_ENABLED=${VITE_PWA_ENABLED}
ENV VITE_DISABLE_PWA=${VITE_DISABLE_PWA}
# Install all dependencies including dev dependencies
RUN npm ci --audit --fund=false && \
npm audit fix --audit-level=moderate || true
# Copy source code
COPY . .
# Expose development port
EXPOSE 5173
# Start development server
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# =============================================================================
# STAGING STAGE - For staging environment testing
# =============================================================================
FROM production AS staging
# Define build arguments for staging stage
ARG BUILD_MODE=staging
ARG NODE_ENV=staging
# Set environment variables
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
# Copy staging-specific nginx configuration
COPY docker/staging.conf /etc/nginx/conf.d/default.conf
# Expose port 80
EXPOSE 80
# Health check for staging
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/health || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

2
android/build.gradle

@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.9.1'
classpath 'com.android.tools.build:gradle:8.10.1'
classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong

210
docker-compose.yml

@ -0,0 +1,210 @@
# TimeSafari Docker Compose Configuration
# Author: Matthew Raymer
# Description: Multi-environment Docker Compose setup for TimeSafari
#
# Usage:
# Development: docker-compose up dev
# Staging: docker-compose up staging
# Production: docker-compose up production
# Custom: BUILD_MODE=staging docker-compose up custom
#
# Environment Variables:
# BUILD_MODE: development, staging, or production (default: production)
# NODE_ENV: node environment (default: production)
# VITE_PLATFORM: vite platform (default: web)
# VITE_PWA_ENABLED: enable PWA (default: true)
# VITE_DISABLE_PWA: disable PWA (default: false)
# PORT: port to expose (default: 80 for production, 5173 for dev)
# ENV_FILE: environment file to use (default: .env.production)
#
# See .env files for application-specific configuration
# VITE_APP_SERVER: Application server URL
# VITE_DEFAULT_ENDORSER_API_SERVER: Endorser API server URL
version: '3.8'
# Default values that can be overridden
x-defaults: &defaults
build:
context: .
dockerfile: Dockerfile
args:
BUILD_MODE: ${BUILD_MODE:-production}
NODE_ENV: ${NODE_ENV:-production}
VITE_PLATFORM: ${VITE_PLATFORM:-web}
VITE_PWA_ENABLED: ${VITE_PWA_ENABLED:-true}
VITE_DISABLE_PWA: ${VITE_DISABLE_PWA:-false}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
services:
# Development service with hot reloading
dev:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: development
args:
BUILD_MODE: development
NODE_ENV: development
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${DEV_PORT:-5173}:5173"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${DEV_ENV_FILE:-.env.development}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5173"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Staging service for testing
staging:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: staging
args:
BUILD_MODE: staging
NODE_ENV: staging
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${STAGING_PORT:-8080}:80"
environment:
- NODE_ENV=staging
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${STAGING_ENV_FILE:-.env.staging}
# Production service
production:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: production
args:
BUILD_MODE: production
NODE_ENV: production
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${PROD_PORT:-80}:80"
environment:
- NODE_ENV=production
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${PROD_ENV_FILE:-.env.production}
# Production service with SSL (requires certificates)
production-ssl:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: production
args:
BUILD_MODE: production
NODE_ENV: production
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${SSL_PORT:-443}:443"
- "${HTTP_PORT:-80}:80"
environment:
- NODE_ENV=production
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${PROD_ENV_FILE:-.env.production}
volumes:
- ./ssl:/etc/nginx/ssl:ro
- ./docker/nginx-ssl.conf:/etc/nginx/conf.d/default.conf:ro
healthcheck:
test: ["CMD", "curl", "-f", "https://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Custom service - configurable via environment variables
custom:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: ${BUILD_TARGET:-production}
args:
BUILD_MODE: ${BUILD_MODE:-production}
NODE_ENV: ${NODE_ENV:-production}
VITE_PLATFORM: ${VITE_PLATFORM:-web}
VITE_PWA_ENABLED: ${VITE_PWA_ENABLED:-true}
VITE_DISABLE_PWA: ${VITE_DISABLE_PWA:-false}
ports:
- "${CUSTOM_PORT:-8080}:${CUSTOM_INTERNAL_PORT:-80}"
environment:
- NODE_ENV=${NODE_ENV:-production}
- VITE_PLATFORM=${VITE_PLATFORM:-web}
- VITE_PWA_ENABLED=${VITE_PWA_ENABLED:-true}
- VITE_DISABLE_PWA=${VITE_DISABLE_PWA:-false}
env_file:
- ${CUSTOM_ENV_FILE:-.env.production}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${CUSTOM_INTERNAL_PORT:-80}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Load balancer for production (optional)
nginx-lb:
image: nginx:alpine
ports:
- "${LB_PORT:-80}:80"
- "${LB_SSL_PORT:-443}:443"
volumes:
- ./docker/nginx-lb.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- production
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
default:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16

509
docker/README.md

@ -0,0 +1,509 @@
# TimeSafari Docker Setup
## Overview
This directory contains Docker configuration files for building and deploying TimeSafari across different environments with full configurability.
## Files
- `Dockerfile` - Multi-stage Docker build for TimeSafari
- `nginx.conf` - Main nginx configuration with security headers
- `default.conf` - Production server configuration
- `staging.conf` - Staging server configuration with relaxed caching
- `docker-compose.yml` - Multi-environment Docker Compose setup
- `.dockerignore` - Optimizes build context
- `run.sh` - Convenient script to run different configurations
## Quick Start
### Using the Run Script (Recommended)
```bash
# Development mode with hot reloading
./docker/run.sh dev
# Staging mode for testing
./docker/run.sh staging
# Production mode
./docker/run.sh production
# Custom mode with environment variables
BUILD_MODE=staging ./docker/run.sh custom
# Show build arguments for a mode
./docker/run.sh dev --build-args
# Custom port and environment file
./docker/run.sh staging --port 9000 --env .env.test
```
### Using Docker Compose
```bash
# Development environment with hot reloading
docker-compose up dev
# Staging environment
docker-compose up staging
# Production environment
docker-compose up production
# Custom environment with variables
BUILD_MODE=staging docker-compose up custom
```
## Build Commands
### Manual Docker Build
```bash
# Build production image (default)
docker build -t timesafari:latest .
# Build staging image
docker build --build-arg BUILD_MODE=staging -t timesafari:staging .
# Build development image
docker build --build-arg BUILD_MODE=development -t timesafari:dev .
# Build with custom arguments
docker build \
--build-arg BUILD_MODE=staging \
--build-arg NODE_ENV=staging \
--build-arg VITE_PWA_ENABLED=true \
-t timesafari:custom .
```
### Run Container
```bash
# Run production container
docker run -d -p 80:80 timesafari:latest
# Run with environment file
docker run -d -p 80:80 --env-file .env.production timesafari:latest
# Run with custom environment variables
docker run -d -p 80:80 \
-e VITE_APP_SERVER=https://myapp.com \
-e VITE_DEFAULT_ENDORSER_API_SERVER=https://api.myapp.com \
timesafari:latest
```
## Configuration Options
### Build Arguments
The Dockerfile supports these build arguments:
| Argument | Default | Description |
|----------|---------|-------------|
| `BUILD_MODE` | `production` | Build mode: development, staging, or production |
| `NODE_ENV` | `production` | Node.js environment |
| `VITE_PLATFORM` | `web` | Vite platform type |
| `VITE_PWA_ENABLED` | `true` | Enable PWA features |
| `VITE_DISABLE_PWA` | `false` | Disable PWA features |
### Environment Variables
Docker Compose supports these environment variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `BUILD_MODE` | `production` | Build mode |
| `NODE_ENV` | `production` | Node environment |
| `VITE_PLATFORM` | `web` | Vite platform |
| `VITE_PWA_ENABLED` | `true` | Enable PWA |
| `VITE_DISABLE_PWA` | `false` | Disable PWA |
| `DEV_PORT` | `5173` | Development port |
| `STAGING_PORT` | `8080` | Staging port |
| `PROD_PORT` | `80` | Production port |
| `DEV_ENV_FILE` | `.env.development` | Development env file |
| `STAGING_ENV_FILE` | `.env.staging` | Staging env file |
| `PROD_ENV_FILE` | `.env.production` | Production env file |
### Environment Files
Create environment files for different deployments:
```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.staging
VITE_APP_SERVER=https://staging.timesafari.app
VITE_DEFAULT_ENDORSER_API_SERVER=https://staging-api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://staging-image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=https://staging-partner-api.endorser.ch
VITE_DEFAULT_PUSH_SERVER=https://staging.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
```
## Build Modes
### Development Mode
- **Target**: `development`
- **Features**: Hot reloading, development server
- **Port**: 5173
- **Caching**: Disabled
- **Use Case**: Local development
```bash
./docker/run.sh dev
# or
docker build --target development -t timesafari:dev .
```
### Staging Mode
- **Target**: `staging`
- **Features**: Production build with relaxed caching
- **Port**: 8080 (mapped from 80)
- **Caching**: Short-term (1 hour)
- **Use Case**: Testing and QA
```bash
./docker/run.sh staging
# or
docker build --build-arg BUILD_MODE=staging -t timesafari:staging .
```
### Production Mode
- **Target**: `production`
- **Features**: Optimized production build
- **Port**: 80
- **Caching**: Long-term (1 year for assets)
- **Use Case**: Live deployment
```bash
./docker/run.sh production
# or
docker build -t timesafari:latest .
```
### Custom Mode
- **Target**: Configurable via `BUILD_TARGET`
- **Features**: Fully configurable
- **Port**: Configurable via `CUSTOM_PORT`
- **Use Case**: Special deployments
```bash
BUILD_MODE=staging NODE_ENV=staging ./docker/run.sh custom
```
## Advanced Usage
### Custom Build Configuration
```bash
# Build with specific environment
docker build \
--build-arg BUILD_MODE=staging \
--build-arg NODE_ENV=staging \
--build-arg VITE_PWA_ENABLED=false \
-t timesafari:staging-no-pwa .
# Run with custom configuration
docker run -d -p 9000:80 \
-e VITE_APP_SERVER=https://test.example.com \
timesafari:staging-no-pwa
```
### Docker Compose with Custom Variables
```bash
# Set environment variables
export BUILD_MODE=staging
export NODE_ENV=staging
export STAGING_PORT=9000
export STAGING_ENV_FILE=.env.test
# Run staging with custom config
docker-compose up staging
```
### Multi-Environment Deployment
```bash
# Development
./docker/run.sh dev
# Staging in another terminal
./docker/run.sh staging --port 8081
# Production in another terminal
./docker/run.sh production --port 8082
```
## Security Features
### Built-in Security
- **Non-root user execution**: All containers run as non-root users
- **Security headers**: XSS protection, content type options, frame options
- **Rate limiting**: API request rate limiting
- **File access restrictions**: Hidden files and backup files blocked
- **Minimal attack surface**: Alpine Linux base images
### Security Headers
- `X-Frame-Options: SAMEORIGIN`
- `X-Content-Type-Options: nosniff`
- `X-XSS-Protection: 1; mode=block`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Content-Security-Policy`: Comprehensive CSP policy
## Performance Optimizations
### Caching Strategy
- **Static assets**: 1 year cache with immutable flag (production)
- **HTML files**: 1 hour cache (production) / no cache (staging)
- **Service worker**: No cache
- **Manifest**: 1 day cache (production) / 1 hour cache (staging)
### Compression
- **Gzip compression**: Enabled for text-based files
- **Compression level**: 6 (balanced)
- **Minimum size**: 1024 bytes
### Nginx Optimizations
- **Sendfile**: Enabled for efficient file serving
- **TCP optimizations**: nopush and nodelay enabled
- **Keepalive**: 65 second timeout
- **Worker processes**: Auto-detected based on CPU cores
## Health Checks
### Built-in Health Checks
All services include health checks that:
- Check every 30 seconds
- Timeout after 10 seconds
- Retry 3 times before marking unhealthy
- Start checking after 40 seconds
### Health Check Endpoints
- **Production/Staging**: `http://localhost/health`
- **Development**: `http://localhost:5173`
## SSL/HTTPS Setup
### SSL Certificates
For SSL deployment, create an `ssl` directory with certificates:
```bash
mkdir ssl
# Copy your certificates to ssl/ directory
cp your-cert.pem ssl/
cp your-key.pem ssl/
```
### SSL Configuration
Use the `production-ssl` service in docker-compose:
```bash
docker-compose up production-ssl
```
## Monitoring and Logging
### Log Locations
- **Access logs**: `/var/log/nginx/access.log`
- **Error logs**: `/var/log/nginx/error.log`
### Log Format
```
$remote_addr - $remote_user [$time_local] "$request"
$status $body_bytes_sent "$http_referer"
"$http_user_agent" "$http_x_forwarded_for"
```
### Log Levels
- **Production**: `warn` level
- **Staging**: `debug` level
- **Development**: Full logging
## Troubleshooting
### Common Issues
#### Build Failures
```bash
# Check build logs
docker build -t timesafari:latest . 2>&1 | tee build.log
# Verify dependencies
docker run --rm timesafari:latest npm list --depth=0
# Check build arguments
./docker/run.sh dev --build-args
```
#### Container Won't Start
```bash
# Check container logs
docker logs <container_id>
# Check health status
docker inspect <container_id> | grep -A 10 "Health"
# Verify port availability
netstat -tulpn | grep :80
```
#### Environment Variables Not Set
```bash
# Check environment in container
docker exec <container_id> env | grep VITE_
# Verify .env file
cat .env.production
# Check build arguments
./docker/run.sh production --build-args
```
#### Performance Issues
```bash
# Check container resources
docker stats <container_id>
# Check nginx configuration
docker exec <container_id> nginx -t
# Monitor access logs
docker exec <container_id> tail -f /var/log/nginx/access.log
```
### Debug Commands
#### Container Debugging
```bash
# Enter running container
docker exec -it <container_id> /bin/sh
# Check nginx status
docker exec <container_id> nginx -t
# Check file permissions
docker exec <container_id> ls -la /usr/share/nginx/html
```
#### Network Debugging
```bash
# Check container network
docker network inspect bridge
# Test connectivity
docker exec <container_id> curl -I http://localhost
# Check DNS resolution
docker exec <container_id> nslookup google.com
```
## Production Deployment
### Recommended Production Setup
1. **Use specific version tags**: `timesafari:1.0.0`
2. **Implement health checks**: Already included
3. **Configure proper logging**: Use external log aggregation
4. **Set up reverse proxy**: Use nginx-lb service
5. **Use Docker secrets**: For sensitive data
### Production Commands
```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 \
--env-file .env.production \
timesafari:1.0.0
# Update production deployment
docker stop timesafari
docker rm timesafari
docker build -t timesafari:1.0.1 .
docker run -d --name timesafari -p 80:80 --restart unless-stopped --env-file .env.production timesafari:1.0.1
```
## Development Workflow
### Local Development
```bash
# Start development environment
./docker/run.sh dev
# Make changes to code (hot reloading enabled)
# Access at http://localhost:5173
# Stop development environment
docker-compose down dev
```
### Testing Changes
```bash
# Build and test staging
./docker/run.sh staging
# Test production build locally
./docker/run.sh production
```
### Continuous Integration
```bash
# Build and test in CI
docker build -t timesafari:test .
docker run -d --name timesafari-test -p 8080:80 timesafari:test
# Run tests against container
curl -f http://localhost:8080/health
# Cleanup
docker stop timesafari-test
docker rm timesafari-test
```
## Best Practices
### Security
- Always use non-root users
- Keep base images updated
- Scan images for vulnerabilities
- Use secrets for sensitive data
- Implement proper access controls
### Performance
- Use multi-stage builds
- Optimize layer caching
- Minimize image size
- Use appropriate base images
- Implement proper caching
### Monitoring
- Use health checks
- Monitor resource usage
- Set up log aggregation
- Implement metrics collection
- Use proper error handling
### Maintenance
- Regular security updates
- Monitor for vulnerabilities
- Keep dependencies updated
- Document configuration changes
- Test deployment procedures

110
docker/default.conf

@ -0,0 +1,110 @@
# TimeSafari Default Server Configuration
# Author: Matthew Raymer
# Description: Production server configuration for TimeSafari web application
#
# Features:
# - Vue.js SPA routing support
# - Static file caching optimization
# - Security hardening
# - Performance optimization
# - Proper error handling
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Handle Vue.js SPA routing
location / {
try_files $uri $uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
# Cache HTML files for a shorter time
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
}
# Handle service worker
location /sw.js {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
# Handle manifest file
location /manifest.json {
expires 1d;
add_header Cache-Control "public";
}
# Handle API requests (if needed)
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle health check
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Handle robots.txt
location /robots.txt {
expires 1d;
add_header Cache-Control "public";
}
# Handle favicon
location /favicon.ico {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security: Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Security: Deny access to backup files
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# Error pages
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# Logging
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
}

72
docker/nginx.conf

@ -0,0 +1,72 @@
# TimeSafari Nginx Configuration
# Author: Matthew Raymer
# Description: Main nginx configuration for TimeSafari web application
#
# Features:
# - Security headers for web application
# - Gzip compression for better performance
# - Proper handling of Vue.js SPA routing
# - Static file caching optimization
# - Security hardening
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 16M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self';" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Include server configurations
include /etc/nginx/conf.d/*.conf;
}

272
docker/run.sh

@ -0,0 +1,272 @@
#!/bin/bash
# TimeSafari Docker Run Script
# Author: Matthew Raymer
# Description: Convenient script to run TimeSafari in different Docker configurations
#
# Usage:
# ./docker/run.sh dev # Run development mode
# ./docker/run.sh staging # Run staging mode
# ./docker/run.sh production # Run production mode
# ./docker/run.sh custom # Run custom mode with environment variables
#
# Environment Variables:
# BUILD_MODE: development, staging, or production
# NODE_ENV: node environment
# VITE_PLATFORM: vite platform
# VITE_PWA_ENABLED: enable PWA
# VITE_DISABLE_PWA: disable PWA
# PORT: port to expose
# ENV_FILE: environment file to use
set -e
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
# Function to show usage
show_usage() {
echo "TimeSafari Docker Run Script"
echo ""
echo "Usage: $0 <mode> [options]"
echo ""
echo "Modes:"
echo " dev - Development mode with hot reloading"
echo " staging - Staging mode for testing"
echo " production - Production mode"
echo " custom - Custom mode with environment variables"
echo ""
echo "Options:"
echo " --port <port> - Custom port (default: 5173 for dev, 8080 for staging, 80 for production)"
echo " --env <file> - Environment file (default: .env.<mode>)"
echo " --build-args - Show build arguments for the mode"
echo " --help - Show this help message"
echo ""
echo "Examples:"
echo " $0 dev"
echo " $0 staging --port 9000"
echo " $0 production --env .env.prod"
echo " BUILD_MODE=staging $0 custom"
echo ""
echo "Environment Variables:"
echo " BUILD_MODE: development, staging, or production"
echo " NODE_ENV: node environment"
echo " VITE_PLATFORM: vite platform"
echo " VITE_PWA_ENABLED: enable PWA"
echo " VITE_DISABLE_PWA: disable PWA"
echo " PORT: port to expose"
echo " ENV_FILE: environment file to use"
}
# Function to show build arguments for a mode
show_build_args() {
local mode=$1
echo "Build arguments for $mode mode:"
echo ""
case $mode in
dev)
echo " BUILD_MODE: development"
echo " NODE_ENV: development"
echo " VITE_PLATFORM: web"
echo " VITE_PWA_ENABLED: true"
echo " VITE_DISABLE_PWA: false"
echo " Target: development"
echo " Port: 5173"
;;
staging)
echo " BUILD_MODE: staging"
echo " NODE_ENV: staging"
echo " VITE_PLATFORM: web"
echo " VITE_PWA_ENABLED: true"
echo " VITE_DISABLE_PWA: false"
echo " Target: staging"
echo " Port: 80 (mapped to 8080)"
;;
production)
echo " BUILD_MODE: production"
echo " NODE_ENV: production"
echo " VITE_PLATFORM: web"
echo " VITE_PWA_ENABLED: true"
echo " VITE_DISABLE_PWA: false"
echo " Target: production"
echo " Port: 80"
;;
custom)
echo " BUILD_MODE: \${BUILD_MODE:-production}"
echo " NODE_ENV: \${NODE_ENV:-production}"
echo " VITE_PLATFORM: \${VITE_PLATFORM:-web}"
echo " VITE_PWA_ENABLED: \${VITE_PWA_ENABLED:-true}"
echo " VITE_DISABLE_PWA: \${VITE_DISABLE_PWA:-false}"
echo " Target: \${BUILD_TARGET:-production}"
echo " Port: \${CUSTOM_PORT:-8080}:\${CUSTOM_INTERNAL_PORT:-80}"
;;
*)
log_error "Unknown mode: $mode"
exit 1
;;
esac
}
# Function to check if Docker is running
check_docker() {
if ! docker info > /dev/null 2>&1; then
log_error "Docker is not running. Please start Docker and try again."
exit 1
fi
}
# Function to check if docker-compose is available
check_docker_compose() {
if ! command -v docker-compose > /dev/null 2>&1; then
log_error "docker-compose is not installed. Please install docker-compose and try again."
exit 1
fi
}
# Function to check if required files exist
check_files() {
local mode=$1
local env_file=$2
if [ ! -f "Dockerfile" ]; then
log_error "Dockerfile not found. Please run this script from the project root."
exit 1
fi
if [ ! -f "docker-compose.yml" ]; then
log_error "docker-compose.yml not found. Please run this script from the project root."
exit 1
fi
if [ -n "$env_file" ] && [ ! -f "$env_file" ]; then
log_warn "Environment file $env_file not found. Using defaults."
fi
}
# Function to run the container
run_container() {
local mode=$1
local port=$2
local env_file=$3
log_info "Starting TimeSafari in $mode mode..."
# Set environment variables based on mode
case $mode in
dev)
export DEV_PORT=${port:-5173}
if [ -n "$env_file" ]; then
export DEV_ENV_FILE="$env_file"
fi
docker-compose up dev
;;
staging)
export STAGING_PORT=${port:-8080}
if [ -n "$env_file" ]; then
export STAGING_ENV_FILE="$env_file"
fi
docker-compose up staging
;;
production)
export PROD_PORT=${port:-80}
if [ -n "$env_file" ]; then
export PROD_ENV_FILE="$env_file"
fi
docker-compose up production
;;
custom)
export CUSTOM_PORT=${port:-8080}
if [ -n "$env_file" ]; then
export CUSTOM_ENV_FILE="$env_file"
fi
docker-compose up custom
;;
*)
log_error "Unknown mode: $mode"
exit 1
;;
esac
}
# Main script
main() {
local mode=""
local port=""
local env_file=""
local show_args=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
dev|staging|production|custom)
mode="$1"
shift
;;
--port)
port="$2"
shift 2
;;
--env)
env_file="$2"
shift 2
;;
--build-args)
show_args=true
shift
;;
--help|-h)
show_usage
exit 0
;;
*)
log_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Check if mode is provided
if [ -z "$mode" ]; then
log_error "No mode specified."
show_usage
exit 1
fi
# Show build arguments if requested
if [ "$show_args" = true ]; then
show_build_args "$mode"
exit 0
fi
# Check prerequisites
check_docker
check_docker_compose
check_files "$mode" "$env_file"
# Run the container
run_container "$mode" "$port" "$env_file"
}
# Run main function with all arguments
main "$@"

110
docker/staging.conf

@ -0,0 +1,110 @@
# TimeSafari Staging Server Configuration
# Author: Matthew Raymer
# Description: Staging server configuration for TimeSafari web application
#
# Features:
# - Relaxed caching for testing
# - Debug-friendly settings
# - Same security as production
# - Development-friendly error handling
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Security headers (same as production)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Handle Vue.js SPA routing
location / {
try_files $uri $uri/ /index.html;
# Relaxed caching for staging
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
add_header Vary "Accept-Encoding";
}
# No caching for HTML files in staging
location ~* \.html$ {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
}
# Handle service worker (no caching)
location /sw.js {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
# Handle manifest file (short cache)
location /manifest.json {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# Handle API requests (if needed)
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle health check
location /health {
access_log off;
return 200 "healthy-staging\n";
add_header Content-Type text/plain;
}
# Handle robots.txt (no caching in staging)
location /robots.txt {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Handle favicon (short cache)
location /favicon.ico {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# Security: Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Security: Deny access to backup files
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# Error pages (more verbose for staging)
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# Enhanced logging for staging
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log debug;
}

155
experiment.sh

@ -0,0 +1,155 @@
#!/bin/bash
# experiment.sh
# Author: Matthew Raymer
# Description: Build script for TimeSafari Electron application
# This script handles the complete build process for the TimeSafari Electron app,
# including web asset compilation and Capacitor sync.
#
# Build Process:
# 1. Environment setup and dependency checks
# 2. Web asset compilation (Vite)
# 3. Capacitor sync
# 4. Electron start
#
# Dependencies:
# - Node.js and npm
# - TypeScript
# - Vite
# - @capacitor-community/electron
#
# Usage: ./experiment.sh
#
# Exit Codes:
# 1 - Required command not found
# 2 - TypeScript installation failed
# 3 - Build process failed
# 4 - Capacitor sync failed
# 5 - Electron start failed
# Exit on any error
set -e
# ANSI color codes for better output formatting
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
# Function to check if a command exists
check_command() {
if ! command -v "$1" &> /dev/null; then
log_error "$1 is required but not installed."
exit 1
fi
log_info "Found $1: $(command -v "$1")"
}
# Function to measure and log execution time
measure_time() {
local start_time=$(date +%s)
"$@"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log_success "Completed in ${duration} seconds"
}
# Print build header
echo -e "\n${BLUE}=== TimeSafari Electron Build Process ===${NC}\n"
log_info "Starting build process at $(date)"
# Check required commands
log_info "Checking required dependencies..."
check_command node
check_command npm
check_command git
# Create application data directory
log_info "Setting up application directories..."
mkdir -p ~/.local/share/TimeSafari/timesafari
# Clean up previous builds
log_info "Cleaning previous builds..."
rm -rf dist* || log_warn "No previous builds to clean"
# Set environment variables for the build
log_info "Configuring build environment..."
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
# Ensure TypeScript is installed
log_info "Verifying TypeScript installation..."
if [ ! -f "./node_modules/.bin/tsc" ]; then
log_info "Installing TypeScript..."
if ! npm install --save-dev typescript@~5.2.2; then
log_error "TypeScript installation failed!"
exit 2
fi
# Verify installation
if [ ! -f "./node_modules/.bin/tsc" ]; then
log_error "TypeScript installation verification failed!"
exit 2
fi
log_success "TypeScript installed successfully"
else
log_info "TypeScript already installed"
fi
# Get git hash for versioning
GIT_HASH=$(git log -1 --pretty=format:%h)
log_info "Using git hash: ${GIT_HASH}"
# Build web assets
log_info "Building web assets with Vite..."
if ! measure_time env VITE_GIT_HASH="$GIT_HASH" npx vite build --config vite.config.app.electron.mts --mode electron; then
log_error "Web asset build failed!"
exit 3
fi
# Sync with Capacitor
log_info "Syncing with Capacitor..."
if ! measure_time npx cap sync electron; then
log_error "Capacitor sync failed!"
exit 4
fi
# Restore capacitor config
log_info "Restoring capacitor config..."
if ! git checkout electron/capacitor.config.json; then
log_error "Failed to restore capacitor config!"
exit 4
fi
# Start Electron
log_info "Starting Electron..."
cd electron/
if ! measure_time npm run electron:start; then
log_error "Electron start failed!"
exit 5
fi
# Print build summary
log_success "Build and start completed successfully!"
echo -e "\n${GREEN}=== End of Build Process ===${NC}\n"
# Exit with success
exit 0

5
index.html

@ -21,11 +21,10 @@
case 'electron':
import('./src/main.electron.ts');
break;
case 'pywebview':
import('./src/main.pywebview.ts');
break;
case 'web':
default:
import('./src/main.web.ts');
break;
}
</script>
</body>

26
package.json

@ -12,38 +12,32 @@
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
"test:all": "npm run test:prerequisites && npm run build && npm run test:web && npm run test:mobile",
"test:all": "./scripts/test-all.sh",
"test:prerequisites": "node scripts/check-prerequisites.js",
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
"test:mobile": "npm run build:capacitor && npm run test:android && npm run test:ios",
"test:mobile": "./scripts/test-mobile.sh",
"test:android": "node scripts/test-android.js",
"test:ios": "node scripts/test-ios.js",
"check:android-device": "adb devices | grep -w 'device' || (echo 'No Android device connected' && exit 1)",
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
"clean:electron": "rimraf dist-electron",
"build:pywebview": "vite build --config vite.config.pywebview.mts",
"build:electron": "npm run clean:electron && tsc -p tsconfig.electron.json && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
"build:electron": "./scripts/build-electron.sh",
"build:capacitor": "vite build --mode capacitor --config vite.config.capacitor.mts",
"build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
"electron:dev": "npm run build && electron .",
"electron:dev": "./scripts/electron-dev.sh",
"electron:start": "electron .",
"clean:android": "adb uninstall app.timesafari.app || true",
"build:android": "npm run clean:android && rm -rf dist && npm run build:web && npm run build:capacitor && cd android && ./gradlew clean && ./gradlew assembleDebug && cd .. && npx cap sync android && npx capacitor-assets generate --android && npx cap open android",
"electron:build-linux": "npm run build:electron && electron-builder --linux AppImage",
"electron:build-linux-deb": "npm run build:electron && electron-builder --linux deb",
"electron:build-linux-prod": "NODE_ENV=production npm run build:electron && electron-builder --linux AppImage",
"build:android": "./scripts/build-android.sh",
"electron:build-linux": "./scripts/build-electron-linux.sh",
"electron:build-linux-deb": "./scripts/build-electron-linux.sh deb",
"electron:build-linux-prod": "./scripts/build-electron-linux.sh prod",
"build:electron-prod": "NODE_ENV=production npm run build:electron",
"pywebview:dev": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
"pywebview:build": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
"pywebview:package-linux": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
"pywebview:package-win": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
"pywebview:package-mac": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
"fastlane:ios:beta": "cd ios && fastlane beta",
"fastlane:ios:release": "cd ios && fastlane release",
"fastlane:android:beta": "cd android && fastlane beta",
"fastlane:android:release": "cd android && fastlane release",
"electron:build-mac": "npm run build:electron-prod && electron-builder --mac",
"electron:build-mac-universal": "npm run build:electron-prod && electron-builder --mac --universal"
"electron:build-mac": "./scripts/build-electron-mac.sh",
"electron:build-mac-universal": "./scripts/build-electron-mac.sh universal"
},
"dependencies": {
"@capacitor-community/sqlite": "6.0.2",

6
requirements.txt

@ -1,6 +0,0 @@
eth_keys
pywebview
pyinstaller>=6.12.0
setuptools>=69.0.0 # Required for distutils for electron-builder on macOS
# For development
watchdog>=3.0.0 # For file watching support

285
scripts/README.md

@ -0,0 +1,285 @@
# TimeSafari Build Scripts
This directory contains unified build and test scripts for the TimeSafari application. All scripts use a common utilities library to eliminate redundancy and provide consistent logging, error handling, timing, and environment variable management.
## Architecture
### Common Utilities (`common.sh`)
The `common.sh` script provides shared functionality used by all build scripts:
- **Logging Functions**: `log_info`, `log_success`, `log_warn`, `log_error`, `log_debug`, `log_step`
- **Timing**: `measure_time` for execution time tracking
- **Headers/Footers**: `print_header`, `print_footer` for consistent output formatting
- **Validation**: `check_command`, `check_directory`, `check_file`, `check_venv`
- **Execution**: `safe_execute` for error-handled command execution
- **Utilities**: `get_git_hash`, `clean_build_artifacts`, `validate_env_vars`
- **Environment Management**: `setup_build_env`, `setup_app_directories`, `load_env_file`, `print_env_vars`
- **CLI**: `parse_args`, `print_usage` for command-line argument handling
### Environment Variable Management
All scripts automatically handle environment variables for different build types:
#### Build Types and Environment Variables
| Platform | Mode | PWA Enabled | Native Features | Build Script |
|----------|------|-------------|-----------------|--------------|
| `web` | web | true | false | `build-web.sh` |
| `capacitor` | capacitor | false | true | `build-capacitor.sh` |
| `electron` | electron | false | true | `build-electron.sh` |
#### Automatic Environment Setup
Each script automatically:
1. **Sets platform-specific variables** based on build type
2. **Gets git hash** for versioning (`VITE_GIT_HASH`)
3. **Creates application directories** (`~/.local/share/TimeSafari/timesafari`)
4. **Loads .env file** if it exists
5. **Validates required variables** when needed
#### Environment Functions
- `setup_build_env(build_type, production)` - Sets environment for specific build type
- `setup_app_directories()` - Creates necessary application directories
- `load_env_file(filename)` - Loads variables from .env file
- `print_env_vars(prefix)` - Displays current environment variables
- `validate_env_vars(var1, var2, ...)` - Validates required variables exist
### Script Structure
All scripts follow this unified pattern:
```bash
#!/bin/bash
# script-name.sh
# Author: Matthew Raymer
# Description: Brief description of what the script does
#
# Exit Codes: List of exit codes and their meanings
# Usage: ./scripts/script-name.sh [options]
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print header
print_header "Script Title"
log_info "Starting process at $(date)"
# Setup environment (automatic)
setup_build_env "build_type"
setup_app_directories
load_env_file ".env"
# Execute steps with safe_execute
safe_execute "Step description" "command to execute" || exit 1
# Print footer
print_footer "Script Title"
exit 0
```
## Available Scripts
### Test Scripts
- **`test-all.sh`**: Comprehensive test suite (prerequisites, build, web tests, mobile tests)
- **`test-mobile.sh`**: Mobile test suite (Capacitor build, Android tests, iOS tests)
- **`test-common.sh`**: Test script to verify common utilities work correctly
- **`test-env.sh`**: Test script to verify environment variable handling
### Build Scripts
- **`build-electron.sh`**: Complete Electron build process
- **`build-android.sh`**: Complete Android build process
- **`build-electron-linux.sh`**: Linux Electron packaging (AppImage, .deb)
- **`build-electron-mac.sh`**: macOS Electron packaging (standard, universal)
### Development Scripts
- **`electron-dev.sh`**: Electron development workflow
## Benefits of Unification
### Before (Redundant)
```bash
# Each script had 50+ lines of duplicate code:
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
# ... 40+ more lines of duplicate logging functions
log_info "Step 1/4: Doing something..."
if ! measure_time some_command; then
log_error "Step failed!"
exit 1
fi
# Manual environment variable setup
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
# ... more manual exports
```
### After (Unified)
```bash
# Each script is now ~20 lines of focused logic:
source "$(dirname "$0")/common.sh"
print_header "Script Title"
setup_build_env "electron" # Automatic environment setup
safe_execute "Step description" "some_command" || exit 1
print_footer "Script Title"
```
## Usage Examples
### Running Tests
```bash
# Run all tests
./scripts/test-all.sh
# Run mobile tests only
./scripts/test-mobile.sh
# Run with verbose logging
./scripts/test-all.sh --verbose
# Show environment variables
./scripts/test-env.sh --env
```
### Building Applications
```bash
# Build Electron
./scripts/build-electron.sh
# Build Android
./scripts/build-android.sh
# Build Linux package
./scripts/build-electron-linux.sh deb
# Build universal Mac package
./scripts/build-electron-mac.sh universal
# Show environment variables for build
./scripts/build-electron.sh --env
```
### Development Workflows
```bash
# Start Electron development
./scripts/electron-dev.sh
```
## Environment Variable Features
### Automatic Setup
All scripts automatically configure the correct environment variables for their build type:
```bash
# Electron builds automatically get:
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
export VITE_GIT_HASH=<git-hash>
# Production builds also get:
export NODE_ENV=production
```
### .env File Support
Scripts automatically load variables from `.env` files if they exist:
```bash
# .env file example:
VITE_API_URL=https://api.example.com
VITE_DEBUG=true
CUSTOM_VAR=value
```
### Environment Validation
Required environment variables can be validated:
```bash
# In your script
validate_env_vars "VITE_API_URL" "VITE_DEBUG" || exit 1
```
### Environment Inspection
View current environment variables with the `--env` flag:
```bash
./scripts/build-electron.sh --env
./scripts/test-env.sh --env
```
## Error Handling
All scripts use consistent error handling:
- **Exit Codes**: Each script documents specific exit codes
- **Safe Execution**: `safe_execute` provides timing and error handling
- **Graceful Failure**: Scripts stop on first error with clear messages
- **Logging**: All operations are logged with timestamps and colors
- **Environment Validation**: Required variables are checked before execution
## Testing
To verify the common utilities work correctly:
```bash
# Test all common functions
./scripts/test-common.sh
# Test environment variable handling
./scripts/test-env.sh
# Test with verbose logging
./scripts/test-env.sh --verbose
```
## Maintenance
### Adding New Scripts
1. Create new script following the unified pattern
2. Source `common.sh` at the top
3. Use `setup_build_env()` for environment setup
4. Use `safe_execute` for command execution
5. Document exit codes and usage
6. Make executable: `chmod +x scripts/new-script.sh`
### Modifying Common Utilities
1. Update `common.sh` with new functions
2. Export new functions with `export -f function_name`
3. Update this README if adding new categories
4. Test with `test-common.sh` and `test-env.sh`
### Adding New Build Types
1. Add new case to `setup_build_env()` function
2. Define appropriate environment variables
3. Update this README with new build type
4. Test with `test-env.sh`
## Security Considerations
- All scripts use `set -e` for immediate failure on errors
- Commands are executed through `safe_execute` for consistent error handling
- No direct execution of user input without validation
- Environment variables are validated when required
- .env files are loaded safely with proper parsing
## Performance
- Common utilities are sourced once per script execution
- Timing information is automatically collected for all operations
- Build artifacts are cleaned up automatically
- No redundant command execution or file operations
- Environment variables are set efficiently with minimal overhead

71
scripts/build-android.sh

@ -0,0 +1,71 @@
#!/bin/bash
# build-android.sh
# Author: Matthew Raymer
# Description: Android build script for TimeSafari application
# This script handles the complete Android build process including cleanup,
# web build, Capacitor build, Gradle build, and Android Studio launch.
#
# Exit Codes:
# 1 - Android cleanup failed
# 2 - Web build failed
# 3 - Capacitor build failed
# 4 - Gradle clean failed
# 5 - Gradle assemble failed
# 6 - Capacitor sync failed
# 7 - Asset generation failed
# 8 - Android Studio launch failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print build header
print_header "TimeSafari Android Build Process"
log_info "Starting Android build process at $(date)"
# Setup environment for Capacitor build
setup_build_env "capacitor"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Clean Android app
safe_execute "Cleaning Android app" "npm run clean:android" || exit 1
# Step 2: Clean dist directory
log_info "Cleaning dist directory..."
clean_build_artifacts "dist"
# Step 3: Build web assets
safe_execute "Building web assets" "npm run build:web" || exit 2
# Step 4: Build Capacitor version
safe_execute "Building Capacitor version" "npm run build:capacitor" || exit 3
# Step 5: Clean Gradle build
safe_execute "Cleaning Gradle build" "cd android && ./gradlew clean && cd .." || exit 4
# Step 6: Assemble debug build
safe_execute "Assembling debug build" "cd android && ./gradlew assembleDebug && cd .." || exit 5
# Step 7: Sync with Capacitor
safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6
# Step 8: Generate assets and open Android Studio
safe_execute "Generating assets" "npx capacitor-assets generate --android" || exit 7
safe_execute "Opening Android Studio" "npx cap open android" || exit 8
# Print build summary
log_success "Android build completed successfully!"
print_footer "Android Build"
# Exit with success
exit 0

82
scripts/build-electron-linux.sh

@ -0,0 +1,82 @@
#!/bin/bash
# build-electron-linux.sh
# Author: Matthew Raymer
# Description: Electron Linux build script for TimeSafari application
# This script builds Electron packages for Linux with support for different formats.
#
# Usage: ./scripts/build-electron-linux.sh [deb|prod]
# - No argument: Builds AppImage
# - deb: Builds .deb package
# - prod: Builds production AppImage
#
# Exit Codes:
# 1 - Build failed
# 2 - Invalid argument
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Parse build type argument
BUILD_TYPE=${1:-"appimage"}
PRODUCTION=false
case $BUILD_TYPE in
"deb")
BUILD_TARGET="deb"
log_info "Building .deb package"
;;
"prod")
BUILD_TARGET="AppImage"
PRODUCTION=true
log_info "Building production AppImage"
;;
"appimage"|"")
BUILD_TARGET="AppImage"
log_info "Building AppImage"
;;
*)
log_error "Invalid build type: $BUILD_TYPE"
log_error "Usage: $0 [deb|prod]"
exit 2
;;
esac
# Print build header
print_header "TimeSafari Electron Linux Build"
log_info "Starting Linux build process at $(date)"
log_info "Build type: $BUILD_TYPE"
log_info "Build target: $BUILD_TARGET"
log_info "Production mode: $PRODUCTION"
# Setup environment for Electron build
setup_build_env "electron" "$PRODUCTION"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Build Electron application
if [ "$PRODUCTION" = true ]; then
safe_execute "Building production Electron application" "npm run build:electron-prod" || exit 1
else
safe_execute "Building Electron application" "npm run build:electron" || exit 1
fi
# Step 2: Build package
safe_execute "Building Linux package" "npx electron-builder --linux $BUILD_TARGET" || exit 1
# Print build summary
log_success "Linux build completed successfully!"
log_info "Package type: $BUILD_TARGET"
print_footer "Linux Build"
# Exit with success
exit 0

74
scripts/build-electron-mac.sh

@ -0,0 +1,74 @@
#!/bin/bash
# build-electron-mac.sh
# Author: Matthew Raymer
# Description: Electron Mac build script for TimeSafari application
# This script builds Electron packages for macOS with support for universal builds.
#
# Usage: ./scripts/build-electron-mac.sh [universal]
# - No argument: Builds standard Mac package
# - universal: Builds universal Mac package (Intel + Apple Silicon)
#
# Exit Codes:
# 1 - Build failed
# 2 - Invalid argument
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Parse build type argument
BUILD_TYPE=${1:-"standard"}
UNIVERSAL=false
case $BUILD_TYPE in
"universal")
UNIVERSAL=true
log_info "Building universal Mac package (Intel + Apple Silicon)"
;;
"standard"|"")
log_info "Building standard Mac package"
;;
*)
log_error "Invalid build type: $BUILD_TYPE"
log_error "Usage: $0 [universal]"
exit 2
;;
esac
# Print build header
print_header "TimeSafari Electron Mac Build"
log_info "Starting Mac build process at $(date)"
log_info "Build type: $BUILD_TYPE"
log_info "Universal build: $UNIVERSAL"
# Setup environment for Electron build (production mode for packaging)
setup_build_env "electron" "true"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Build Electron application
safe_execute "Building Electron application" "npm run build:electron-prod" || exit 1
# Step 2: Build package
if [ "$UNIVERSAL" = true ]; then
safe_execute "Building universal Mac package" "npx electron-builder --mac --universal" || exit 1
else
safe_execute "Building Mac package" "npx electron-builder --mac" || exit 1
fi
# Print build summary
log_success "Mac build completed successfully!"
log_info "Package type: $([ "$UNIVERSAL" = true ] && echo "Universal" || echo "Standard")"
print_footer "Mac Build"
# Exit with success
exit 0

53
scripts/build-electron.sh

@ -0,0 +1,53 @@
#!/bin/bash
# build-electron.sh
# Author: Matthew Raymer
# Description: Electron build script for TimeSafari application
# This script handles the complete Electron build process including cleanup,
# TypeScript compilation, Vite build, and Electron-specific setup.
#
# Exit Codes:
# 1 - Cleanup failed
# 2 - TypeScript compilation failed
# 3 - Vite build failed
# 4 - Electron build script failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print build header
print_header "TimeSafari Electron Build Process"
log_info "Starting Electron build process at $(date)"
# Setup environment for Electron build
setup_build_env "electron"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Clean previous builds
safe_execute "Cleaning previous builds" "npm run clean:electron" || exit 1
# Step 2: Compile TypeScript for Electron
safe_execute "Compiling TypeScript for Electron" "npx tsc -p tsconfig.electron.json" || exit 2
# Step 3: Build with Vite
safe_execute "Building with Vite" "npx vite build --config vite.config.electron.mts" || exit 3
# Step 4: Run Electron build script
safe_execute "Running Electron build script" "node scripts/build-electron.js" || exit 4
# Print build summary
log_success "Electron build completed successfully!"
print_footer "Electron Build"
# Exit with success
exit 0

331
scripts/common.sh

@ -0,0 +1,331 @@
#!/bin/bash
# common.sh
# Author: Matthew Raymer
# Description: Common utilities and functions for TimeSafari build scripts
# This script provides shared logging, timing, and utility functions
# that can be sourced by other build scripts to eliminate redundancy.
#
# Usage: source ./scripts/common.sh
#
# Provides:
# - Color constants
# - Logging functions (log_info, log_success, log_warn, log_error)
# - Timing function (measure_time)
# - Common utility functions
# - Environment variable management
# ANSI color codes for better output formatting
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
log_debug() {
echo -e "${PURPLE}[$(date '+%Y-%m-%d %H:%M:%S')] [DEBUG]${NC} $1"
}
log_step() {
echo -e "${CYAN}[$(date '+%Y-%m-%d %H:%M:%S')] [STEP]${NC} $1"
}
# Function to measure and log execution time
measure_time() {
local start_time=$(date +%s)
"$@"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log_success "Completed in ${duration} seconds"
}
# Function to print section headers
print_header() {
local title="$1"
echo -e "\n${BLUE}=== $title ===${NC}\n"
}
print_footer() {
local title="$1"
echo -e "\n${GREEN}=== $title Complete ===${NC}\n"
}
# Function to check if a command exists
check_command() {
if ! command -v "$1" &> /dev/null; then
log_error "$1 is required but not installed."
return 1
fi
log_debug "Found $1: $(command -v "$1")"
return 0
}
# Function to check if a directory exists
check_directory() {
if [ ! -d "$1" ]; then
log_error "Directory not found: $1"
return 1
fi
log_debug "Directory exists: $1"
return 0
}
# Function to check if a file exists
check_file() {
if [ ! -f "$1" ]; then
log_error "File not found: $1"
return 1
fi
log_debug "File exists: $1"
return 0
}
# Function to safely execute a command with error handling
safe_execute() {
local step_name="$1"
local command="$2"
log_step "$step_name"
if ! measure_time eval "$command"; then
log_error "$step_name failed!"
return 1
fi
return 0
}
# Function to check virtual environment for Python scripts
check_venv() {
if [ ! -d ".venv" ]; then
log_error "Virtual environment not found. Please create it first:"
log_error "python -m venv .venv"
log_error "source .venv/bin/activate"
log_error "pip install -r requirements.txt"
return 1
fi
log_debug "Virtual environment found: .venv"
return 0
}
# Function to get git hash for versioning
get_git_hash() {
if command -v git &> /dev/null; then
git log -1 --pretty=format:%h 2>/dev/null || echo "unknown"
else
echo "unknown"
fi
}
# Function to clean build artifacts
clean_build_artifacts() {
local artifacts=("$@")
for artifact in "${artifacts[@]}"; do
if [ -e "$artifact" ]; then
log_info "Cleaning $artifact"
rm -rf "$artifact"
fi
done
}
# Function to validate environment variables
validate_env_vars() {
local required_vars=("$@")
local missing_vars=()
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
missing_vars+=("$var")
fi
done
if [ ${#missing_vars[@]} -gt 0 ]; then
log_error "Missing required environment variables: ${missing_vars[*]}"
return 1
fi
return 0
}
# Function to set environment variables for different build types
setup_build_env() {
local build_type="$1"
local production="${2:-false}"
log_info "Setting up environment for $build_type build"
# Get git hash for versioning
local git_hash=$(get_git_hash)
export VITE_GIT_HASH="$git_hash"
log_debug "Set VITE_GIT_HASH=$git_hash"
case $build_type in
"electron")
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
if [ "$production" = true ]; then
export NODE_ENV=production
log_debug "Set production mode for Electron"
fi
;;
"capacitor")
export VITE_PLATFORM=capacitor
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
;;
"web")
export VITE_PLATFORM=web
export VITE_PWA_ENABLED=true
export VITE_DISABLE_PWA=false
export DEBUG_MIGRATIONS=0
;;
*)
log_warn "Unknown build type: $build_type, using default environment"
export VITE_PLATFORM=web
export VITE_PWA_ENABLED=true
export VITE_DISABLE_PWA=false
export DEBUG_MIGRATIONS=0
;;
esac
# Log environment setup
log_debug "Environment variables set:"
log_debug " VITE_PLATFORM=$VITE_PLATFORM"
log_debug " VITE_PWA_ENABLED=$VITE_PWA_ENABLED"
log_debug " VITE_DISABLE_PWA=$VITE_DISABLE_PWA"
log_debug " DEBUG_MIGRATIONS=$DEBUG_MIGRATIONS"
if [ -n "$NODE_ENV" ]; then
log_debug " NODE_ENV=$NODE_ENV"
fi
}
# Function to create application directories
setup_app_directories() {
log_info "Setting up application directories..."
# Create TimeSafari data directory
mkdir -p ~/.local/share/TimeSafari/timesafari
# Create build directories if they don't exist
mkdir -p dist
mkdir -p dist-electron
log_debug "Application directories created"
}
# Function to load environment from .env file if it exists
load_env_file() {
local env_file="$1"
if [ -f "$env_file" ]; then
log_info "Loading environment from $env_file"
# Export variables from .env file (simple key=value format)
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ $key =~ ^#.*$ ]] && continue
[[ -z $key ]] && continue
# Remove quotes from value if present
value=$(echo "$value" | sed 's/^["'\'']//;s/["'\'']$//')
export "$key=$value"
log_debug "Loaded: $key=$value"
done < "$env_file"
else
log_debug "No $env_file file found"
fi
}
# Function to print current environment variables
print_env_vars() {
local prefix="$1"
if [ -n "$prefix" ]; then
log_info "Environment variables with prefix '$prefix':"
env | grep "^$prefix" | sort | while read -r line; do
log_debug " $line"
done
else
log_info "Current environment variables:"
env | sort | while read -r line; do
log_debug " $line"
done
fi
}
# Function to print script usage
print_usage() {
local script_name="$1"
local usage_text="$2"
echo "Usage: $script_name $usage_text"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --verbose Enable verbose logging"
echo " -e, --env Show environment variables"
echo ""
}
# Function to parse command line arguments
parse_args() {
local args=("$@")
local verbose=false
local show_env=false
for arg in "${args[@]}"; do
case $arg in
-h|--help)
print_usage "$0" "[options]"
exit 0
;;
-v|--verbose)
verbose=true
;;
-e|--env)
show_env=true
;;
*)
# Handle other arguments in child scripts
;;
esac
done
if [ "$verbose" = true ]; then
# Enable debug logging
set -x
fi
if [ "$show_env" = true ]; then
print_env_vars "VITE_"
exit 0
fi
}
# Export functions for use in child scripts
export -f log_info log_success log_warn log_error log_debug log_step
export -f measure_time print_header print_footer
export -f check_command check_directory check_file
export -f safe_execute check_venv get_git_hash
export -f clean_build_artifacts validate_env_vars
export -f setup_build_env setup_app_directories load_env_file print_env_vars
export -f print_usage parse_args

44
scripts/electron-dev.sh

@ -0,0 +1,44 @@
#!/bin/bash
# electron-dev.sh
# Author: Matthew Raymer
# Description: Electron development script for TimeSafari application
# This script builds the application and starts Electron for development.
#
# Exit Codes:
# 1 - Build failed
# 2 - Electron start failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print dev header
print_header "TimeSafari Electron Development"
log_info "Starting Electron development at $(date)"
# Setup environment for Electron development
setup_build_env "electron"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Build the application
safe_execute "Building application" "npm run build" || exit 1
# Step 2: Start Electron
safe_execute "Starting Electron" "electron ." || exit 2
# Print dev summary
log_success "Electron development session ended"
print_footer "Electron Development"
# Exit with success
exit 0

44
scripts/test-all.sh

@ -0,0 +1,44 @@
#!/bin/bash
# test-all.sh
# Author: Matthew Raymer
# Description: Comprehensive test suite for TimeSafari application
# This script runs all tests including prerequisites, web tests, and mobile tests
# with proper error handling and logging.
#
# Exit Codes:
# 1 - Prerequisites check failed
# 2 - Build failed
# 3 - Web tests failed
# 4 - Mobile tests failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print test header
print_header "TimeSafari Test Suite"
log_info "Starting comprehensive test suite at $(date)"
# Step 1: Check prerequisites
safe_execute "Checking prerequisites" "npm run test:prerequisites" || exit 1
# Step 2: Build the application
safe_execute "Building application" "npm run build" || exit 2
# Step 3: Run web tests
safe_execute "Running web tests" "npm run test:web" || exit 3
# Step 4: Run mobile tests
safe_execute "Running mobile tests" "npm run test:mobile" || exit 4
# Print test summary
log_success "All tests completed successfully!"
print_footer "Test Suite"
# Exit with success
exit 0

74
scripts/test-common.sh

@ -0,0 +1,74 @@
#!/bin/bash
# test-common.sh
# Author: Matthew Raymer
# Description: Test script to verify common utilities work correctly
# This script tests the common.sh utilities to ensure they function properly.
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print test header
print_header "Common Utilities Test"
log_info "Testing common utilities at $(date)"
# Test logging functions
log_info "Testing info logging"
log_success "Testing success logging"
log_warn "Testing warning logging"
log_error "Testing error logging (this is expected)"
log_debug "Testing debug logging"
log_step "Testing step logging"
# Test timing function
log_info "Testing timing function..."
measure_time sleep 1
# Test command checking
log_info "Testing command checking..."
if check_command "echo"; then
log_success "echo command found"
else
log_error "echo command not found"
fi
# Test directory checking
log_info "Testing directory checking..."
if check_directory "scripts"; then
log_success "scripts directory found"
else
log_error "scripts directory not found"
fi
# Test file checking
log_info "Testing file checking..."
if check_file "scripts/common.sh"; then
log_success "common.sh file found"
else
log_error "common.sh file not found"
fi
# Test git hash function
log_info "Testing git hash function..."
GIT_HASH=$(get_git_hash)
log_info "Git hash: $GIT_HASH"
# Test safe execute
log_info "Testing safe execute..."
safe_execute "Testing safe execute" "echo 'Hello from safe_execute'"
# Test build artifact cleaning
log_info "Testing build artifact cleaning..."
clean_build_artifacts "test-file-1" "test-file-2"
# Print test summary
log_success "All common utilities tests completed successfully!"
print_footer "Common Utilities Test"
# Exit with success
exit 0

59
scripts/test-env.sh

@ -0,0 +1,59 @@
#!/bin/bash
# test-env.sh
# Author: Matthew Raymer
# Description: Test script to verify environment variable handling
# This script tests the environment variable setup functions.
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print test header
print_header "Environment Variable Test"
log_info "Testing environment variable handling at $(date)"
# Test 1: Electron environment
log_info "Test 1: Setting up Electron environment..."
setup_build_env "electron"
print_env_vars "VITE_"
# Test 2: Capacitor environment
log_info "Test 2: Setting up Capacitor environment..."
setup_build_env "capacitor"
print_env_vars "VITE_"
# Test 3: Web environment
log_info "Test 3: Setting up Web environment..."
setup_build_env "web"
print_env_vars "VITE_"
# Test 4: Production Electron environment
log_info "Test 4: Setting up Production Electron environment..."
setup_build_env "electron" "true"
print_env_vars "VITE_"
print_env_vars "NODE_ENV"
# Test 5: Application directories
log_info "Test 5: Setting up application directories..."
setup_app_directories
# Test 6: Load .env file (if it exists)
log_info "Test 6: Loading .env file..."
load_env_file ".env"
# Test 7: Git hash
log_info "Test 7: Getting git hash..."
GIT_HASH=$(get_git_hash)
log_info "Git hash: $GIT_HASH"
# Print test summary
log_success "All environment variable tests completed successfully!"
print_footer "Environment Variable Test"
# Exit with success
exit 0

40
scripts/test-mobile.sh

@ -0,0 +1,40 @@
#!/bin/bash
# test-mobile.sh
# Author: Matthew Raymer
# Description: Mobile test suite for TimeSafari application
# This script builds the Capacitor version and runs Android and iOS tests
# with proper error handling and logging.
#
# Exit Codes:
# 1 - Capacitor build failed
# 2 - Android tests failed
# 3 - iOS tests failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print test header
print_header "TimeSafari Mobile Test Suite"
log_info "Starting mobile test suite at $(date)"
# Step 1: Build Capacitor version
safe_execute "Building Capacitor version" "npm run build:capacitor" || exit 1
# Step 2: Run Android tests
safe_execute "Running Android tests" "npm run test:android" || exit 2
# Step 3: Run iOS tests
safe_execute "Running iOS tests" "npm run test:ios" || exit 3
# Print test summary
log_success "Mobile test suite completed successfully!"
print_footer "Mobile Test Suite"
# Exit with success
exit 0

4
src/main.pywebview.ts

@ -1,4 +0,0 @@
import { initializeApp } from "./main.common";
const app = initializeApp();
app.mount("#app");

59
src/pywebview/main.py

@ -1,59 +0,0 @@
import webview
import os
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
import threading
def get_dist_path():
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
dist_path = os.path.join(base_path, 'dist')
print(f"Dist path: {dist_path}")
return dist_path
class CustomHandler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
dist_dir = get_dist_path()
print(f"Serving from directory: {dist_dir}")
super().__init__(*args, directory=dist_dir, **kwargs)
def log_message(self, format, *args):
# Override to show more detailed logging
print(f"Request: {format%args}")
if hasattr(self, 'path'):
print(f"Requested path: {self.path}")
full_path = os.path.join(self.directory, self.path.lstrip('/'))
print(f"Full path: {full_path}")
print(f"File exists: {os.path.exists(full_path)}")
if self.path.endswith('.html'):
print(f"HTML content: {open(full_path).read()[:200]}...")
def run_server(port=8000):
server_address = ('localhost', port)
httpd = HTTPServer(server_address, CustomHandler)
print(f"Serving files from {get_dist_path()} at http://localhost:{port}")
httpd.serve_forever()
def main():
dist_path = get_dist_path()
# Start local server
server_thread = threading.Thread(target=run_server)
server_thread.daemon = True
server_thread.start()
# Create window using local server
window = webview.create_window(
'TimeSafari',
url='http://localhost:8000',
width=1200,
height=800
)
webview.start(debug=True)
if __name__ == '__main__':
main()

5
src/services/PlatformServiceFactory.ts

@ -2,7 +2,6 @@ import { PlatformService } from "./PlatformService";
import { WebPlatformService } from "./platforms/WebPlatformService";
import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService";
import { ElectronPlatformService } from "./platforms/ElectronPlatformService";
import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService";
/**
* Factory class for creating platform-specific service implementations.
@ -12,7 +11,6 @@ import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService";
* environment variable. Supported platforms are:
* - capacitor: Mobile platform using Capacitor
* - electron: Desktop platform using Electron
* - pywebview: Python WebView implementation
* - web: Default web platform (fallback)
*
* @example
@ -44,9 +42,6 @@ export class PlatformServiceFactory {
case "electron":
PlatformServiceFactory.instance = new ElectronPlatformService();
break;
case "pywebview":
PlatformServiceFactory.instance = new PyWebViewPlatformService();
break;
case "web":
default:
PlatformServiceFactory.instance = new WebPlatformService();

135
src/services/platforms/PyWebViewPlatformService.ts

@ -1,135 +0,0 @@
import {
ImageResult,
PlatformService,
PlatformCapabilities,
} from "../PlatformService";
import { logger } from "../../utils/logger";
import { QueryExecResult } from "@/interfaces/database";
/**
* Platform service implementation for PyWebView platform.
* Note: This is a placeholder implementation with most methods currently unimplemented.
* Implements the PlatformService interface but throws "Not implemented" errors for most operations.
*
* @remarks
* This service is intended for Python-based desktop applications using pywebview.
* Future implementations should provide:
* - Integration with Python backend file operations
* - System camera access through Python
* - Native system dialogs via pywebview
* - Python-JavaScript bridge functionality
*/
export class PyWebViewPlatformService implements PlatformService {
/**
* Gets the capabilities of the PyWebView platform
* @returns Platform capabilities object
*/
getCapabilities(): PlatformCapabilities {
return {
hasFileSystem: false, // Not implemented yet
hasCamera: false, // Not implemented yet
isMobile: false,
isIOS: false,
hasFileDownload: false, // Not implemented yet
needsFileHandlingInstructions: false,
};
}
/**
* Reads a file using the Python backend.
* @param _path - Path to the file to read
* @returns Promise that should resolve to file contents
* @throws Error with "Not implemented" message
* @todo Implement file reading through pywebview's Python-JavaScript bridge
*/
async readFile(_path: string): Promise<string> {
throw new Error("Not implemented");
}
/**
* Writes content to a file using the Python backend.
* @param _path - Path where to write the file
* @param _content - Content to write to the file
* @throws Error with "Not implemented" message
* @todo Implement file writing through pywebview's Python-JavaScript bridge
*/
async writeFile(_path: string, _content: string): Promise<void> {
throw new Error("Not implemented");
}
/**
* Deletes a file using the Python backend.
* @param _path - Path to the file to delete
* @throws Error with "Not implemented" message
* @todo Implement file deletion through pywebview's Python-JavaScript bridge
*/
async deleteFile(_path: string): Promise<void> {
throw new Error("Not implemented");
}
/**
* Lists files in the specified directory using the Python backend.
* @param _directory - Path to the directory to list
* @returns Promise that should resolve to array of filenames
* @throws Error with "Not implemented" message
* @todo Implement directory listing through pywebview's Python-JavaScript bridge
*/
async listFiles(_directory: string): Promise<string[]> {
throw new Error("Not implemented");
}
/**
* Should open system camera through Python backend.
* @returns Promise that should resolve to captured image data
* @throws Error with "Not implemented" message
* @todo Implement camera access using Python's camera libraries
*/
async takePicture(): Promise<ImageResult> {
logger.error("takePicture not implemented in PyWebView platform");
throw new Error("Not implemented");
}
/**
* Should open system file picker through pywebview.
* @returns Promise that should resolve to selected image data
* @throws Error with "Not implemented" message
* @todo Implement file picker using pywebview's file dialog API
*/
async pickImage(): Promise<ImageResult> {
logger.error("pickImage not implemented in PyWebView platform");
throw new Error("Not implemented");
}
/**
* Should handle deep link URLs through the Python backend.
* @param _url - The deep link URL to handle
* @throws Error with "Not implemented" message
* @todo Implement deep link handling using Python's URL handling capabilities
*/
async handleDeepLink(_url: string): Promise<void> {
logger.error("handleDeepLink not implemented in PyWebView platform");
throw new Error("Not implemented");
}
dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
throw new Error("Not implemented for " + sql + " with params " + params);
}
dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
throw new Error("Not implemented for " + sql + " with params " + params);
}
/**
* Should write and share a file using the Python backend.
* @param _fileName - Name of the file to write and share
* @param _content - Content to write to the file
* @throws Error with "Not implemented" message
* @todo Implement file writing and sharing through pywebview's Python-JavaScript bridge
*/
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
logger.error("writeAndShareFile not implemented in PyWebView platform");
throw new Error("Not implemented");
}
}

8
src/services/platforms/WebPlatformService.ts

@ -324,14 +324,6 @@ export class WebPlatformService implements PlatformService {
return false;
}
/**
* Checks if running on PyWebView platform.
* @returns false, as this is not PyWebView
*/
isPyWebView(): boolean {
return false;
}
/**
* Checks if running on web platform.
* @returns true, as this is the web implementation

3
src/views/HomeView.vue

@ -255,7 +255,7 @@ Raymer * @version 1.0.0 */
<button class="text-blue-500">View All New Activity For You</button>
</div>
</div>
<div>{{ apiServer }}</div>
<InfiniteScroll @reached-bottom="loadMoreGives">
<ul id="listLatestActivity" class="space-y-4">
<ActivityListItem
@ -345,6 +345,7 @@ import { logger } from "../utils/logger";
import { GiveRecordWithContactInfo } from "../interfaces/give";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import * as Package from "../../package.json";
import { id_ce_authorityKeyIdentifier } from "node_modules/@simplewebauthn/server/esm/deps";
interface Claim {
claim?: Claim; // For nested claims in Verifiable Credentials

6
vite.config.common.mts

@ -15,19 +15,19 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
const appConfig = await loadAppConfig();
const isElectron = mode === "electron";
const isCapacitor = mode === "capacitor";
const isPyWebView = mode === "pywebview";
const isNative = isElectron || isCapacitor;
// Explicitly set platform and disable PWA for Electron
process.env.VITE_PLATFORM = mode;
process.env.VITE_PWA_ENABLED = isElectron ? 'false' : 'true';
process.env.VITE_DISABLE_PWA = isElectron ? 'true' : 'false';
if (isElectron || isPyWebView || isCapacitor) {
if (isElectron || isCapacitor) {
process.env.VITE_PWA_ENABLED = 'false';
}
return {
base: isElectron || isPyWebView ? "./" : "/",
base: isElectron ? "./" : "/",
plugins: [vue()],
server: {
port: parseInt(process.env.VITE_PORT || "8080"),

4
vite.config.pywebview.mts

@ -1,4 +0,0 @@
import { defineConfig } from "vite";
import { createBuildConfig } from "./vite.config.common.mts";
export default defineConfig(async () => createBuildConfig('pywebview'));
Loading…
Cancel
Save