Compare commits
3 Commits
d54d5b32f1
...
v1.15
| Author | SHA1 | Date | |
|---|---|---|---|
| 65efb632db | |||
| b260e4e762 | |||
| e02ef05c02 |
@@ -5,7 +5,36 @@ on:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
# Fast fail: lockfile sync, types, lint — catches in ~2 min what would
|
||||
# otherwise kill the APK build twenty minutes in.
|
||||
runs-on: desktop
|
||||
container:
|
||||
image: node:22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git init -q .
|
||||
git fetch -q --depth 1 "https://git.rehbock.xyz/${{ github.repository }}.git" "${{ github.sha }}"
|
||||
git checkout -q FETCH_HEAD
|
||||
- name: Lockfile + typecheck + lint
|
||||
run: |
|
||||
cd app
|
||||
npm ci --no-audit --no-fund
|
||||
npx tsc --noEmit
|
||||
npx eslint .
|
||||
- name: Notify failure
|
||||
if: failure()
|
||||
env:
|
||||
NTFY_TOKEN: ${{ secrets.NTFY_TOKEN }}
|
||||
run: |
|
||||
[ -n "$NTFY_TOKEN" ] || { echo "NTFY_TOKEN not set — skipping notification"; exit 0; }
|
||||
curl -s -H "Authorization: Bearer $NTFY_TOKEN" -H "Title: gtd CI: check failed" -H "Priority: high" \
|
||||
-d "typecheck/lint/lockfile failed at ${{ github.sha }} — https://git.rehbock.xyz/marcus/gtd/actions" \
|
||||
https://ntfy.rehbock.xyz/gtd-ci
|
||||
|
||||
build:
|
||||
needs: check
|
||||
# RN/NDK builds starve the 4-core VPS — run on the desktop runner
|
||||
# (marcusDesktop). Jobs queue while the desktop is off and start when it
|
||||
# comes online; switch back to ubuntu-latest to build on the VPS.
|
||||
@@ -81,3 +110,13 @@ jobs:
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@$APK;type=application/vnd.android.package-archive" >/dev/null
|
||||
echo "published $TAG"
|
||||
|
||||
- name: Notify failure
|
||||
if: failure()
|
||||
env:
|
||||
NTFY_TOKEN: ${{ secrets.NTFY_TOKEN }}
|
||||
run: |
|
||||
[ -n "$NTFY_TOKEN" ] || { echo "NTFY_TOKEN not set — skipping notification"; exit 0; }
|
||||
curl -s -H "Authorization: Bearer $NTFY_TOKEN" -H "Title: gtd CI: APK build failed" -H "Priority: high" \
|
||||
-d "release build failed at ${{ github.sha }} — https://git.rehbock.xyz/marcus/gtd/actions" \
|
||||
https://ntfy.rehbock.xyz/gtd-ci
|
||||
|
||||
12
.githooks/pre-push
Executable file
12
.githooks/pre-push
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Reject pushes whose lockfile CI's npm (node 22) would refuse — a lock
|
||||
# written by a newer local npm fails `npm ci` on the runner half a build in.
|
||||
# Enable with: git config core.hooksPath .githooks
|
||||
set -e
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
if git diff --quiet origin/main...HEAD -- app/package.json app/package-lock.json 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
echo "pre-push: package.json/lock changed — validating with CI's npm (node:22)…"
|
||||
docker run --rm -v "$root/app:/w" -w /w node:22 npm ci --dry-run --no-audit --no-fund >/dev/null
|
||||
echo "pre-push: lockfile OK"
|
||||
@@ -22,6 +22,15 @@ Minimal Getting Things Done app. One Expo/React Native codebase for iPhone, Andr
|
||||
cd app && npx expo start # scan QR with Expo Go (iPhone or Pixel)
|
||||
```
|
||||
|
||||
- **Node 22 only** (`app/.nvmrc`): CI's npm 10 rejects lockfiles written by newer npm. Never run bare `npm install` to change deps — use `npm run lock` (regenerates the lock via a `node:22` container), or run npm from node 22.
|
||||
- **Hook**: `git config core.hooksPath .githooks` enables a pre-push check that validates the lockfile against CI's npm before it can break a build.
|
||||
- **Local Android builds** need JDK 21 (AGP chokes on newer): pinned via `org.gradle.java.home` in `~/.gradle/gradle.properties` → `~/.local/share/java/jdk-21.0.12+8`.
|
||||
- **`expo prebuild` is safe to re-run**: `app/plugins/withAndroidRelease.js` re-injects the release signing, env-driven versionCode/Name, and gradle.properties tuning into the committed `android/`.
|
||||
|
||||
## Release (Android APK → Obtainium)
|
||||
|
||||
Every push to `main` runs `.gitea/workflows/release.yml` on the desktop runner: fast check job (lockfile/types/lint), then a signed `assembleRelease`, published as Gitea release `v1.<run>` with the APK attached. Phones track it with Obtainium → source `https://git.rehbock.xyz/marcus/gtd` (Forgejo/Gitea source type). CI failures push to ntfy topic `gtd-ci` when the `NTFY_TOKEN` repo secret is set.
|
||||
|
||||
## Deploy
|
||||
|
||||
```sh
|
||||
|
||||
1
app/.nvmrc
Normal file
1
app/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
22
|
||||
@@ -1,18 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M0,0h108v108H0z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="54" android:startY="0"
|
||||
android:endX="54" android:endY="108"
|
||||
android:startColor="#1A1F2B"
|
||||
android:endColor="#0B0D12" />
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splashscreen_background"/>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splashscreen_logo"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- The four GTD quadrants (next/waiting/someday/projects) with a check in
|
||||
"next" — matches the app's home screen. Grid spans 32..76 to stay inside
|
||||
the adaptive-icon safe zone. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<!-- top-left: next (green) -->
|
||||
<path android:pathData="M37,32 h11 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-11 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="34" android:startY="34"
|
||||
android:endX="53" android:endY="53"
|
||||
android:startColor="#4FD48C"
|
||||
android:endColor="#2E9E60" />
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<!-- top-right: waiting (amber) -->
|
||||
<path android:pathData="M60,32 h11 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-11 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="57" android:startY="34"
|
||||
android:endX="76" android:endY="53"
|
||||
android:startColor="#EDBB55"
|
||||
android:endColor="#C8912B" />
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<!-- bottom-left: someday (violet) -->
|
||||
<path android:pathData="M37,55 h11 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-11 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="34" android:startY="57"
|
||||
android:endX="53" android:endY="76"
|
||||
android:startColor="#AC9FF0"
|
||||
android:endColor="#7E6DD6" />
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<!-- bottom-right: projects (blue) -->
|
||||
<path android:pathData="M60,55 h11 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-11 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="57" android:startY="57"
|
||||
android:endX="76" android:endY="76"
|
||||
android:startColor="#6FAAEE"
|
||||
android:endColor="#3F7DC4" />
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<!-- checkmark in the green quadrant -->
|
||||
<path
|
||||
android:pathData="M38,42.8 L42,46.8 L48.5,38.8"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="3"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000" />
|
||||
</vector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M37,33.5 h11 a3.5,3.5 0 0 1 3.5,3.5 v11 a3.5,3.5 0 0 1 -3.5,3.5 h-11 a3.5,3.5 0 0 1 -3.5,-3.5 v-11 a3.5,3.5 0 0 1 3.5,-3.5 z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#00000000" />
|
||||
<path
|
||||
android:pathData="M60,33.5 h11 a3.5,3.5 0 0 1 3.5,3.5 v11 a3.5,3.5 0 0 1 -3.5,3.5 h-11 a3.5,3.5 0 0 1 -3.5,-3.5 v-11 a3.5,3.5 0 0 1 3.5,-3.5 z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#00000000" />
|
||||
<path
|
||||
android:pathData="M37,56.5 h11 a3.5,3.5 0 0 1 3.5,3.5 v11 a3.5,3.5 0 0 1 -3.5,3.5 h-11 a3.5,3.5 0 0 1 -3.5,-3.5 v-11 a3.5,3.5 0 0 1 3.5,-3.5 z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#00000000" />
|
||||
<path
|
||||
android:pathData="M60,56.5 h11 a3.5,3.5 0 0 1 3.5,3.5 v11 a3.5,3.5 0 0 1 -3.5,3.5 h-11 a3.5,3.5 0 0 1 -3.5,-3.5 v-11 a3.5,3.5 0 0 1 3.5,-3.5 z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#FFFFFF" />
|
||||
</vector>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -28,8 +28,6 @@ android.enablePngCrunchInReleaseBuilds=true
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
||||
# arm64 only: Pixel is arm64-v8a; x86/v7a builds are wasted time + APK bloat.
|
||||
# Add back x86_64 temporarily if an emulator build is ever needed.
|
||||
reactNativeArchitectures=arm64-v8a
|
||||
|
||||
# Use this property to enable support to the new architecture.
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"./plugins/withAndroidRelease",
|
||||
"expo-router",
|
||||
[
|
||||
"react-native-android-widget",
|
||||
|
||||
83
app/package-lock.json
generated
83
app/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-community/netinfo": "12.0.1",
|
||||
"expo": "~57.0.11",
|
||||
"expo-application": "~57.0.2",
|
||||
"expo-constants": "~57.0.9",
|
||||
"expo-crypto": "~57.0.1",
|
||||
"expo-device": "~57.0.1",
|
||||
@@ -43,6 +44,9 @@
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-expo": "~57.0.1",
|
||||
"typescript": "~6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "22.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@@ -79,7 +83,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
@@ -964,6 +967,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
|
||||
"integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.29.7"
|
||||
},
|
||||
@@ -979,6 +983,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
|
||||
"integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.29.7"
|
||||
},
|
||||
@@ -1010,6 +1015,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz",
|
||||
"integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.29.7"
|
||||
},
|
||||
@@ -1199,6 +1205,31 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
|
||||
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
|
||||
@@ -1206,6 +1237,7 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
@@ -2560,6 +2592,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.2.tgz",
|
||||
"integrity": "sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.24.7",
|
||||
@@ -2722,6 +2755,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.2.tgz",
|
||||
"integrity": "sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@react-native/babel-preset": "0.86.2",
|
||||
@@ -2740,6 +2774,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.2.tgz",
|
||||
"integrity": "sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@react-native/js-polyfills": "0.86.2",
|
||||
"@react-native/metro-babel-transformer": "0.86.2",
|
||||
@@ -2797,6 +2832,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@@ -2816,6 +2852,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -2828,6 +2865,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -2841,7 +2879,8 @@
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
"version": "6.9.1",
|
||||
@@ -2896,7 +2935,8 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
@@ -2963,7 +3003,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
||||
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -3037,7 +3076,6 @@
|
||||
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.67.0",
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
@@ -3628,7 +3666,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4228,7 +4265,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.44",
|
||||
"caniuse-lite": "^1.0.30001806",
|
||||
@@ -4882,7 +4918,8 @@
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
@@ -5166,7 +5203,6 @@
|
||||
"integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -5387,7 +5423,6 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -5750,7 +5785,6 @@
|
||||
"resolved": "https://registry.npmjs.org/expo/-/expo-57.0.11.tgz",
|
||||
"integrity": "sha512-R97257N39Dw0kQFuI4/RvYx95GQ+dmePdo8hxcMOjDxAT4VcCckjILJeAWCE19Jxjb92hZ5NDXAfDPkkV1RB9w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@expo/cli": "^57.0.13",
|
||||
@@ -5808,6 +5842,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/expo-application": {
|
||||
"version": "57.0.2",
|
||||
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-57.0.2.tgz",
|
||||
"integrity": "sha512-q31YwcXyymviAmdrtDfAg3Dld4VMxLCNAfgMHip7vZpPX4lzF/AfwsywqBJUAjQnJknzaNAkzqvMVjO5XmKYDA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-asset": {
|
||||
"version": "57.0.9",
|
||||
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.9.tgz",
|
||||
@@ -5828,7 +5871,6 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.9.tgz",
|
||||
"integrity": "sha512-Y47sGiF+U8fwicUSPdJPjB27PuU+FgLK4Mpai0ksZF5hv8jNN3HBqKuDNxsiu70uHBKf80TaR4gwMPh0pmETiQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@expo/env": "~2.4.2"
|
||||
},
|
||||
@@ -5873,7 +5915,6 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz",
|
||||
"integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fontfaceobserver": "^2.1.0"
|
||||
},
|
||||
@@ -5938,7 +5979,6 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.5.tgz",
|
||||
"integrity": "sha512-SmJI3wr0EVfeKPGf+Qgr9gUbrXk8mM0ATqYLvAX/EAzawDjohPzMJ5pTt7TYMX0Wknj4XCtyCQrGhnERMXT6cQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"expo-constants": "~57.0.9",
|
||||
"invariant": "^2.2.4"
|
||||
@@ -5998,7 +6038,6 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-router/-/expo-router-57.0.11.tgz",
|
||||
"integrity": "sha512-kE2hz4lLkddZ94vN4nmbq5aXeo0t6FaZ8xJn/Hyn8/dQPsGlvDK0j4ZVayEUUdNmkNHsBVihgf1bl/2rCsYEzA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@expo/log-box": "^57.0.2",
|
||||
"@expo/metro-runtime": "^57.0.8",
|
||||
@@ -6073,7 +6112,6 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.1.tgz",
|
||||
"integrity": "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.16.0"
|
||||
}
|
||||
@@ -6337,7 +6375,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
|
||||
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -8331,6 +8368,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -9655,7 +9693,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -9675,7 +9712,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -9712,7 +9748,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.2.tgz",
|
||||
"integrity": "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@react-native/assets-registry": "0.86.2",
|
||||
"@react-native/codegen": "0.86.2",
|
||||
@@ -9818,7 +9853,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.32.0.tgz",
|
||||
"integrity": "sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"@types/react-test-renderer": "^19.1.0",
|
||||
@@ -9845,7 +9879,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.5.1.tgz",
|
||||
"integrity": "sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"react-native-is-edge-to-edge": "^1.3.1",
|
||||
"semver": "^7.7.3"
|
||||
@@ -9861,7 +9894,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz",
|
||||
"integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
@@ -9886,7 +9918,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.6",
|
||||
"@react-native/normalize-colors": "^0.74.1",
|
||||
@@ -9919,7 +9950,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.10.1.tgz",
|
||||
"integrity": "sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-arrow-functions": "^7.27.1",
|
||||
"@babel/plugin-transform-class-properties": "^7.28.6",
|
||||
@@ -9955,7 +9985,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -11228,7 +11257,6 @@
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -11846,7 +11874,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-community/netinfo": "12.0.1",
|
||||
"expo": "~57.0.11",
|
||||
"expo-application": "~57.0.2",
|
||||
"expo-constants": "~57.0.9",
|
||||
"expo-crypto": "~57.0.1",
|
||||
"expo-device": "~57.0.1",
|
||||
@@ -39,7 +40,11 @@
|
||||
"eslint-config-expo": "~57.0.1",
|
||||
"typescript": "~6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "22.x"
|
||||
},
|
||||
"scripts": {
|
||||
"lock": "docker run --rm -v $PWD:/w -w /w node:22 npm install --package-lock-only --no-audit --no-fund",
|
||||
"start": "expo start",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "expo run:android",
|
||||
|
||||
59
app/plugins/withAndroidRelease.js
Normal file
59
app/plugins/withAndroidRelease.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// Makes `expo prebuild` safe to re-run: re-applies the hand-maintained Android
|
||||
// release config that a fresh prebuild would otherwise reset to template
|
||||
// defaults (android/ is committed; CI builds it and relies on all of this).
|
||||
const { withAppBuildGradle, withGradleProperties } = require("expo/config-plugins");
|
||||
|
||||
const RELEASE_SIGNING = ` release {
|
||||
// Keystore path + password come from env (CI) or ~/.android-keys (local).
|
||||
def ksPath = System.getenv("KEYSTORE_FILE") ?: "\${System.properties['user.home']}/.android-keys/gtd-release.jks"
|
||||
def passFile = new File("\${System.properties['user.home']}/.android-keys/gtd-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") ?: "gtd"
|
||||
keyPassword System.getenv("KEY_PASSWORD") ?: ksPass
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function patchBuildGradle(gradle) {
|
||||
gradle = gradle.replace(/versionCode \d+\n/, 'versionCode ((System.getenv("VERSION_CODE") ?: "1").toInteger())\n');
|
||||
gradle = gradle.replace(/versionName "[^"]*"\n/, 'versionName (System.getenv("VERSION_NAME") ?: "1.0-dev")\n');
|
||||
if (!gradle.includes("KEYSTORE_FILE")) {
|
||||
// Insert the release signing config right after the debug one.
|
||||
gradle = gradle.replace(
|
||||
/(signingConfigs \{\n debug \{[\s\S]*?\n \}\n)/,
|
||||
`$1${RELEASE_SIGNING}`,
|
||||
);
|
||||
}
|
||||
// Release builds use the release key when present, debug key otherwise.
|
||||
gradle = gradle.replace(
|
||||
/(release \{\n)(?:\s*\/\/[^\n]*\n)*\s*signingConfig signingConfigs\.debug\n/,
|
||||
"$1 signingConfig signingConfigs.release.storeFile ? signingConfigs.release : signingConfigs.debug\n",
|
||||
);
|
||||
return gradle;
|
||||
}
|
||||
|
||||
const GRADLE_PROPS = [
|
||||
// RN/NDK builds want headroom; the template default is 2 GB.
|
||||
{ key: "org.gradle.jvmargs", value: "-Xmx8192m -XX:MaxMetaspaceSize=1024m" },
|
||||
// arm64 only: Pixel is arm64-v8a; x86/v7a builds are wasted time + APK bloat.
|
||||
{ key: "reactNativeArchitectures", value: "arm64-v8a" },
|
||||
];
|
||||
|
||||
module.exports = function withAndroidRelease(config) {
|
||||
config = withAppBuildGradle(config, (c) => {
|
||||
c.modResults.contents = patchBuildGradle(c.modResults.contents);
|
||||
return c;
|
||||
});
|
||||
config = withGradleProperties(config, (c) => {
|
||||
for (const { key, value } of GRADLE_PROPS) {
|
||||
const existing = c.modResults.find((p) => p.type === "property" && p.key === key);
|
||||
if (existing) existing.value = value;
|
||||
else c.modResults.push({ type: "property", key, value });
|
||||
}
|
||||
return c;
|
||||
});
|
||||
return config;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Application from "expo-application";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ScrollView, Text } from "react-native";
|
||||
@@ -44,6 +45,10 @@ export default function Settings() {
|
||||
/>
|
||||
<Button title="Save & test" onPress={save} />
|
||||
{status && <Text style={{ color: C.danger }}>{status}</Text>}
|
||||
{/* CI stamps versionName from the release tag — confirms which build Obtainium installed. */}
|
||||
<Text style={{ color: C.muted, fontSize: 13, textAlign: "center", marginTop: 32 }}>
|
||||
GTD v{Application.nativeApplicationVersion ?? "dev"}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user