Add release-signing config plugin, README, memory of PWA parity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 16:40:54 -07:00
parent 2a5c956c32
commit d6de6dedf1
4 changed files with 84 additions and 41 deletions

View File

@@ -1,56 +1,61 @@
# Welcome to your Expo app 👋 # health-app
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). Cross-platform (iOS + Android) Expo app — native front end for the private
health dashboard at **https://health.rehbock.xyz**.
## Get started The app is a pure client: all data lives on the server and is fetched live over
HTTPS with HTTP Basic auth. Nothing is stored on the device except the auth
token (in the platform keychain via `expo-secure-store`).
1. Install dependencies ## Screens
```bash | Screen | Data source |
| --- | --- |
| Today | overview cards + clinical priorities (`health-meta.json`) |
| Blood | `/api/health/v1/biomarkers` + `/draws` — search, flags, sparklines |
| Sleep | `/api/health/v1/sleep` + `sleep-data.json` — stacked stage chart |
| Train | `/api/health/v1/workouts` — weekly volume chart, expandable sessions |
| Body | `dexa-scans.json` — DXA summary with deltas, regions, indices |
| Mind | `/api/meditation/v1/stats` + `/sessions` |
| DNA | `ancestry-traits.json` — 99 AncestryDNA traits |
Ported from the PWA at `health.rehbock.xyz/app/`
(repo `marcus/health.rehbock.xyz`, `app/index.html`); charts are
react-native-svg, tap a bar for the night/week detail.
## Development
```sh
npm install npm install
npx expo start # scan the QR with Expo Go (iOS or Android)
``` ```
2. Start the app Log in with the same Basic-auth credentials as the website.
```bash ## Android release APK
npx expo start
Signing expects (auto-skipped if absent, falling back to the debug key):
- keystore `~/.android-keys/health-app-release.jks` (alias `health`), or `$HEALTH_KEYSTORE`
- password in `~/.android-keys/health-app-release.password`, or `$KEYSTORE_PASSWORD`
The signing config is injected by `plugins/with-release-signing.js` at prebuild.
```sh
npx expo prebuild --platform android
cd android && ./gradlew assembleRelease
# → android/app/build/outputs/apk/release/app-release.apk
``` ```
In the output, you'll find options to open the app in a Install on GrapheneOS by copying the APK over (or via Obtainium once a
Gitea release exists).
- [development build](https://docs.expo.dev/develop/development-builds/introduction/) ## iOS
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). No Mac is available in this setup, so there is no local iOS build. Options:
## Get a fresh project - **Expo Go** (zero setup): `npx expo start`, scan the QR from the iPhone.
- **EAS Build** (real install): `npx eas build -p ios` — needs an Apple
Developer account; produces a TestFlight/ad-hoc build in the cloud.
When you're ready, run: The `ios.bundleIdentifier` is already set to `xyz.rehbock.health`.
```bash
npm run reset-project
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
### Other setup steps
- To set up ESLint for linting, run `npx expo lint`, or follow our guide on ["Using ESLint and Prettier"](https://docs.expo.dev/guides/using-eslint/)
- If you'd like to set up unit testing, follow our guide on ["Unit Testing with Jest"](https://docs.expo.dev/develop/unit-testing/)
- Learn more about the TypeScript setup in this template in our guide on ["Using TypeScript"](https://docs.expo.dev/guides/typescript/)
## Learn more
To learn more about developing your project with Expo, look at the following resources:
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Join the community
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.

View File

@@ -30,6 +30,7 @@
"plugins": [ "plugins": [
"expo-router", "expo-router",
"expo-secure-store", "expo-secure-store",
"./plugins/with-release-signing.js",
[ [
"expo-splash-screen", "expo-splash-screen",
{ {

View File

@@ -36,8 +36,8 @@
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
"reset-project": "node ./scripts/reset-project.js", "reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android", "android": "expo run:android",
"ios": "expo start --ios", "ios": "expo run:ios",
"web": "expo start --web", "web": "expo start --web",
"lint": "expo lint" "lint": "expo lint"
}, },

View File

@@ -0,0 +1,37 @@
/* Injects a release signing config into the prebuilt android project.
Keystore: $HEALTH_KEYSTORE or ~/.android-keys/health-app-release.jks
Password: $KEYSTORE_PASSWORD or ~/.android-keys/health-app-release.password
Falls back to the debug keystore when neither exists. */
const { withAppBuildGradle } = require('expo/config-plugins');
const SIGNING = ` release {
// keystore lives outside the repo; password from env or ~/.android-keys
def ksPath = System.getenv("HEALTH_KEYSTORE") ?: "\${System.properties['user.home']}/.android-keys/health-app-release.jks"
def passFile = new File("\${System.properties['user.home']}/.android-keys/health-app-release.password")
def ksPass = System.getenv("KEYSTORE_PASSWORD") ?: (passFile.exists() ? passFile.text.trim() : null)
if (new File(ksPath).exists() && ksPass != null) {
storeFile file(ksPath)
storePassword ksPass
keyAlias System.getenv("KEY_ALIAS") ?: "health"
keyPassword ksPass
}
}
}`;
module.exports = function withReleaseSigning(config) {
return withAppBuildGradle(config, (cfg) => {
let g = cfg.modResults.contents;
if (!g.includes('def ksPath')) {
g = g.replace(
/(signingConfigs \{[\s\S]*?keyPassword 'android'\n {8}\}\n)( {4}\})/,
`$1${SIGNING}`,
);
g = g.replace(
/\/\/ Caution![^\n]*\n\s*\/\/ see https:\/\/reactnative\.dev[^\n]*\n\s*signingConfig signingConfigs\.debug/,
'signingConfig signingConfigs.release.storeFile ? signingConfigs.release : signingConfigs.debug',
);
cfg.modResults.contents = g;
}
return cfg;
});
};