Merge branch 'master' into retire-legacy-notification
This commit is contained in:
@@ -13,7 +13,7 @@ This guard is in `android/app/build.gradle`. Do NOT change this conditional to a
|
||||
|
||||
`google-services.json` is intentionally excluded from git (`android/.gitignore`). Never commit it.
|
||||
|
||||
Full details, incident history, and F-Droid notes: `doc/development/android-firebase-gms.md`
|
||||
Full details, incident history, and F-Droid notes: `doc/android-firebase-gms.md`
|
||||
|
||||
## Android Build — MLKit Barcode Scanner
|
||||
|
||||
|
||||
+44
-22
@@ -1084,6 +1084,11 @@ npm run build:ios:assets # Generate assets only
|
||||
npm run build:ios:deploy # Build and deploy to connected device
|
||||
```
|
||||
|
||||
The iOS asset command reads source artwork from `resources/` explicitly. It
|
||||
generates the default 1024×1024 App Store icon, from which Xcode derives the
|
||||
required device sizes, and installs any available appearance variants under
|
||||
`ios/App/App/Assets.xcassets/AppIcon.appiconset/`.
|
||||
|
||||
#### Automated Build Script
|
||||
|
||||
The recommended way to build for iOS is using the automated build script:
|
||||
@@ -1127,7 +1132,7 @@ If you need to build manually or want to understand the individual steps:
|
||||
|
||||
##### 0. First time (or if dependencies change)
|
||||
|
||||
- `pkgx +rubygems.org +pod zsh`
|
||||
- `pkgx +rubygems.org +pod /bin/zsh`
|
||||
|
||||
- ... and you may have to fix these, especially with pkgx:
|
||||
|
||||
@@ -1141,7 +1146,7 @@ export GEM_PATH=$shortened_path
|
||||
##### 1. Bump the version in package.json & CHANGELOG.md for `MARKETING_VERSION`, then `grep CURRENT_PROJECT_VERSION ios/App/App.xcodeproj/project.pbxproj` and add 1 for the numbered version here:
|
||||
|
||||
```bash
|
||||
cd ios/App && xcrun agvtool new-version 70 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.4.4;/g" App.xcodeproj/project.pbxproj && cd -
|
||||
cd ios/App && xcrun agvtool new-version 72 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.4.6;/g" App.xcodeproj/project.pbxproj && cd -
|
||||
# Unfortunately this edits Info.plist directly.
|
||||
#xcrun agvtool new-marketing-version 0.4.5
|
||||
```
|
||||
@@ -1162,14 +1167,20 @@ npm run build:ios:prod
|
||||
|
||||
3.2. Use Xcode to release.
|
||||
|
||||
- Someday: Under "General" we want to rename a bunch of things to "Time Safari"
|
||||
- Someday: Under "General" we want to rename a bunch of things to "Giftopia"
|
||||
- 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.
|
||||
- Required password entry 26 times.
|
||||
- It can get stuck on uploading SPI; try canceling and restarting.
|
||||
- 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`).
|
||||
- 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
|
||||
@@ -1319,6 +1330,7 @@ npm run build:android:prod # Production build (builds for production e
|
||||
# Auto-run builds
|
||||
npm run build:android:test:run # Test build with auto-run (builds then runs on emulator)
|
||||
npm run build:android:prod:run # Production build with auto-run (builds then runs on emulator)
|
||||
# Auto-run targets the one connected device; with none or several it prompts, or exits 9 without a terminal
|
||||
|
||||
# Debug and release builds
|
||||
npm run build:android:debug # Debug build (builds debug APK)
|
||||
@@ -1422,8 +1434,8 @@ The recommended way to build for Android is using the automated build script:
|
||||
##### 1. Bump the version in package.json, then update these versions & run:
|
||||
|
||||
```bash
|
||||
perl -p -i -e 's/versionCode .*/versionCode 70/g' android/app/build.gradle
|
||||
perl -p -i -e 's/versionName .*/versionName "1.4.4"/g' android/app/build.gradle
|
||||
perl -p -i -e 's/versionCode .*/versionCode 72/g' android/app/build.gradle
|
||||
perl -p -i -e 's/versionName .*/versionName "1.4.6"/g' android/app/build.gradle
|
||||
```
|
||||
|
||||
##### 2. Build
|
||||
@@ -1442,35 +1454,38 @@ npx cap open android
|
||||
|
||||
##### 4. Use Android Studio to build and run on emulator or device
|
||||
|
||||
## Android Build from the console
|
||||
#### Android Build from the console
|
||||
|
||||
```bash
|
||||
cd android
|
||||
./gradlew clean
|
||||
./gradlew build -Dlint.baselines.continue=true
|
||||
cd -
|
||||
```
|
||||
`cd android`
|
||||
|
||||
... or, to create the `aab` file, `bundle` instead of `build`:
|
||||
* Note that this currently fails: `./gradlew build`
|
||||
|
||||
To create the `aab` file, `bundle` instead of `build` (but not signed and not usable in Play Store):
|
||||
|
||||
```bash
|
||||
./gradlew bundleDebug -Dlint.baselines.continue=true
|
||||
```
|
||||
|
||||
... or, to create a signed release:
|
||||
So, to create a signed `aab` release:
|
||||
|
||||
- Setup by adding the app/gradle.properties.secrets file (see properties at top
|
||||
of app/build.gradle) and the app/time-safari-upload-key-pkcs12.jks file
|
||||
- Then `bundleRelease`:
|
||||
- Then `bundleRelease` or `bundleDebug`:
|
||||
|
||||
```bash
|
||||
cd android
|
||||
# if you use pkgx: pkgx +openjdk.org /bin/zsh
|
||||
./gradlew bundleRelease -Dlint.baselines.continue=true -PfirebaseEnabled
|
||||
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:
|
||||
@@ -1607,10 +1622,13 @@ Use the commands above to check and fix code quality issues.
|
||||
4. **Native Build**: Platform-specific compilation
|
||||
5. **Package Creation**: APK/IPA generation
|
||||
|
||||
`resources/` is the canonical source for app icons and splash screens.
|
||||
Do not keep a legacy top-level `assets/` directory unless it is intentionally
|
||||
used: `@capacitor/assets` prioritizes `assets/` over `resources/`, which can
|
||||
prevent the canonical assets from being discovered.
|
||||
`resources/` is the canonical source for app icons and splash screens. Build
|
||||
scripts select it explicitly with `--assetPath resources`. Use the same option
|
||||
when invoking the generator directly:
|
||||
|
||||
```bash
|
||||
npx capacitor-assets generate --assetPath resources
|
||||
```
|
||||
|
||||
## Architecture Environment Configuration
|
||||
|
||||
@@ -1822,7 +1840,7 @@ npm run build:android:assets
|
||||
- Configure signing keys
|
||||
- Check device/emulator setup
|
||||
|
||||
## Additional Resources
|
||||
## Additional Documentation
|
||||
|
||||
- [Electron Build Patterns](doc/electron-build-patterns.md)
|
||||
- [iOS Build Scripts](doc/ios-build-scripts.md)
|
||||
@@ -2212,6 +2230,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
|
||||
|
||||
@@ -2272,6 +2292,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
|
||||
|
||||
|
||||
+7
-2
@@ -6,13 +6,18 @@ 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).
|
||||
|
||||
|
||||
## [?] - 2026
|
||||
## [1.4.6] - 2026.09.27
|
||||
### Fixed
|
||||
- Incorrect 'test' server URLs on production.
|
||||
|
||||
|
||||
## [1.4.5] - 2026.09.24
|
||||
### Added
|
||||
- Full flow for setting up SMS notifications: phone registration and code
|
||||
verification, delegated alertSearch JWT batches with a send hour, and
|
||||
revocation to stop the texts
|
||||
- Notification debug panel action to mint and upload a push-channel alertSearch
|
||||
authorization, built on the same batch minter as the SMS channel
|
||||
authorization, built on the same batch minter as the SMS channel (hidden by dev flag)
|
||||
### Fixed
|
||||
- Native build scripts set NODE_ENV, so `import.meta.env.DEV` matches the build
|
||||
mode instead of always reporting a production build
|
||||
|
||||
@@ -37,8 +37,8 @@ android {
|
||||
applicationId "app.timesafari.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 70
|
||||
versionName "1.4.4"
|
||||
versionCode 72
|
||||
versionName "1.4.6"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -54,5 +54,9 @@
|
||||
{
|
||||
"pkg": "SharedImage",
|
||||
"classpath": "app.timesafari.sharedimage.SharedImagePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "NotificationInspector",
|
||||
"classpath": "app.timesafari.notifications.NotificationInspectorPlugin"
|
||||
}
|
||||
]
|
||||
|
||||
+17
-1
@@ -1,5 +1,6 @@
|
||||
package app.timesafari.notifications;
|
||||
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.getcapacitor.Plugin;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import com.getcapacitor.PluginMethod;
|
||||
@@ -12,5 +13,20 @@ public class NotificationInspectorPlugin extends Plugin {
|
||||
call.unimplemented(
|
||||
"Pending notification inspection is currently implemented on iOS only");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this build carries a Firebase config, i.e. was built with
|
||||
* -PfirebaseEnabled. Without it, PushNotifications.register() throws on the
|
||||
* Capacitor plugin thread and kills the app, so JS must check this first.
|
||||
* The google-services Gradle plugin generates the google_app_id resource,
|
||||
* which is what Firebase's own auto-initialization looks for.
|
||||
*/
|
||||
@PluginMethod
|
||||
public void isFirebaseConfigured(PluginCall call) {
|
||||
int resId = getContext().getResources().getIdentifier(
|
||||
"google_app_id", "string", getContext().getPackageName());
|
||||
JSObject ret = new JSObject();
|
||||
ret.put("configured", resId != 0);
|
||||
call.resolve(ret);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,16 @@ This means a single Play Store AAB (`bundleRelease -PfirebaseEnabled`) covers bo
|
||||
|
||||
**F-Droid** is stricter: their build policy rejects any APK with GMS dependencies at the binary level, even with graceful degradation. F-Droid submission would require a separate `assembleRelease` build (no flag) and a dedicated F-Droid listing.
|
||||
|
||||
## Push registration in builds without Firebase
|
||||
|
||||
A build without `-PfirebaseEnabled` contains the `@capacitor/push-notifications` plugin, but no Firebase config. In that build, `PushNotifications.register()` throws `Default FirebaseApp is not initialized` on the Capacitor plugin thread, which kills the app; a JS `try/catch` cannot intercept it.
|
||||
|
||||
The JS therefore asks native code first. `NotificationInspector.isFirebaseConfigured()` (`android/app/src/main/java/app/timesafari/notifications/NotificationInspectorPlugin.java`) reports whether the `google_app_id` string resource exists; the google-services Gradle plugin generates that resource only when Firebase is enabled, and it is the same signal Firebase's auto-initialization uses. `firebaseMessagingClient.ts` skips `register()` on Android when it is absent (or when the check fails), logging `No Firebase config in this Android build; skipping push registration`. Listener setup and the notification permission request run either way, since local daily notifications need that permission.
|
||||
|
||||
`NotificationInspector` must be listed in `scripts/restore-local-plugins.js`: `MainActivity` registers plugins after the Capacitor bridge is built, so a plugin missing from `capacitor.plugins.json` reports "not implemented on android" and the check falls back to skipping push.
|
||||
|
||||
Any code that calls `PushNotifications.register()` or other `FirebaseMessaging` APIs on Android must go through the same check.
|
||||
|
||||
## The `google-services.json` file
|
||||
|
||||
- Gitignored (`android/.gitignore` line 80) — never commit it
|
||||
@@ -64,3 +74,9 @@ This is an accepted trade-off. Removing it would require either forking the plug
|
||||
Jose Olarte III's `notify-api` branch placed a production `google-services.json` in `android/` to test Firebase Cloud Messaging. The branch was never merged to `master`, but because the file is gitignored it persisted on disk after switching branches. At the time, the Gradle conditional activated Firebase based on file presence alone (no opt-in flag), so all subsequent local builds embedded Firebase and required Google Play Services. This silently broke APK/Aurora/Zapstore distribution.
|
||||
|
||||
**Fix applied:** deleted `google-services.json` from disk, changed the Gradle conditional to require `-PfirebaseEnabled`, and documented the rule in `AGENTS.md`.
|
||||
|
||||
## Incident: September 2026
|
||||
|
||||
After the June fix, local builds without `-PfirebaseEnabled` (including every `scripts/build-android.sh` build and the FOSS APKs from v1.4.4 on) crashed at launch as soon as notification permission was granted, because startup code called `PushNotifications.register()` unconditionally. Found while running `npm run build:android:prod:run` on an emulator.
|
||||
|
||||
**Fix applied:** the `isFirebaseConfigured()` gate described in "Push registration in builds without Firebase", plus adding `NotificationInspector` to `scripts/restore-local-plugins.js` so the check is reachable on Android.
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
C86585E32ED456DE00824752 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
C86585E32ED456DE00824752 /* Exceptions for "TimeSafariShareExtension" folder in "TimeSafariShareExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
@@ -82,7 +82,18 @@
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
C86585D62ED456DE00824752 /* TimeSafariShareExtension */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (C86585E32ED456DE00824752 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TimeSafariShareExtension; sourceTree = "<group>"; };
|
||||
C86585D62ED456DE00824752 /* TimeSafariShareExtension */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
C86585E32ED456DE00824752 /* Exceptions for "TimeSafariShareExtension" folder in "TimeSafariShareExtension" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = TimeSafariShareExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -530,7 +541,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = App/AppDebug.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 70;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -541,7 +552,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.6;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -560,7 +571,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 70;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -571,7 +582,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
@@ -589,7 +600,7 @@
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 70;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -603,7 +614,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.6;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
||||
@@ -627,7 +638,7 @@
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 70;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -641,7 +652,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.6;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
Generated
+677
-1419
File diff suppressed because it is too large
Load Diff
+118
-117
@@ -1,138 +1,139 @@
|
||||
{
|
||||
"name": "giftopia",
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.7-beta",
|
||||
"description": "Giftopia App",
|
||||
"author": {
|
||||
"name": "Gift Economies Team"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"type-check:vue": "vue-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",
|
||||
"test:unit": "jest",
|
||||
"check:dependencies": "./scripts/check-dependencies.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",
|
||||
"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)",
|
||||
"auto-run": "./scripts/auto-run.sh",
|
||||
"auto-run:test": "./scripts/auto-run.sh --test",
|
||||
"auto-run:prod": "./scripts/auto-run.sh --prod",
|
||||
"auto-run:ios": "./scripts/auto-run.sh --platform=ios",
|
||||
"auto-run:android": "./scripts/auto-run.sh --platform=android",
|
||||
"auto-run:electron": "./scripts/auto-run.sh --platform=electron",
|
||||
"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 && node scripts/restore-local-plugins.js",
|
||||
"build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && bash -c 'source scripts/common.sh && ensure_ios_capacitor_asset_directories' && npx capacitor-assets generate",
|
||||
"assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true",
|
||||
"assets:config": "npx tsx scripts/assets-config.ts",
|
||||
"assets:validate": "npx tsx scripts/assets-validator.ts",
|
||||
"assets:validate:android": "./scripts/build-android.sh --assets-only",
|
||||
"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",
|
||||
"build:ios:test": "./scripts/build-ios.sh --test",
|
||||
"build:ios:test:run": "./scripts/build-ios.sh --test --auto-run",
|
||||
"build:ios:prod": "./scripts/build-ios.sh --prod",
|
||||
"build:ios:prod:run": "./scripts/build-ios.sh --prod --auto-run",
|
||||
"build:ios:debug": "./scripts/build-ios.sh --debug",
|
||||
"build:ios:debug:run": "./scripts/build-ios.sh --debug --auto-run",
|
||||
"build:ios:release": "./scripts/build-ios.sh --release",
|
||||
"build:ios:release:run": "./scripts/build-ios.sh --release --auto-run",
|
||||
"build:ios:studio": "./scripts/build-ios.sh --studio",
|
||||
"build:ios:ipa": "./scripts/build-ios.sh --ipa",
|
||||
"build:ios:clean": "./scripts/build-ios.sh --clean",
|
||||
"build:ios:sync": "./scripts/build-ios.sh --sync",
|
||||
"build:ios:assets": "./scripts/build-ios.sh --assets",
|
||||
"build:ios:deploy": "./scripts/build-ios.sh --deploy",
|
||||
"build:ios:dev:custom": "./scripts/build-ios.sh --dev --api-ip",
|
||||
"build:ios:test:custom": "./scripts/build-ios.sh --test --api-ip",
|
||||
"build:ios:dev:run:custom": "./scripts/build-ios.sh --dev --api-ip --auto-run",
|
||||
"build:ios:test:run:custom": "./scripts/build-ios.sh --test --api-ip --auto-run",
|
||||
"build:web": "./scripts/build-web.sh",
|
||||
"build:web:dev": "./scripts/build-web.sh --dev",
|
||||
"build:web:test": "./scripts/build-web.sh --test",
|
||||
"build:web:prod": "./scripts/build-web.sh --prod",
|
||||
"build:web:docker": "./scripts/build-web.sh --docker",
|
||||
"build:web:docker:test": "./scripts/build-web.sh --docker:test",
|
||||
"build:web:docker:prod": "./scripts/build-web.sh --docker:prod",
|
||||
"build:web:serve": "./scripts/build-web.sh --serve",
|
||||
"build:web:serve:test": "./scripts/build-web.sh --serve --test",
|
||||
"build:web:serve:prod": "./scripts/build-web.sh --serve --prod",
|
||||
"docker:up": "docker-compose up",
|
||||
"docker:up:test": "npm run build:web:build -- --mode test && docker-compose up test",
|
||||
"docker:up:prod": "npm run build:web:build -- --mode production && docker-compose up production",
|
||||
"docker:down": "docker-compose down",
|
||||
"docker:logs": "docker-compose logs -f",
|
||||
"build:electron": "./scripts/build-electron.sh",
|
||||
"build:electron:dev": "./scripts/build-electron.sh --dev",
|
||||
"build:electron:dev:run": "./scripts/build-electron.sh --dev --auto-run",
|
||||
"build:electron:test": "./scripts/build-electron.sh --test",
|
||||
"build:electron:test:run": "./scripts/build-electron.sh --test --auto-run",
|
||||
"build:electron:prod": "./scripts/build-electron.sh --prod",
|
||||
"build:electron:prod:run": "./scripts/build-electron.sh --prod --auto-run",
|
||||
"build:electron:windows": "./scripts/build-electron.sh --prod --windows",
|
||||
"build:electron:windows:dev": "./scripts/build-electron.sh --dev --windows",
|
||||
"build:electron:windows:test": "./scripts/build-electron.sh --test --windows",
|
||||
"build:electron:windows:prod": "./scripts/build-electron.sh --prod --windows",
|
||||
"build:electron:mac": "./scripts/build-electron.sh --prod --mac",
|
||||
"build:electron:mac:dev": "./scripts/build-electron.sh --dev --mac",
|
||||
"build:electron:mac:test": "./scripts/build-electron.sh --test --mac",
|
||||
"build:electron:mac:prod": "./scripts/build-electron.sh --prod --mac",
|
||||
"build:electron:linux": "./scripts/build-electron.sh --prod --linux",
|
||||
"build:electron:linux:dev": "./scripts/build-electron.sh --dev --linux",
|
||||
"build:electron:linux:test": "./scripts/build-electron.sh --test --linux",
|
||||
"build:electron:linux:prod": "./scripts/build-electron.sh --prod --linux",
|
||||
"build:electron:appimage": "./scripts/build-electron.sh --prod --appimage",
|
||||
"build:electron:appimage:dev": "./scripts/build-electron.sh --dev --appimage",
|
||||
"build:electron:appimage:test": "./scripts/build-electron.sh --test --appimage",
|
||||
"build:electron:appimage:prod": "./scripts/build-electron.sh --prod --appimage",
|
||||
"build:electron:deb": "./scripts/build-electron.sh --prod --deb",
|
||||
"build:electron:deb:dev": "./scripts/build-electron.sh --dev --deb",
|
||||
"build:electron:deb:test": "./scripts/build-electron.sh --test --deb",
|
||||
"build:electron:deb:prod": "./scripts/build-electron.sh --prod --deb",
|
||||
"build:electron:dmg": "./scripts/build-electron.sh --prod --dmg",
|
||||
"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",
|
||||
"markdown:fix": "markdownlint-cli2 --fix",
|
||||
"markdown:check": "markdownlint-cli2",
|
||||
"markdown:setup": "./scripts/setup-markdown-hooks.sh",
|
||||
"prepare": "husky",
|
||||
"guard": "bash ./scripts/build-arch-guard.sh",
|
||||
"guard:test": "bash ./scripts/build-arch-guard.sh --staged",
|
||||
"guard:setup": "npm run prepare && echo '✅ Build Architecture Guard is now active!'",
|
||||
"clean:android": "./scripts/uninstall-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",
|
||||
"auto-run": "./scripts/auto-run.sh",
|
||||
"auto-run:android": "./scripts/auto-run.sh --platform=android",
|
||||
"auto-run:electron": "./scripts/auto-run.sh --platform=electron",
|
||||
"auto-run:ios": "./scripts/auto-run.sh --platform=ios",
|
||||
"auto-run:prod": "./scripts/auto-run.sh --prod",
|
||||
"auto-run:test": "./scripts/auto-run.sh --test",
|
||||
"build:android": "./scripts/build-android.sh",
|
||||
"build:android:dev": "./scripts/build-android.sh --dev",
|
||||
"build:android:test": "./scripts/build-android.sh --test",
|
||||
"build:android:test:run": "./scripts/build-android.sh --test --auto-run",
|
||||
"build:android:prod": "./scripts/build-android.sh --prod",
|
||||
"build:android:prod:run": "./scripts/build-android.sh --prod --auto-run",
|
||||
"build:android:aab": "./scripts/build-android.sh --aab",
|
||||
"build:android:apk": "./scripts/build-android.sh --apk",
|
||||
"build:android:assets": "./scripts/build-android.sh --assets",
|
||||
"build:android:clean": "./scripts/build-android.sh --clean",
|
||||
"build:android:debug": "./scripts/build-android.sh --debug",
|
||||
"build:android:debug:run": "./scripts/build-android.sh --debug --auto-run",
|
||||
"build:android:deploy": "./scripts/build-android.sh --deploy",
|
||||
"build:android:dev": "./scripts/build-android.sh --dev",
|
||||
"build:android:dev:custom": "./scripts/build-android.sh --dev --api-ip",
|
||||
"build:android:dev:run:custom": "./scripts/build-android.sh --dev --api-ip --auto-run",
|
||||
"build:android:prod": "./scripts/build-android.sh --prod",
|
||||
"build:android:prod:run": "./scripts/build-android.sh --prod --auto-run",
|
||||
"build:android:release": "./scripts/build-android.sh --release",
|
||||
"build:android:release:run": "./scripts/build-android.sh --release --auto-run",
|
||||
"build:android:studio": "./scripts/build-android.sh --studio",
|
||||
"build:android:apk": "./scripts/build-android.sh --apk",
|
||||
"build:android:aab": "./scripts/build-android.sh --aab",
|
||||
"build:android:clean": "./scripts/build-android.sh --clean",
|
||||
"build:android:sync": "./scripts/build-android.sh --sync",
|
||||
"build:android:assets": "./scripts/build-android.sh --assets",
|
||||
"build:android:deploy": "./scripts/build-android.sh --deploy",
|
||||
"build:android:dev:custom": "./scripts/build-android.sh --dev --api-ip",
|
||||
"build:android:test": "./scripts/build-android.sh --test",
|
||||
"build:android:test:custom": "./scripts/build-android.sh --test --api-ip",
|
||||
"build:android:dev:run:custom": "./scripts/build-android.sh --dev --api-ip --auto-run",
|
||||
"build:android:test:run:custom": "./scripts/build-android.sh --test --api-ip --auto-run"
|
||||
"build:android:test:run": "./scripts/build-android.sh --test --auto-run",
|
||||
"build:android:test:run:custom": "./scripts/build-android.sh --test --api-ip --auto-run",
|
||||
"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 && node scripts/restore-local-plugins.js",
|
||||
"build:electron": "./scripts/build-electron.sh",
|
||||
"build:electron:appimage": "./scripts/build-electron.sh --prod --appimage",
|
||||
"build:electron:appimage:dev": "./scripts/build-electron.sh --dev --appimage",
|
||||
"build:electron:appimage:prod": "./scripts/build-electron.sh --prod --appimage",
|
||||
"build:electron:appimage:test": "./scripts/build-electron.sh --test --appimage",
|
||||
"build:electron:deb": "./scripts/build-electron.sh --prod --deb",
|
||||
"build:electron:deb:dev": "./scripts/build-electron.sh --dev --deb",
|
||||
"build:electron:deb:prod": "./scripts/build-electron.sh --prod --deb",
|
||||
"build:electron:deb:test": "./scripts/build-electron.sh --test --deb",
|
||||
"build:electron:dev": "./scripts/build-electron.sh --dev",
|
||||
"build:electron:dev:run": "./scripts/build-electron.sh --dev --auto-run",
|
||||
"build:electron:dmg": "./scripts/build-electron.sh --prod --dmg",
|
||||
"build:electron:dmg:dev": "./scripts/build-electron.sh --dev --dmg",
|
||||
"build:electron:dmg:prod": "./scripts/build-electron.sh --prod --dmg",
|
||||
"build:electron:dmg:test": "./scripts/build-electron.sh --test --dmg",
|
||||
"build:electron:linux": "./scripts/build-electron.sh --prod --linux",
|
||||
"build:electron:linux:dev": "./scripts/build-electron.sh --dev --linux",
|
||||
"build:electron:linux:prod": "./scripts/build-electron.sh --prod --linux",
|
||||
"build:electron:linux:test": "./scripts/build-electron.sh --test --linux",
|
||||
"build:electron:mac": "./scripts/build-electron.sh --prod --mac",
|
||||
"build:electron:mac:dev": "./scripts/build-electron.sh --dev --mac",
|
||||
"build:electron:mac:prod": "./scripts/build-electron.sh --prod --mac",
|
||||
"build:electron:mac:test": "./scripts/build-electron.sh --test --mac",
|
||||
"build:electron:prod": "./scripts/build-electron.sh --prod",
|
||||
"build:electron:prod:run": "./scripts/build-electron.sh --prod --auto-run",
|
||||
"build:electron:test": "./scripts/build-electron.sh --test",
|
||||
"build:electron:test:run": "./scripts/build-electron.sh --test --auto-run",
|
||||
"build:electron:windows": "./scripts/build-electron.sh --prod --windows",
|
||||
"build:electron:windows:dev": "./scripts/build-electron.sh --dev --windows",
|
||||
"build:electron:windows:prod": "./scripts/build-electron.sh --prod --windows",
|
||||
"build:electron:windows:test": "./scripts/build-electron.sh --test --windows",
|
||||
"build:ios": "./scripts/build-ios.sh",
|
||||
"build:ios:assets": "./scripts/build-ios.sh --assets",
|
||||
"build:ios:clean": "./scripts/build-ios.sh --clean",
|
||||
"build:ios:debug": "./scripts/build-ios.sh --debug",
|
||||
"build:ios:debug:run": "./scripts/build-ios.sh --debug --auto-run",
|
||||
"build:ios:deploy": "./scripts/build-ios.sh --deploy",
|
||||
"build:ios:dev": "./scripts/build-ios.sh --dev",
|
||||
"build:ios:dev:custom": "./scripts/build-ios.sh --dev --api-ip",
|
||||
"build:ios:dev:run:custom": "./scripts/build-ios.sh --dev --api-ip --auto-run",
|
||||
"build:ios:ipa": "./scripts/build-ios.sh --ipa",
|
||||
"build:ios:prod": "./scripts/build-ios.sh --prod",
|
||||
"build:ios:prod:run": "./scripts/build-ios.sh --prod --auto-run",
|
||||
"build:ios:release": "./scripts/build-ios.sh --release",
|
||||
"build:ios:release:run": "./scripts/build-ios.sh --release --auto-run",
|
||||
"build:ios:studio": "./scripts/build-ios.sh --studio",
|
||||
"build:ios:sync": "./scripts/build-ios.sh --sync",
|
||||
"build:ios:test": "./scripts/build-ios.sh --test",
|
||||
"build:ios:test:custom": "./scripts/build-ios.sh --test --api-ip",
|
||||
"build:ios:test:run": "./scripts/build-ios.sh --test --auto-run",
|
||||
"build:ios:test:run:custom": "./scripts/build-ios.sh --test --api-ip --auto-run",
|
||||
"build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && bash -c 'source scripts/common.sh && ensure_ios_capacitor_asset_directories' && npx capacitor-assets generate --assetPath resources",
|
||||
"build:web": "./scripts/build-web.sh",
|
||||
"build:web:dev": "./scripts/build-web.sh --dev",
|
||||
"build:web:docker": "./scripts/build-web.sh --docker",
|
||||
"build:web:docker:prod": "./scripts/build-web.sh --docker:prod",
|
||||
"build:web:docker:test": "./scripts/build-web.sh --docker:test",
|
||||
"build:web:prod": "./scripts/build-web.sh --prod",
|
||||
"build:web:serve": "./scripts/build-web.sh --serve",
|
||||
"build:web:serve:prod": "./scripts/build-web.sh --serve --prod",
|
||||
"build:web:serve:test": "./scripts/build-web.sh --serve --test",
|
||||
"build:web:test": "./scripts/build-web.sh --test",
|
||||
"check:android-device": "adb devices | grep -w 'device' || (echo 'No Android device connected' && exit 1)",
|
||||
"check:dependencies": "./scripts/check-dependencies.sh",
|
||||
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
|
||||
"check:prod-bundle": "./scripts/check-prod-bundle.sh",
|
||||
"clean:all": "npm run clean:ios && npm run clean:android && npm run clean:electron",
|
||||
"clean:android": "./scripts/uninstall-android.sh",
|
||||
"clean:electron": "./scripts/build-electron.sh --clean",
|
||||
"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",
|
||||
"deps:update-daily-notification-plugin": "npm install @timesafari/daily-notification-plugin@git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#master",
|
||||
"docker:down": "docker-compose down",
|
||||
"docker:logs": "docker-compose logs -f",
|
||||
"docker:up": "docker-compose up",
|
||||
"docker:up:prod": "npm run build:web:build -- --mode production && docker-compose up production",
|
||||
"docker:up:test": "npm run build:web:build -- --mode test && docker-compose up test",
|
||||
"guard": "bash ./scripts/build-arch-guard.sh",
|
||||
"guard:setup": "npm run prepare && echo 'Build Architecture Guard is now active!'",
|
||||
"guard:test": "bash ./scripts/build-arch-guard.sh --staged",
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"markdown:check": "markdownlint-cli2",
|
||||
"markdown:fix": "markdownlint-cli2 --fix",
|
||||
"markdown:setup": "./scripts/setup-markdown-hooks.sh",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||
"prepare": "husky",
|
||||
"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:android": "node scripts/test-android.js",
|
||||
"test:ios": "node scripts/test-ios.js",
|
||||
"test:mobile": "./scripts/test-mobile.sh",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
"test:unit": "jest",
|
||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||
"type-check": "tsc --noEmit",
|
||||
"type-check:vue": "vue-tsc --noEmit"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,vue,css,json,yml,yaml}": "eslint --fix || true",
|
||||
|
||||
+7
-8
@@ -46,20 +46,19 @@ Assets are automatically generated during the build process using `capacitor-ass
|
||||
|
||||
```bash
|
||||
# Generate assets for all platforms
|
||||
npx capacitor-assets generate
|
||||
npx capacitor-assets generate --assetPath resources
|
||||
|
||||
# Generate assets for specific platform
|
||||
npx capacitor-assets generate --ios
|
||||
npx capacitor-assets generate --android
|
||||
npx capacitor-assets generate --web
|
||||
npx capacitor-assets generate --ios --assetPath resources
|
||||
npx capacitor-assets generate --android --assetPath resources
|
||||
npx capacitor-assets generate --web --assetPath resources
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`resources/` is this project's canonical asset source. `@capacitor/assets`
|
||||
prioritizes a top-level `assets/` directory over `resources/`, so a legacy
|
||||
`assets/` directory can prevent these assets from being discovered. Remove that
|
||||
directory when it is empty or obsolete.
|
||||
`resources/` is this project's canonical asset source. Pass it explicitly with
|
||||
`--assetPath resources`; `@capacitor/assets` otherwise prioritizes a top-level
|
||||
`assets/` directory when one exists, even when that directory is empty.
|
||||
|
||||
## Version Control
|
||||
|
||||
|
||||
+33
-13
@@ -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
|
||||
@@ -649,7 +658,18 @@ fi
|
||||
# we already synced and ran restore-local-plugins.js above, so skip sync here.
|
||||
if [ "$AUTO_RUN" = true ]; then
|
||||
log_step "Auto-running Android app..."
|
||||
safe_execute "Launching app" "npx cap run android --no-sync" || {
|
||||
# With exactly one connected device, target it directly. Otherwise cap run
|
||||
# shows an interactive picker, which without a terminal exits 0 having
|
||||
# launched nothing, so fail instead of reporting a false success.
|
||||
run_devices=$(adb devices | awk 'NR > 1 && $2 == "device" { print $1 }')
|
||||
run_target=""
|
||||
if [ "$(printf '%s\n' "$run_devices" | grep -c .)" = "1" ]; then
|
||||
run_target="--target $run_devices"
|
||||
elif [ ! -t 0 ]; then
|
||||
log_error "Auto-run needs exactly one connected device when not run from a terminal (found: ${run_devices:-none})"
|
||||
exit 9
|
||||
fi
|
||||
safe_execute "Launching app" "npx cap run android --no-sync $run_target" || {
|
||||
log_error "Failed to launch Android app"
|
||||
log_info "You can manually run with: npx cap run android --no-sync"
|
||||
exit 9
|
||||
|
||||
@@ -295,12 +295,7 @@ apply_ios_app_icon_appearances() {
|
||||
# Generate iOS assets (capacitor-assets), then apply optional appearance variants.
|
||||
generate_ios_assets() {
|
||||
ensure_ios_capacitor_asset_directories
|
||||
if [ -d "assets" ]; then
|
||||
log_warn "@capacitor/assets prioritizes the top-level assets/ directory over resources/."
|
||||
log_warn "This project intentionally uses resources/ as the canonical asset source."
|
||||
log_warn "Remove the legacy assets/ directory if it is empty or obsolete."
|
||||
fi
|
||||
npx capacitor-assets generate --ios
|
||||
npx capacitor-assets generate --ios --assetPath resources
|
||||
apply_ios_app_icon_appearances
|
||||
}
|
||||
|
||||
@@ -685,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
|
||||
|
||||
@@ -751,4 +751,4 @@ fi
|
||||
print_footer "iOS Build"
|
||||
|
||||
# Exit with success
|
||||
exit 0
|
||||
exit 0
|
||||
|
||||
Executable
+100
@@ -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="<url>"`. 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
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Restore Local Capacitor Plugins
|
||||
*
|
||||
* This script ensures that local custom plugins (SafeArea and SharedImage)
|
||||
* This script ensures that local custom plugins (SafeArea, SharedImage, NotificationInspector)
|
||||
* are present in capacitor.plugins.json after `npx cap sync` runs.
|
||||
*
|
||||
* The capacitor.plugins.json file is auto-generated by Capacitor and gets
|
||||
@@ -28,6 +28,10 @@ const LOCAL_PLUGINS = [
|
||||
{
|
||||
pkg: 'SharedImage',
|
||||
classpath: 'app.timesafari.sharedimage.SharedImagePlugin'
|
||||
},
|
||||
{
|
||||
pkg: 'NotificationInspector',
|
||||
classpath: 'app.timesafari.notifications.NotificationInspectorPlugin'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ const MIGRATIONS = [
|
||||
},
|
||||
{
|
||||
name: "008_remove_iViewContent_from_contacts",
|
||||
// Recreate contacts without iViewContent: backup, drop, recreate, restore
|
||||
// Recreate contacts without iViewContent (since we use hideTheirContent instead): backup, drop, recreate, restore
|
||||
sql: `
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ export type PendingNotificationInfo = {
|
||||
|
||||
export interface NotificationInspectorPlugin {
|
||||
getPendingNotifications(): Promise<{ pending: PendingNotificationInfo[] }>;
|
||||
/** Android only: whether the build has a Firebase config (-PfirebaseEnabled). */
|
||||
isFirebaseConfigured(): Promise<{ configured: boolean }>;
|
||||
}
|
||||
|
||||
export const NotificationInspector =
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
onMessage,
|
||||
} from "firebase/messaging";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { NotificationInspector } from "@/plugins/NotificationInspectorPlugin";
|
||||
import { handleCapacitorPushNotificationReceived } from "./NativeNotificationService";
|
||||
import { getNotificationApiHeaders } from "./notificationApiAuth";
|
||||
import { deferFcmRegistration } from "./notificationAuthLifecycle";
|
||||
@@ -67,6 +68,24 @@ async function registerRetrievedToken(
|
||||
lastRegisteredFcmToken = trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether PushNotifications.register() is safe to call. Android builds without
|
||||
* -PfirebaseEnabled (FOSS/F-Droid/APK) have no Firebase config, and register()
|
||||
* there throws natively, crashing the app before any JS catch can run.
|
||||
*/
|
||||
async function canRegisterNativePush(): Promise<boolean> {
|
||||
if (Capacitor.getPlatform() !== "android") {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const { configured } = await NotificationInspector.isFirebaseConfigured();
|
||||
return configured;
|
||||
} catch (err) {
|
||||
logger.warn(`${LOG} Could not check Firebase config; skipping push`, err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Most recent FCM token from native/web push registration (for debug UI). */
|
||||
export function getLastKnownFcmToken(): string | null {
|
||||
return lastSeenFcmToken ?? lastRegisteredFcmToken;
|
||||
@@ -80,6 +99,12 @@ export async function reregisterFcmTokenNow(): Promise<string> {
|
||||
throw new Error("FCM registration is only available on native platforms");
|
||||
}
|
||||
|
||||
if (!(await canRegisterNativePush())) {
|
||||
throw new Error(
|
||||
"Push is unavailable: this Android build has no Firebase config (built without -PfirebaseEnabled)",
|
||||
);
|
||||
}
|
||||
|
||||
lastRegisteredFcmToken = null;
|
||||
|
||||
const cached = lastSeenFcmToken?.trim();
|
||||
@@ -290,6 +315,13 @@ async function initializeNativePushAndFirebaseMessagingImpl(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await canRegisterNativePush())) {
|
||||
logger.info(
|
||||
`${LOG} No Firebase config in this Android build; skipping push registration`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await PushNotifications.register();
|
||||
|
||||
if (app) {
|
||||
|
||||
Reference in New Issue
Block a user