feat(waiter): Capacitor 8 Android project, native build mode, signed APK tooling
- capacitor.config.json: appId gr.bonamin.xenia, webDir dist-native, androidScheme http (origin http://localhost - no mixed-content block when calling venue servers over plain HTTP on the LAN) - vite: `--mode native` builds to dist-native with the PWA plugin disabled (no service worker inside the app) - Android: minSdk 24 / target 36; CAMERA permission for QR pairing; cleartext allowed via network_security_config (LAN IPs can't be listed per-domain); allowBackup=false so backups never carry login tokens - Release signing reads ~/.xenia/keystore.properties (override with XENIA_KEYSTORE_PROPS) - the key never enters the repo; versionName 1.0.0 / versionCode 1 with a bump-both rule for sideloaded updates - npm scripts build:native, apk:debug, apk:release (scripts/build-apk.mjs); APKs land in releases/ (gitignored); release APK is also copied to public/downloads/ so venue servers serve it at /downloads/xenia-waiter.apk - waiter nginx: /downloads/ served as application/vnd.android.package-archive, real 404 when missing (never falls through to index.html) - launcher icons + splash generated from the app icon (assets/ is the source) - .gitattributes: gradlew LF, *.bat CRLF Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// Build the Android APK (plan step 4).
|
||||
//
|
||||
// node scripts/build-apk.mjs debug → debug APK (WebView debuggable, auto-signed)
|
||||
// node scripts/build-apk.mjs release → release APK signed with ~/.xenia keystore,
|
||||
// also copied to public/downloads/ for the web build
|
||||
//
|
||||
// Assumes `npm run build:native` already ran (the npm scripts chain it). The APK
|
||||
// is copied to waiter_pwa/releases/xenia-waiter-<versionName>(-debug).apk.
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const variant = process.argv[2] === 'release' ? 'release' : 'debug'
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const androidDir = join(root, 'android')
|
||||
|
||||
const gradleFile = readFileSync(join(androidDir, 'app', 'build.gradle'), 'utf8')
|
||||
const versionName = gradleFile.match(/versionName\s+"([^"]+)"/)?.[1] ?? '0.0.0'
|
||||
const versionCode = gradleFile.match(/versionCode\s+(\d+)/)?.[1] ?? '0'
|
||||
|
||||
if (variant === 'release') {
|
||||
const props = process.env.XENIA_KEYSTORE_PROPS
|
||||
|| join(process.env.USERPROFILE || process.env.HOME || '', '.xenia', 'keystore.properties')
|
||||
if (!existsSync(props)) {
|
||||
console.error(`No keystore properties at ${props} — refusing to build an unsigned release APK.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Full path: cmd.exe doesn't look in the working directory for gradlew.bat
|
||||
const gradlew = join(androidDir, process.platform === 'win32' ? 'gradlew.bat' : 'gradlew')
|
||||
const task = variant === 'release' ? 'assembleRelease' : 'assembleDebug'
|
||||
console.log(`> gradlew ${task} (versionName ${versionName}, versionCode ${versionCode})`)
|
||||
execFileSync(gradlew, [task, '--console=plain'], { cwd: androidDir, stdio: 'inherit', shell: process.platform === 'win32' })
|
||||
|
||||
const built = join(androidDir, 'app', 'build', 'outputs', 'apk', variant, `app-${variant}.apk`)
|
||||
const outDir = join(root, 'releases')
|
||||
mkdirSync(outDir, { recursive: true })
|
||||
const out = join(outDir, `xenia-waiter-${versionName}${variant === 'debug' ? '-debug' : ''}.apk`)
|
||||
copyFileSync(built, out)
|
||||
console.log(`\nAPK: ${out}`)
|
||||
|
||||
// Release APKs are also bundled into the web build, so every venue server hands
|
||||
// out the app at http://<LAN IP>/downloads/xenia-waiter.apk (the browser-mode
|
||||
// install banner links there). Build the Docker images AFTER this step.
|
||||
if (variant === 'release') {
|
||||
const downloads = join(root, 'public', 'downloads')
|
||||
mkdirSync(downloads, { recursive: true })
|
||||
copyFileSync(built, join(downloads, 'xenia-waiter.apk'))
|
||||
console.log('Bundled for venue servers: public/downloads/xenia-waiter.apk')
|
||||
}
|
||||
Reference in New Issue
Block a user