forked from jsnbuchanan/crowd-funder-for-time-pwa
Merge pull request 'fix: Fix onboard-meeting-members deep link with groupId.' (#172) from fix-deep-link into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#172
This commit is contained in:
@@ -46,27 +46,18 @@ Practical rules to keep TypeScript strict and predictable. Minimize exceptions.
|
||||
### Core Type Safety Rules
|
||||
|
||||
- **No `any` Types**: Use explicit types or `unknown` with proper type guards
|
||||
- **Error Handling Uses Guards**: Implement and reuse type guards from
|
||||
`src/interfaces/**`
|
||||
- **Dynamic Property Access**: Use `keyof` + `in` checks for type-safe
|
||||
property access
|
||||
- **Error Handling Uses Guards**: Implement and reuse type guards from `src/interfaces/**`
|
||||
- **Dynamic Property Access**: Use `keyof` + `in` checks for type-safe property access
|
||||
|
||||
### Type Guard Patterns
|
||||
|
||||
- **API Errors**: Use `isApiError(error)` guards for API error handling
|
||||
- **Database Errors**: Use `isDatabaseError(error)` guards for database
|
||||
operations
|
||||
- **Axios Errors**: Implement `isAxiosError(error)` guards for HTTP error
|
||||
handling
|
||||
- **Database Errors**: Use `isDatabaseError(error)` guards for database operations
|
||||
- **Axios Errors**: Implement `isAxiosError(error)` guards for HTTP error handling
|
||||
|
||||
### Implementation Guidelines
|
||||
|
||||
- **Avoid Type Assertions**: Replace `as any` with proper type guards and
|
||||
interfaces
|
||||
- **Narrow Types Properly**: Use type guards to narrow `unknown` types
|
||||
safely
|
||||
- **Document Type Decisions**: Explain complex type structures and their
|
||||
purpose
|
||||
- **Avoid Type Assertions**: Replace `as any` with proper type guards and interfaces
|
||||
- **Narrow Types Properly**: Use type guards to narrow `unknown` types safely
|
||||
- **Document Type Decisions**: Explain complex type structures and their purpose
|
||||
|
||||
## Minimal Special Cases (document in PR when used)
|
||||
|
||||
|
||||
@@ -171,10 +171,55 @@ debugging, architecture decisions, and testing.
|
||||
- [ ] Environment impact assessed for team members
|
||||
- [ ] Pre-build validation implemented where appropriate
|
||||
|
||||
---
|
||||
## Additional Core Principles
|
||||
|
||||
**Status**: Active development guidelines
|
||||
**Priority**: High
|
||||
**Estimated Effort**: Ongoing
|
||||
**Dependencies**: base_context.mdc, research_diagnostic.mdc
|
||||
**Stakeholders**: Development team
|
||||
### 4. Dependency Management & Environment Validation
|
||||
- **Pre-build Validation**: Always validate critical dependencies before executing build scripts
|
||||
- **Environment Consistency**: Ensure team members have identical development environments
|
||||
- **Dependency Verification**: Check that required packages are installed and accessible
|
||||
- **Path Resolution**: Use `npx` for local dependencies to avoid PATH issues
|
||||
|
||||
## Additional Required Workflows
|
||||
|
||||
### Dependency Validation (Before Proposing Changes)
|
||||
- [ ] **Dependency Validation**: Verify all required dependencies are available and accessible
|
||||
|
||||
### Environment Impact Assessment (During Solution Design)
|
||||
- [ ] **Environment Impact**: Assess how changes affect team member setups
|
||||
|
||||
## Additional Competence Hooks
|
||||
|
||||
### Dependency & Environment Management
|
||||
- **"What dependencies does this feature require and are they properly declared?"**
|
||||
- **"How will this change affect team member development environments?"**
|
||||
- **"What validation can we add to catch dependency issues early?"**
|
||||
|
||||
## Dependency Management Best Practices
|
||||
|
||||
### Pre-build Validation
|
||||
- **Check Critical Dependencies**: Validate essential tools before executing build scripts
|
||||
- **Use npx for Local Dependencies**: Prefer `npx tsx` over direct `tsx` to avoid PATH issues
|
||||
- **Environment Consistency**: Ensure all team members have identical dependency versions
|
||||
|
||||
### Common Pitfalls
|
||||
- **Missing npm install**: Team members cloning without running `npm install`
|
||||
- **PATH Issues**: Direct command execution vs. npm script execution differences
|
||||
- **Version Mismatches**: Different Node.js/npm versions across team members
|
||||
|
||||
### Validation Strategies
|
||||
- **Dependency Check Scripts**: Implement pre-build validation for critical dependencies
|
||||
- **Environment Requirements**: Document and enforce minimum Node.js/npm versions
|
||||
- **Onboarding Checklist**: Standardize team member setup procedures
|
||||
|
||||
### Error Messages and Guidance
|
||||
- **Specific Error Context**: Provide clear guidance when dependency issues occur
|
||||
- **Actionable Solutions**: Direct users to specific commands (`npm install`, `npm run check:dependencies`)
|
||||
- **Environment Diagnostics**: Implement comprehensive environment validation tools
|
||||
|
||||
### Build Script Enhancements
|
||||
- **Early Validation**: Check dependencies before starting build processes
|
||||
- **Graceful Degradation**: Continue builds when possible but warn about issues
|
||||
- **Helpful Tips**: Remind users about dependency management best practices
|
||||
|
||||
- **Narrow Types Properly**: Use type guards to narrow `unknown` types safely
|
||||
- **Document Type Decisions**: Explain complex type structures and their purpose
|
||||
|
||||
65
BUILDING.md
65
BUILDING.md
@@ -1017,47 +1017,27 @@ If you need to build manually or want to understand the individual steps:
|
||||
export GEM_PATH=$shortened_path
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
# It makes no sense why capacitor-assets will not run without these but it actually changes the contents.
|
||||
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
|
||||
npx capacitor-assets generate --ios
|
||||
```
|
||||
|
||||
4. Bump the version to match Android & package.json:
|
||||
1. Bump the version in package.json, then here.
|
||||
|
||||
```
|
||||
cd ios/App && xcrun agvtool new-version 39 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.6;/g" App.xcodeproj/project.pbxproj && cd -
|
||||
cd ios/App && xcrun agvtool new-version 40 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.7;/g" App.xcodeproj/project.pbxproj && cd -
|
||||
# Unfortunately this edits Info.plist directly.
|
||||
#xcrun agvtool new-marketing-version 0.4.5
|
||||
```
|
||||
|
||||
5. Open the project in Xcode:
|
||||
2. Build.
|
||||
|
||||
Here's prod. Also available: test, dev
|
||||
|
||||
```bash
|
||||
npx cap open ios
|
||||
npm run build:ios:prod
|
||||
```
|
||||
|
||||
6. Use Xcode to build and run on simulator or device.
|
||||
3.1. Use Xcode to build and run on simulator or device.
|
||||
|
||||
* Select Product -> Destination with some Simulator version. Then click the run arrow.
|
||||
|
||||
7. Release
|
||||
3.2. Use Xcode to release.
|
||||
|
||||
* Someday: Under "General" we want to rename a bunch of things to "Time Safari"
|
||||
* Choose Product -> Destination -> Any iOS Device
|
||||
@@ -1125,35 +1105,28 @@ The recommended way to build for Android is using the automated build script:
|
||||
|
||||
#### Manual Build Process
|
||||
|
||||
1. Build the web assets:
|
||||
1. Bump the version in package.json, then here: android/app/build.gradle
|
||||
|
||||
```bash
|
||||
rm -rf dist
|
||||
npm run build:web
|
||||
npm run build:capacitor
|
||||
perl -p -i -e 's/versionCode .*/versionCode 40/g' android/app/build.gradle
|
||||
perl -p -i -e 's/versionName .*/versionName "1.0.7"/g' android/app/build.gradle
|
||||
```
|
||||
|
||||
2. Update Android project with latest build:
|
||||
2. Build.
|
||||
|
||||
Here's prod. Also available: test, dev
|
||||
|
||||
```bash
|
||||
npx cap sync android
|
||||
npm run build:android:prod
|
||||
```
|
||||
|
||||
3. Copy the assets
|
||||
|
||||
```bash
|
||||
npx capacitor-assets generate --android
|
||||
```
|
||||
|
||||
4. Bump version to match iOS & package.json: android/app/build.gradle
|
||||
|
||||
5. Open the project in Android Studio:
|
||||
3. Open the project in Android Studio:
|
||||
|
||||
```bash
|
||||
npx cap open android
|
||||
```
|
||||
|
||||
6. Use Android Studio to build and run on emulator or device.
|
||||
4. Use Android Studio to build and run on emulator or device.
|
||||
|
||||
## Android Build from the console
|
||||
|
||||
@@ -1186,9 +1159,9 @@ cd -
|
||||
|
||||
At play.google.com/console:
|
||||
|
||||
- Go to the Testing Track (eg. Closed).
|
||||
- Go to Production or the Closed Testing and either Create Track or Manage Track.
|
||||
- Click "Create new release".
|
||||
- Upload the `aab` file.
|
||||
- Upload the `aab` file from: app/build/outputs/bundle/release/app-release.aab
|
||||
- Hit "Next".
|
||||
- Save, go to "Publishing Overview" as prompted, and click "Send changes for review".
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.3] - 2025.07.12
|
||||
### Changed
|
||||
- Photo is pinned to profile mode
|
||||
## [1.0.7] - 2025.08.18
|
||||
### Fixed
|
||||
- Deep link URLs (and other prod settings)
|
||||
- Error in BVC begin view
|
||||
- Deep link for onboard-meeting-members
|
||||
|
||||
|
||||
## [1.0.6] - 2025.08.09
|
||||
### Fixed
|
||||
|
||||
27
README.md
27
README.md
@@ -170,6 +170,33 @@ npm run assets:clean
|
||||
npm run build:native
|
||||
```
|
||||
|
||||
### Environment Setup & Dependencies
|
||||
|
||||
Before building the application, ensure your development environment is properly
|
||||
configured:
|
||||
|
||||
```bash
|
||||
# Install all dependencies (required first time and after updates)
|
||||
npm install
|
||||
|
||||
# Validate your development environment
|
||||
npm run check:dependencies
|
||||
|
||||
# Check prerequisites for testing
|
||||
npm run test:prerequisites
|
||||
```
|
||||
|
||||
**Common Issues & Solutions**:
|
||||
|
||||
- **"tsx: command not found"**: Run `npm install` to install devDependencies
|
||||
- **"capacitor-assets: command not found"**: Ensure `@capacitor/assets` is installed
|
||||
- **Build failures**: Run `npm run check:dependencies` to diagnose environment issues
|
||||
|
||||
**Required Versions**:
|
||||
- Node.js: 18+ (LTS recommended)
|
||||
- npm: 8+ (comes with Node.js)
|
||||
- Platform-specific tools: Android Studio, Xcode (for mobile builds)
|
||||
|
||||
### Platform Support
|
||||
|
||||
- **Android**: Adaptive icons with foreground/background, monochrome support
|
||||
|
||||
@@ -31,8 +31,8 @@ android {
|
||||
applicationId "app.timesafari.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 39
|
||||
versionName "1.0.6"
|
||||
versionCode 40
|
||||
versionName "1.0.7"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -413,7 +413,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.6;
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -430,7 +430,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -440,7 +440,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.6;
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "timesafari",
|
||||
"version": "1.0.7-beta",
|
||||
"version": "1.0.8-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "timesafari",
|
||||
"version": "1.0.7-beta",
|
||||
"version": "1.0.8-beta",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.1",
|
||||
"@capacitor-community/sqlite": "6.0.2",
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "timesafari",
|
||||
"version": "1.0.7-beta",
|
||||
"version": "1.0.8-beta",
|
||||
"description": "Time Safari Application",
|
||||
"author": {
|
||||
"name": "Time Safari Team"
|
||||
@@ -12,6 +12,8 @@
|
||||
"type-check": "tsc --noEmit",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
"check:dependencies": "./scripts/check-dependencies.sh",
|
||||
"test:all": "npm run lint && tsc && npm run test:web && npm run test:mobile && ./scripts/test-safety-check.sh && echo '\n\n\nGotta add the performance tests'",
|
||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||
"test:mobile": "./scripts/test-mobile.sh",
|
||||
"test:android": "node scripts/test-android.js",
|
||||
@@ -27,8 +29,8 @@
|
||||
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
|
||||
"build:capacitor:sync": "npm run build:capacitor && npx cap sync",
|
||||
"build:native": "vite build && npx cap sync && npx capacitor-assets generate",
|
||||
"assets:config": "tsx scripts/assets-config.ts",
|
||||
"assets:validate": "tsx scripts/assets-validator.ts",
|
||||
"assets:config": "npx tsx scripts/assets-config.ts",
|
||||
"assets:validate": "npx tsx scripts/assets-validator.ts",
|
||||
"assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true",
|
||||
"build:ios": "./scripts/build-ios.sh",
|
||||
"build:ios:dev": "./scripts/build-ios.sh --dev",
|
||||
@@ -96,7 +98,7 @@
|
||||
"build:electron:dmg:dev": "./scripts/build-electron.sh --dev --dmg",
|
||||
"build:electron:dmg:test": "./scripts/build-electron.sh --test --dmg",
|
||||
"build:electron:dmg:prod": "./scripts/build-electron.sh --prod --dmg",
|
||||
"clean:android": "adb uninstall app.timesafari.app || true",
|
||||
"clean:android": "./scripts/clean-android.sh",
|
||||
"clean:ios": "rm -rf ios/App/build ios/App/Pods ios/App/output ios/App/App/public ios/DerivedData ios/capacitor-cordova-ios-plugins ios/App/App/capacitor.config.json ios/App/App/config.xml || true",
|
||||
"clean:electron": "./scripts/build-electron.sh --clean",
|
||||
"clean:all": "npm run clean:ios && npm run clean:android && npm run clean:electron",
|
||||
|
||||
@@ -49,6 +49,31 @@ set -e
|
||||
# Source common utilities
|
||||
source "$(dirname "$0")/common.sh"
|
||||
|
||||
# Function to validate critical dependencies
|
||||
validate_dependencies() {
|
||||
log_info "Validating critical dependencies..."
|
||||
|
||||
# Check if node_modules exists
|
||||
if [ ! -d "node_modules" ]; then
|
||||
log_error "node_modules directory not found. Please run 'npm install' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tsx is available
|
||||
if [ ! -f "node_modules/.bin/tsx" ]; then
|
||||
log_error "tsx dependency not found. Please run 'npm install' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if capacitor-assets is available
|
||||
if [ ! -f "node_modules/.bin/capacitor-assets" ]; then
|
||||
log_error "capacitor-assets dependency not found. Please run 'npm install' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "All critical dependencies validated successfully"
|
||||
}
|
||||
|
||||
# Default values
|
||||
BUILD_MODE="development"
|
||||
BUILD_TYPE="debug"
|
||||
@@ -179,6 +204,11 @@ parse_android_args "$@"
|
||||
|
||||
# Print build header
|
||||
print_header "TimeSafari Android Build Process"
|
||||
|
||||
# Validate dependencies before proceeding
|
||||
validate_dependencies
|
||||
|
||||
# Log build start
|
||||
log_info "Starting Android build process at $(date)"
|
||||
log_info "Build mode: $BUILD_MODE"
|
||||
log_info "Build type: $BUILD_TYPE"
|
||||
@@ -257,6 +287,7 @@ fi
|
||||
# Step 1: Validate asset configuration
|
||||
safe_execute "Validating asset configuration" "npm run assets:validate" || {
|
||||
log_warn "Asset validation found issues, but continuing with build..."
|
||||
log_info "If you encounter build failures, please run 'npm install' first to ensure all dependencies are available."
|
||||
}
|
||||
|
||||
# Step 2: Clean Android app
|
||||
@@ -337,6 +368,9 @@ if [ "$OPEN_STUDIO" = true ]; then
|
||||
log_info "Android Studio: opened"
|
||||
fi
|
||||
|
||||
# Reminder about dependency management
|
||||
log_info "💡 Tip: If you encounter dependency issues, run 'npm install' to ensure all packages are up to date."
|
||||
|
||||
print_footer "Android Build"
|
||||
|
||||
# Exit with success
|
||||
|
||||
@@ -173,20 +173,20 @@ check_ios_resources() {
|
||||
|
||||
# Check for required assets
|
||||
if [ ! -f "assets/icon.png" ]; then
|
||||
log_warning "App icon not found at assets/icon.png"
|
||||
log_warn "App icon not found at assets/icon.png"
|
||||
fi
|
||||
|
||||
if [ ! -f "assets/splash.png" ]; then
|
||||
log_warning "Splash screen not found at assets/splash.png"
|
||||
log_warn "Splash screen not found at assets/splash.png"
|
||||
fi
|
||||
|
||||
# Check for iOS-specific files
|
||||
if [ ! -f "ios/App/App/Info.plist" ]; then
|
||||
log_warning "Info.plist not found"
|
||||
log_warn "Info.plist not found"
|
||||
fi
|
||||
|
||||
if [ ! -f "ios/App/App/AppDelegate.swift" ]; then
|
||||
log_warning "AppDelegate.swift not found"
|
||||
log_warn "AppDelegate.swift not found"
|
||||
fi
|
||||
|
||||
log_success "iOS resource check completed"
|
||||
|
||||
110
scripts/check-dependencies.sh
Executable file
110
scripts/check-dependencies.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/bin/bash
|
||||
# check-dependencies.sh
|
||||
# Author: Matthew Raymer
|
||||
# Date: 2025-08-19
|
||||
# Description: Dependency validation script for TimeSafari development environment
|
||||
# This script checks for critical dependencies required for building the application.
|
||||
|
||||
# Exit on any error
|
||||
set -e
|
||||
|
||||
# Source common utilities
|
||||
source "$(dirname "$0")/common.sh"
|
||||
|
||||
print_header "TimeSafari Dependency Validation"
|
||||
|
||||
log_info "Checking development environment dependencies..."
|
||||
|
||||
# Check Node.js version
|
||||
if command -v node &> /dev/null; then
|
||||
NODE_VERSION=$(node --version)
|
||||
log_info "Node.js version: $NODE_VERSION"
|
||||
|
||||
# Extract major version number
|
||||
MAJOR_VERSION=$(echo $NODE_VERSION | sed 's/v\([0-9]*\)\..*/\1/')
|
||||
if [ "$MAJOR_VERSION" -lt 18 ]; then
|
||||
log_error "Node.js version $NODE_VERSION is too old. Please upgrade to Node.js 18 or later."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_error "Node.js is not installed. Please install Node.js 18 or later."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check npm version
|
||||
if command -v npm &> /dev/null; then
|
||||
NPM_VERSION=$(npm --version)
|
||||
log_info "npm version: $NPM_VERSION"
|
||||
else
|
||||
log_error "npm is not installed. Please install npm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if node_modules exists
|
||||
if [ ! -d "node_modules" ]; then
|
||||
log_error "node_modules directory not found."
|
||||
log_info "Please run: npm install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check critical dependencies
|
||||
log_info "Validating critical packages..."
|
||||
|
||||
CRITICAL_DEPS=("tsx" "capacitor-assets" "vite")
|
||||
|
||||
for dep in "${CRITICAL_DEPS[@]}"; do
|
||||
if [ -f "node_modules/.bin/$dep" ]; then
|
||||
log_success "✓ $dep found"
|
||||
else
|
||||
log_error "✗ $dep not found in node_modules/.bin"
|
||||
log_info "This usually means the package wasn't installed properly."
|
||||
log_info "Try running: npm install"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check TypeScript via npx
|
||||
if npx tsc --version &> /dev/null; then
|
||||
TSC_VERSION=$(npx tsc --version)
|
||||
log_success "✓ TypeScript found: $TSC_VERSION"
|
||||
else
|
||||
log_error "✗ TypeScript not accessible via npx"
|
||||
log_info "Try running: npm install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Capacitor CLI
|
||||
if command -v npx &> /dev/null; then
|
||||
if npx cap --version &> /dev/null; then
|
||||
CAP_VERSION=$(npx cap --version)
|
||||
log_success "✓ Capacitor CLI version: $CAP_VERSION"
|
||||
else
|
||||
log_error "✗ Capacitor CLI not accessible via npx"
|
||||
log_info "Try running: npm install @capacitor/cli"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_error "npx is not available. Please ensure npm is properly installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Android development tools
|
||||
if command -v adb &> /dev/null; then
|
||||
log_success "✓ Android Debug Bridge (adb) found"
|
||||
else
|
||||
log_warn "⚠ Android Debug Bridge (adb) not found"
|
||||
log_info "This is only needed for Android development and testing."
|
||||
fi
|
||||
|
||||
if command -v gradle &> /dev/null; then
|
||||
GRADLE_VERSION=$(gradle --version | head -n 1)
|
||||
log_success "✓ Gradle found: $GRADLE_VERSION"
|
||||
else
|
||||
log_warn "⚠ Gradle not found in PATH"
|
||||
log_info "This is only needed if building outside of Android Studio."
|
||||
fi
|
||||
|
||||
log_success "Dependency validation completed successfully!"
|
||||
log_info "Your development environment is ready for TimeSafari development."
|
||||
|
||||
print_footer "Dependency Validation"
|
||||
62
scripts/clean-android.sh
Executable file
62
scripts/clean-android.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
# clean-android.sh
|
||||
# Author: Matthew Raymer
|
||||
# Date: 2025-08-19
|
||||
# Description: Clean Android app with timeout protection to prevent hanging
|
||||
# This script safely uninstalls the TimeSafari app from connected Android devices
|
||||
# with a 30-second timeout to prevent indefinite hanging.
|
||||
|
||||
# Exit on any error
|
||||
set -e
|
||||
|
||||
# Source common utilities
|
||||
source "$(dirname "$0")/common.sh"
|
||||
|
||||
# Function to implement timeout for systems without timeout command
|
||||
timeout_command() {
|
||||
local timeout_seconds="$1"
|
||||
shift
|
||||
|
||||
# Check if timeout command exists
|
||||
if command -v timeout &> /dev/null; then
|
||||
timeout "$timeout_seconds" "$@"
|
||||
else
|
||||
# Fallback for systems without timeout (like macOS)
|
||||
# Use perl to implement timeout
|
||||
perl -e '
|
||||
eval {
|
||||
local $SIG{ALRM} = sub { die "timeout" };
|
||||
alarm shift;
|
||||
system @ARGV;
|
||||
alarm 0;
|
||||
};
|
||||
if ($@) { exit 1; }
|
||||
' "$timeout_seconds" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "Starting Android cleanup process..."
|
||||
|
||||
# Check if adb is available
|
||||
if ! command -v adb &> /dev/null; then
|
||||
log_error "adb command not found. Please install Android SDK Platform Tools."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for connected devices
|
||||
log_info "Checking for connected Android devices..."
|
||||
if adb devices | grep -q 'device$'; then
|
||||
log_info "Android device(s) found. Attempting to uninstall app..."
|
||||
|
||||
# Try to uninstall with timeout
|
||||
if timeout_command 30 adb uninstall app.timesafari.app; then
|
||||
log_success "Successfully uninstalled TimeSafari app"
|
||||
else
|
||||
log_warn "Uninstall failed or timed out after 30 seconds"
|
||||
log_info "This is normal if the app wasn't installed or device is unresponsive"
|
||||
fi
|
||||
else
|
||||
log_info "No Android devices connected. Skipping uninstall."
|
||||
fi
|
||||
|
||||
log_success "Android cleanup process completed"
|
||||
@@ -28,7 +28,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Parameter validation schemas for each route type
|
||||
export const deepLinkSchemas = {
|
||||
export const deepLinkPathSchemas = {
|
||||
claim: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
@@ -60,7 +60,7 @@ export const deepLinkSchemas = {
|
||||
jwt: z.string().optional(),
|
||||
}),
|
||||
"onboard-meeting-members": z.object({
|
||||
id: z.string(),
|
||||
groupId: z.string(),
|
||||
}),
|
||||
project: z.object({
|
||||
id: z.string(),
|
||||
@@ -70,6 +70,17 @@ export const deepLinkSchemas = {
|
||||
}),
|
||||
};
|
||||
|
||||
export const deepLinkQuerySchemas = {
|
||||
"onboard-meeting-members": z.object({
|
||||
password: z.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
// Add a union type of all valid route paths
|
||||
export const VALID_DEEP_LINK_ROUTES = Object.keys(
|
||||
deepLinkPathSchemas,
|
||||
) as readonly (keyof typeof deepLinkPathSchemas)[];
|
||||
|
||||
// Create a type from the array
|
||||
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
|
||||
|
||||
@@ -80,14 +91,13 @@ export const baseUrlSchema = z.object({
|
||||
queryParams: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
// Add a union type of all valid route paths
|
||||
export const VALID_DEEP_LINK_ROUTES = Object.keys(
|
||||
deepLinkSchemas,
|
||||
) as readonly (keyof typeof deepLinkSchemas)[];
|
||||
// export type DeepLinkPathParams = {
|
||||
// [K in keyof typeof deepLinkPathSchemas]: z.infer<(typeof deepLinkPathSchemas)[K]>;
|
||||
// };
|
||||
|
||||
export type DeepLinkParams = {
|
||||
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
|
||||
};
|
||||
// export type DeepLinkQueryParams = {
|
||||
// [K in keyof typeof deepLinkQuerySchemas]: z.infer<(typeof deepLinkQuerySchemas)[K]>;
|
||||
// };
|
||||
|
||||
export interface DeepLinkError extends Error {
|
||||
code: string;
|
||||
|
||||
@@ -10,7 +10,6 @@ import { getHeaders, errorStringForLog } from "@/libs/endorserServer";
|
||||
import { handleApiError } from "./api";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||
import { AxiosErrorResponse } from "@/interfaces/common";
|
||||
|
||||
/**
|
||||
* Profile data interface
|
||||
@@ -125,15 +124,18 @@ export class ProfileService {
|
||||
async deleteProfile(activeDid: string): Promise<boolean> {
|
||||
try {
|
||||
const headers = await getHeaders(activeDid);
|
||||
const response = await this.axios.delete(
|
||||
`${this.partnerApiServer}/api/partner/userProfile`,
|
||||
{ headers },
|
||||
);
|
||||
const url = `${this.partnerApiServer}/api/partner/userProfile`;
|
||||
const response = await this.axios.delete(url, { headers });
|
||||
|
||||
if (response.status === 204 || response.status === 200) {
|
||||
logger.info("Profile deleted successfully");
|
||||
return true;
|
||||
} else {
|
||||
logger.error("Unexpected response status when deleting profile:", {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: response.data,
|
||||
});
|
||||
throw new Error(
|
||||
`Profile not deleted - HTTP ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
@@ -157,9 +159,11 @@ export class ProfileService {
|
||||
return true; // Consider this a success if profile doesn't exist
|
||||
} else if (response.status === 400) {
|
||||
logger.error("Bad request when deleting profile:", response.data);
|
||||
throw new Error(
|
||||
`Profile deletion failed: ${response.data?.error?.message || "Bad request"}`,
|
||||
);
|
||||
const errorMessage =
|
||||
typeof response.data === "string"
|
||||
? response.data
|
||||
: response.data?.message || "Bad request";
|
||||
throw new Error(`Profile deletion failed: ${errorMessage}`);
|
||||
} else if (response.status === 401) {
|
||||
logger.error("Unauthorized to delete profile");
|
||||
throw new Error("You are not authorized to delete this profile");
|
||||
@@ -240,10 +244,33 @@ export class ProfileService {
|
||||
/**
|
||||
* Type guard for API errors with proper typing
|
||||
*/
|
||||
private isApiError(error: unknown): error is AxiosErrorResponse {
|
||||
private isApiError(error: unknown): error is {
|
||||
response?: {
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
data?: { message?: string } | string;
|
||||
};
|
||||
} {
|
||||
return typeof error === "object" && error !== null && "response" in error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract URL from AxiosError without type casting
|
||||
*/
|
||||
private getErrorUrl(error: unknown): string | undefined {
|
||||
if (this.isAxiosError(error)) {
|
||||
return error.config?.url;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for AxiosError
|
||||
*/
|
||||
private isAxiosError(error: unknown): error is AxiosError {
|
||||
return error instanceof AxiosError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract error URL safely from error object
|
||||
*/
|
||||
|
||||
@@ -47,10 +47,11 @@ import { Router } from "vue-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
deepLinkSchemas,
|
||||
deepLinkPathSchemas,
|
||||
baseUrlSchema,
|
||||
routeSchema,
|
||||
DeepLinkRoute,
|
||||
deepLinkQuerySchemas,
|
||||
} from "../interfaces/deepLinks";
|
||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||
import { logger } from "../utils/logger";
|
||||
@@ -74,7 +75,7 @@ function getFirstKeyFromZodObject(
|
||||
* because "router.replace" expects the right parameter name for the route.
|
||||
*/
|
||||
export const ROUTE_MAP: Record<string, { name: string; paramKey?: string }> =
|
||||
Object.entries(deepLinkSchemas).reduce(
|
||||
Object.entries(deepLinkPathSchemas).reduce(
|
||||
(acc, [routeName, schema]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const paramKey = getFirstKeyFromZodObject(schema as z.ZodObject<any>);
|
||||
@@ -198,15 +199,24 @@ export class DeepLinkHandler {
|
||||
}
|
||||
|
||||
// Continue with parameter validation as before...
|
||||
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
|
||||
const pathSchema =
|
||||
deepLinkPathSchemas[path as keyof typeof deepLinkPathSchemas];
|
||||
const querySchema =
|
||||
deepLinkQuerySchemas[path as keyof typeof deepLinkQuerySchemas];
|
||||
|
||||
let validatedParams;
|
||||
let validatedPathParams: Record<string, string> = {};
|
||||
let validatedQueryParams: Record<string, string> = {};
|
||||
try {
|
||||
validatedParams = await schema.parseAsync(params);
|
||||
if (pathSchema) {
|
||||
validatedPathParams = await pathSchema.parseAsync(params);
|
||||
}
|
||||
if (querySchema) {
|
||||
validatedQueryParams = await querySchema.parseAsync(query);
|
||||
}
|
||||
} catch (error) {
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
logger.error(
|
||||
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
||||
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path} ... with error: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
||||
);
|
||||
await this.router.replace({
|
||||
name: "deep-link-error",
|
||||
@@ -226,20 +236,22 @@ export class DeepLinkHandler {
|
||||
try {
|
||||
await this.router.replace({
|
||||
name: routeName,
|
||||
params: validatedParams,
|
||||
params: validatedPathParams,
|
||||
query: validatedQueryParams,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)}`,
|
||||
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedPathParams)} ... and query: ${JSON.stringify(validatedQueryParams)}`,
|
||||
);
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
await this.router.replace({
|
||||
name: "deep-link-error",
|
||||
params: validatedParams,
|
||||
params: validatedPathParams,
|
||||
query: {
|
||||
originalPath: path,
|
||||
errorCode: "ROUTING_ERROR",
|
||||
errorMessage: `Error routing to ${routeName}: ${JSON.stringify(error)}`,
|
||||
...validatedQueryParams,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -182,7 +182,6 @@
|
||||
@change="onLocationCheckboxChange"
|
||||
/>
|
||||
<label for="includeUserProfileLocation">Include Location</label>
|
||||
|
||||
</div>
|
||||
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
|
||||
<p class="text-sm mb-2 text-slate-500">
|
||||
|
||||
@@ -47,7 +47,7 @@ import { computed, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {
|
||||
VALID_DEEP_LINK_ROUTES,
|
||||
deepLinkSchemas,
|
||||
deepLinkPathSchemas,
|
||||
} from "../interfaces/deepLinks";
|
||||
import { logConsoleAndDb } from "../db/databaseUtil";
|
||||
import { logger } from "../utils/logger";
|
||||
@@ -56,7 +56,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
// an object with the route as the key and the first param name as the value
|
||||
const deepLinkSchemaKeys = Object.fromEntries(
|
||||
Object.entries(deepLinkSchemas).map(([route, schema]) => {
|
||||
Object.entries(deepLinkPathSchemas).map(([route, schema]) => {
|
||||
const param = Object.keys(schema.shape)[0];
|
||||
return [route, param];
|
||||
}),
|
||||
|
||||
@@ -113,7 +113,7 @@ export default class OnboardMeetingMembersView extends Vue {
|
||||
try {
|
||||
// Identity creation should be handled by router guard, but keep as fallback for meeting setup
|
||||
if (!this.activeDid) {
|
||||
logger.info(
|
||||
this.$logAndConsole(
|
||||
"[OnboardMeetingMembersView] No active DID found, creating identity as fallback for meeting setup",
|
||||
);
|
||||
this.activeDid = await generateSaveAndActivateIdentity();
|
||||
|
||||
Reference in New Issue
Block a user