feat(android): manual signed-APK CI workflow
Add .gitea/workflows/android-build.yaml (workflow_dispatch + confirm=build, master-only; mirrors prod-deploy): builds the native SPA, bundles the offline dicts, assembles a release APK as a run artifact. JDK 21 via setup-java; the Android SDK is host-provisioned (host-executor runner) with a fail-fast verify that also checks the runner user's access. Signing degrades gracefully — no keystore => unsigned release APK, not a failure. Wire ui/android/app/build.gradle: versionCode/versionName from the release tag (-P props; '=' assignment, not the command form that binds .toInteger() to the DSL setter's null return), plus a guarded signingConfigs.release from env. Rename VITE_STORE_URL -> VITE_RUSTORE_URL (empty until publish). Bake the E as-built into ANDROID_PLAN.md.
This commit is contained in:
@@ -3,12 +3,22 @@ apply plugin: 'com.android.application'
|
||||
android {
|
||||
namespace = "ru.eruditgame.app"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
|
||||
// Release signing is supplied by the android-build CI workflow via env: it decodes the keystore
|
||||
// secret to a file and points ANDROID_KEYSTORE_FILE at it. A local build or a keyless CI run leaves
|
||||
// it unset, so the release APK is produced UNSIGNED rather than failing — assembleDebug and
|
||||
// assembleRelease both build without the keystore. The keystore is never committed (see .gitignore).
|
||||
def keystoreFile = System.getenv('ANDROID_KEYSTORE_FILE')
|
||||
def hasKeystore = keystoreFile != null && !keystoreFile.isEmpty() && file(keystoreFile).exists()
|
||||
|
||||
defaultConfig {
|
||||
applicationId "ru.eruditgame.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
// Deterministic from the release tag by the android-build workflow (vMA.MI.PA ->
|
||||
// MA*1_000_000 + MI*1_000 + PA); the defaults keep a local assembleDebug building.
|
||||
versionCode = (project.findProperty('versionCode') ?: '1').toInteger()
|
||||
versionName = (project.findProperty('versionName') ?: '0.0.0').toString()
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
@@ -16,10 +26,27 @@ android {
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
// Populated only when the workflow provided a keystore (see hasKeystore above); left empty
|
||||
// otherwise so it is never referenced with a null storeFile.
|
||||
if (hasKeystore) {
|
||||
storeFile file(keystoreFile)
|
||||
storePassword System.getenv('ANDROID_KEYSTORE_PASSWORD')
|
||||
keyAlias System.getenv('ANDROID_KEY_ALIAS')
|
||||
keyPassword System.getenv('ANDROID_KEY_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
// Sign only when a keystore was supplied; a keyless release build stays unsigned instead
|
||||
// of failing.
|
||||
if (hasKeystore) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Non-dismissable "update required" cover. Shown when the gateway has refused a foreground call
|
||||
// as too old (update.svelte.ts — the client-version gate). Unlike the maintenance overlay this is
|
||||
// terminal: an installed build cannot become compatible without an actual update, so its one
|
||||
// action takes the user to the fix — the store listing on a native build (VITE_STORE_URL, opened
|
||||
// action takes the user to the fix — the store listing on a native build (VITE_RUSTORE_URL, opened
|
||||
// in the system browser / store app) or a plain reload on the web (which fetches the current
|
||||
// client). Mirrors MaintenanceOverlay.svelte's look.
|
||||
import { updateRequired } from '../lib/update.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
const ch = clientChannel();
|
||||
if (ch === 'android' || ch === 'ios') {
|
||||
// '_system' hands the URL to the OS (the store app / external browser) rather than the WebView.
|
||||
const url = import.meta.env.VITE_STORE_URL;
|
||||
const url = import.meta.env.VITE_RUSTORE_URL;
|
||||
if (url) window.open(url, '_system');
|
||||
} else {
|
||||
location.reload();
|
||||
|
||||
Vendored
+3
-2
@@ -7,8 +7,9 @@ interface ImportMetaEnv {
|
||||
readonly VITE_GATEWAY_URL?: string;
|
||||
/** "1" hides the in-app-currency purchase actions in the thin native MVP that defers store billing. */
|
||||
readonly VITE_PAYMENTS_DISABLED?: string;
|
||||
/** Store listing URL the native update overlay opens for its "update" action (e.g. the RuStore page). */
|
||||
readonly VITE_STORE_URL?: string;
|
||||
/** RuStore listing URL the native update overlay opens for its "update" action. Empty until the app
|
||||
* is published (the button then no-ops); the version gate is dormant in the MVP so it never fires yet. */
|
||||
readonly VITE_RUSTORE_URL?: string;
|
||||
/** Bundled-dictionary version the native offline path requests; matches the packaged DAWG files. */
|
||||
readonly VITE_DICT_VERSION?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user