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
npm install
```
| 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 |
2. Start the app
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.
```bash
npx expo start
```
## Development
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [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).
## Get a fresh project
When you're ready, run:
```bash
npm run reset-project
```sh
npm install
npx expo start # scan the QR with Expo Go (iOS or Android)
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
Log in with the same Basic-auth credentials as the website.
### Other setup steps
## Android release APK
- 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/)
Signing expects (auto-skipped if absent, falling back to the debug key):
## Learn more
- keystore `~/.android-keys/health-app-release.jks` (alias `health`), or `$HEALTH_KEYSTORE`
- password in `~/.android-keys/health-app-release.password`, or `$KEYSTORE_PASSWORD`
To learn more about developing your project with Expo, look at the following resources:
The signing config is injected by `plugins/with-release-signing.js` at prebuild.
- [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.
```sh
npx expo prebuild --platform android
cd android && ./gradlew assembleRelease
# → android/app/build/outputs/apk/release/app-release.apk
```
## Join the community
Install on GrapheneOS by copying the APK over (or via Obtainium once a
Gitea release exists).
Join our community of developers creating universal apps.
## iOS
- [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.
No Mac is available in this setup, so there is no local iOS build. Options:
- **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.
The `ios.bundleIdentifier` is already set to `xyz.rehbock.health`.

View File

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

View File

@@ -36,8 +36,8 @@
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"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;
});
};