diff --git a/BUILDING.md b/BUILDING.md index 6e753e2f..32e3d180 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1176,6 +1176,10 @@ npm run build:ios:prod (.../.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`). - Required password entry 26 times. + - Before distributing, confirm the archive carries production server + defaults. Xcode packages whatever web bundle is in `ios/App/App/public`, + even one from a `--test` build: + `./scripts/check-prod-bundle.sh ios/App/App/public` - Click Distribute -> App Store Connect - In AppStoreConnect, add the build to the distribution. You may have to remove the current build with the "-" when you hover over it, then "Add Build" with the @@ -1478,6 +1482,13 @@ cd - ... and find your `aab` file at app/build/outputs/bundle/release +* Gradle packages whatever web bundle was last synced into + `android/app/src/main/assets/public`, even one from a `--test` build. Before + uploading, confirm it has production server defaults: + `./scripts/check-prod-bundle.sh android/app/build/outputs/bundle/release/app-release.aab` +* `build-android.sh` runs `./gradlew clean`, which deletes any earlier `aab`, so + run `bundleRelease` after the build script finishes. + * Note that F-Droid builds should omit `-PfirebaseEnabled`. At play.google.com/console: @@ -2222,6 +2233,8 @@ command chaining, following DRY principles. - `7` - Asset generation failed - `8` - Android Studio launch failed - `9` - Resource check failed +- `10` - Production bundle has test/dev server defaults (`--prod` only; see + `scripts/check-prod-bundle.sh`) ### A.4 build-ios.sh @@ -2282,6 +2295,8 @@ command chaining, following DRY principles. - **Clean Build**: Removes Xcode build artifacts and DerivedData - **Asset Generation**: Creates platform-specific assets - **Simulator Support**: Launches iOS Simulator for testing +- **Production Bundle Check**: With `--prod`, exits with code `10` if the synced + web bundle has test/dev server defaults (`scripts/check-prod-bundle.sh`) ### A.5 common.sh diff --git a/package.json b/package.json index 7df94418..784672be 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test:prerequisites": "node scripts/check-prerequisites.js", "test:unit": "jest", "check:dependencies": "./scripts/check-dependencies.sh", + "check:prod-bundle": "./scripts/check-prod-bundle.sh", "deps:update-daily-notification-plugin": "npm install @timesafari/daily-notification-plugin@git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#master", "test:all": "npm run lint && npm run type-check && npm run type-check:vue && npm run test:unit && npm run test:web && npm run test:mobile && echo '\n\n\nGotta add the performance tests'", "test:web": "npx playwright test -c playwright.config-local.ts --trace on", diff --git a/scripts/build-android.sh b/scripts/build-android.sh index ef048b62..ea0ac10b 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -545,7 +545,7 @@ fi # Handle assets-only mode if [ "$ASSETS_ONLY" = true ]; then log_info "Assets-only mode: generating assets" - safe_execute "Generating assets" "npx capacitor-assets generate --android" || exit 7 + safe_execute "Generating assets" "npx capacitor-assets generate --android --assetPath resources" || exit 7 log_success "Assets generation completed successfully!" exit 0 fi @@ -612,25 +612,31 @@ elif [ "$BUILD_MODE" = "production" ]; then safe_execute "Building Capacitor version (production)" "npm run build:capacitor -- --mode production" || exit 3 fi -# Step 6: Clean Gradle build +# Step 6: Sync with Capacitor (before any Gradle assemble, which packages +# whatever web bundle and resources are in android/app/src/main at that moment) +safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6 + +# Step 6.5: Restore local plugins (capacitor.plugins.json gets overwritten by cap sync) +safe_execute "Restoring local plugins" "node scripts/restore-local-plugins.js" || exit 7 + +# Step 6.6: Verify the synced web bundle has production server defaults +if [ "$BUILD_MODE" = "production" ]; then + safe_execute "Checking production bundle" "./scripts/check-prod-bundle.sh android/app/src/main/assets/public" || exit 10 +fi + +# Step 7: Generate assets +safe_execute "Generating assets" "npx capacitor-assets generate --android --assetPath resources" || exit 7 + +# Step 8: Clean Gradle build safe_execute "Cleaning Gradle build" "cd android && ./gradlew clean && cd .." || exit 4 -# Step 7: Build based on type +# Step 9: Build based on type if [ "$BUILD_TYPE" = "debug" ]; then safe_execute "Assembling debug build" "cd android && ./gradlew assembleDebug && cd .." || exit 5 elif [ "$BUILD_TYPE" = "release" ]; then safe_execute "Assembling release build" "cd android && ./gradlew assembleRelease && cd .." || exit 5 fi -# Step 8: Sync with Capacitor -safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6 - -# Step 8.5: Restore local plugins (capacitor.plugins.json gets overwritten by cap sync) -safe_execute "Restoring local plugins" "node scripts/restore-local-plugins.js" || exit 7 - -# Step 9: Generate assets -safe_execute "Generating assets" "npx capacitor-assets generate --android" || exit 7 - # Step 10: Build APK/AAB if requested if [ "$BUILD_APK" = true ]; then if [ "$BUILD_TYPE" = "debug" ]; then @@ -642,6 +648,9 @@ fi if [ "$BUILD_AAB" = true ]; then safe_execute "Building AAB" "cd android && ./gradlew bundleRelease && cd .." || exit 5 + if [ "$BUILD_MODE" = "production" ]; then + safe_execute "Checking production AAB" "./scripts/check-prod-bundle.sh android/app/build/outputs/bundle/release/app-release.aab" || exit 10 + fi fi # Step 11: Auto-run app if requested diff --git a/scripts/build-ios.sh b/scripts/build-ios.sh index 27867437..b432c124 100755 --- a/scripts/build-ios.sh +++ b/scripts/build-ios.sh @@ -680,6 +680,11 @@ safe_execute "Installing CocoaPods dependencies" "run_pod_install_with_workaroun # Step 6.6: Sync with Capacitor (uses run_cap_sync_with_workaround defined above for Xcode 26) safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6 +# Step 6.7: Verify the synced web bundle has production server defaults +if [ "$BUILD_MODE" = "production" ]; then + safe_execute "Checking production bundle" "./scripts/check-prod-bundle.sh ios/App/App/public" || exit 10 +fi + # Step 7: Generate assets safe_execute "Generating assets" "generate_ios_assets" || exit 7 diff --git a/scripts/check-prod-bundle.sh b/scripts/check-prod-bundle.sh new file mode 100755 index 00000000..8d006bfb --- /dev/null +++ b/scripts/check-prod-bundle.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# check-prod-bundle.sh +# Fails if a built web bundle has test/dev server URLs baked in as defaults. +# +# Native packaging (gradlew bundleRelease, Xcode archive) ships whatever web +# bundle was last synced into the native project, whether it came from a +# --test, --dev, or --prod build. Run this on the bundle before uploading. +# +# Usage: +# ./scripts/check-prod-bundle.sh [PATH...] +# +# Each PATH is a directory (e.g. android/app/src/main/assets/public, +# ios/App/App/public, an .xcarchive) or an .aab, .apk, or .ipa file. +# With no PATH, checks the synced Android and iOS web assets plus the +# release AAB if it exists. +# +# Detection: the canary URLs below appear in src/ only as AppString.TEST_* +# enum values, which minify to `x.TEST_..._SERVER=""`. Any other quoted +# occurrence is a VITE_* default compiled in from .env.test or +# .env.development (.env.development also uses the test image server). +# +# Exit codes: 0 = clean, 1 = test defaults found, 2 = bad input + +source "$(dirname "$0")/common.sh" + +CANARY_REGEX='"https://test(-image-api|-notify-api)?\.timesafari\.app"' +ENUM_REGEX='TEST[0-9]*_[A-Z_]+="https' + +# Prints offending snippets from the JS files under a directory; returns 1 if any. +check_js_dir() { + local dir="$1" + local js_count + js_count=$(find "$dir" -name "*.js" -path "*assets*" | wc -l | tr -d ' ') + if [ "$js_count" = "0" ]; then + log_error "No JS assets found under $dir" + return 2 + fi + + local hits + hits=$(find "$dir" -name "*.js" -path "*assets*" -print0 \ + | xargs -0 grep -ohE ".{0,40}${CANARY_REGEX}" \ + | grep -vE "$ENUM_REGEX") + if [ -n "$hits" ]; then + log_error "Test/dev server defaults found in bundle:" + echo "$hits" | sed 's/^/ /' >&2 + return 1 + fi + return 0 +} + +check_path() { + local path="$1" + local result + + if [ -d "$path" ]; then + check_js_dir "$path" + result=$? + elif [ -f "$path" ]; then + case "$path" in + *.aab|*.apk|*.ipa) ;; + *) + log_error "Unsupported file type: $path" + return 2 + ;; + esac + local tmp + tmp=$(mktemp -d) + unzip -q "$path" '*public/assets/*.js' -d "$tmp" 2>/dev/null + check_js_dir "$tmp" + result=$? + rm -rf "$tmp" + else + log_error "Not found: $path" + return 2 + fi + + if [ "$result" = "0" ]; then + log_success "Production defaults OK: $path" + else + log_error "Not a production bundle: $path" + fi + return $result +} + +paths=("$@") +if [ ${#paths[@]} -eq 0 ]; then + for p in android/app/src/main/assets/public \ + ios/App/App/public \ + android/app/build/outputs/bundle/release/app-release.aab; do + [ -e "$p" ] && paths+=("$p") + done +fi + +status=0 +for p in "${paths[@]}"; do + check_path "$p" + rc=$? + [ $rc -gt $status ] && status=$rc +done +exit $status