CI hardening: fast-fail check job (lockfile/tsc/eslint) gates the APK build + optional ntfy alerts; config plugin makes 'expo prebuild' preserve release signing/versioning; node 22 pinned with docker lock script + pre-push hook
All checks were successful
Build & Release APK / check (push) Successful in 16s
Build & Release APK / build (push) Successful in 2m27s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 00:39:29 -07:00
parent e02ef05c02
commit b260e4e762
13 changed files with 139 additions and 118 deletions

View 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;
};