Mobile: standalone signed release APK build (config plugin + script)
- plugins/withReleaseSigning.js — Expo config plugin that injects a release signingConfig into android/app/build.gradle at prebuild, reading credentials from credentials/keystore.properties (gitignored). Survives CNG regeneration. - scripts/build-release-apk.sh — generate keystore (once) → prebuild → build → install → emit a shareable cardclaws-demo.apk - .gitignore: exclude /credentials, *.keystore, *.apk (no secrets/binaries) Produces a standalone APK with the JS bundle embedded — runs with no Metro, no laptop, no Wi-Fi. Built, signed, installed, and verified running on a physical device with Metro stopped + the USB route removed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bc6f158f43
commit
eddca62f23
@@ -7,6 +7,11 @@ node_modules/
|
|||||||
/android
|
/android
|
||||||
/ios
|
/ios
|
||||||
|
|
||||||
|
# Signing secrets + build artifacts — NEVER commit
|
||||||
|
/credentials
|
||||||
|
*.keystore
|
||||||
|
*.apk
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const config: ExpoConfig = {
|
|||||||
cameraPermission: "CardClaws uses the camera to capture your card image.",
|
cameraPermission: "CardClaws uses the camera to capture your card image.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"./plugins/withReleaseSigning.js",
|
||||||
],
|
],
|
||||||
experiments: {
|
experiments: {
|
||||||
typedRoutes: true,
|
typedRoutes: true,
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Expo config plugin: inject a release signing config into the generated
|
||||||
|
// android/app/build.gradle so `assembleRelease` produces a properly-signed,
|
||||||
|
// standalone APK. Credentials are read at build time from
|
||||||
|
// credentials/keystore.properties (gitignored) — never baked into source.
|
||||||
|
//
|
||||||
|
// This runs during `expo prebuild`, so release signing is reproducible even
|
||||||
|
// though android/ is regenerated (CNG workflow).
|
||||||
|
|
||||||
|
const { withAppBuildGradle } = require("@expo/config-plugins");
|
||||||
|
|
||||||
|
const RELEASE_SIGNING_CONFIG = `release {
|
||||||
|
def kp = new Properties()
|
||||||
|
def kf = rootProject.file("../credentials/keystore.properties")
|
||||||
|
if (kf.exists()) {
|
||||||
|
kp.load(new FileInputStream(kf))
|
||||||
|
storeFile file(kp["storeFile"])
|
||||||
|
storePassword kp["storePassword"]
|
||||||
|
keyAlias kp["keyAlias"]
|
||||||
|
keyPassword kp["keyPassword"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug {`;
|
||||||
|
|
||||||
|
module.exports = function withReleaseSigning(config) {
|
||||||
|
return withAppBuildGradle(config, (cfg) => {
|
||||||
|
let src = cfg.modResults.contents;
|
||||||
|
if (src.includes("signingConfigs.release")) return cfg; // already applied
|
||||||
|
|
||||||
|
// 1) Add a `release` signing config alongside `debug`.
|
||||||
|
src = src.replace(/signingConfigs \{\s*\n\s*debug \{/, `signingConfigs {\n ${RELEASE_SIGNING_CONFIG}`);
|
||||||
|
|
||||||
|
// 2) Point the release build type at it (only the release block, anchored on
|
||||||
|
// the shrinkResources line that follows it in the template).
|
||||||
|
src = src.replace(
|
||||||
|
/signingConfig signingConfigs\.debug(\s*\n\s*shrinkResources)/,
|
||||||
|
"signingConfig signingConfigs.release$1",
|
||||||
|
);
|
||||||
|
|
||||||
|
cfg.modResults.contents = src;
|
||||||
|
return cfg;
|
||||||
|
});
|
||||||
|
};
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build a standalone, signed release APK (JS bundled in — runs with no Metro,
|
||||||
|
# no laptop). Reproducible: the release signing config is injected at prebuild
|
||||||
|
# by plugins/withReleaseSigning.js, which reads credentials/keystore.properties.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# JAVA_HOME=$(/usr/libexec/java_home -v 17) ./scripts/build-release-apk.sh
|
||||||
|
#
|
||||||
|
# Override the (demo) keystore password via KEYSTORE_PASSWORD if you like.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
PASS="${KEYSTORE_PASSWORD:-cardclaws-demo}"
|
||||||
|
KS="$(pwd)/credentials/cardclaws-release.keystore"
|
||||||
|
|
||||||
|
# 1) Create the release keystore once (gitignored).
|
||||||
|
if [ ! -f "$KS" ]; then
|
||||||
|
echo "Generating release keystore…"
|
||||||
|
mkdir -p credentials
|
||||||
|
keytool -genkeypair -v -keystore "$KS" -alias cardclaws \
|
||||||
|
-keyalg RSA -keysize 2048 -validity 10000 \
|
||||||
|
-storepass "$PASS" -keypass "$PASS" \
|
||||||
|
-dname "CN=CardClaws, O=RedClaw Systems LLC, C=US"
|
||||||
|
cat > credentials/keystore.properties <<EOF
|
||||||
|
storeFile=$KS
|
||||||
|
storePassword=$PASS
|
||||||
|
keyAlias=cardclaws
|
||||||
|
keyPassword=$PASS
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Regenerate native project (applies the signing config plugin).
|
||||||
|
npx expo prebuild --platform android --clean --no-install
|
||||||
|
|
||||||
|
# 3) Build + install the signed release APK to the connected device.
|
||||||
|
npx expo run:android --variant release
|
||||||
|
|
||||||
|
APK="android/app/build/outputs/apk/release/app-release.apk"
|
||||||
|
if [ -f "$APK" ]; then
|
||||||
|
cp "$APK" cardclaws-demo.apk
|
||||||
|
echo "Standalone APK: $(pwd)/cardclaws-demo.apk"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user