Files
bonaminandClaude Opus 5.5 0b0f5c60c3 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>
2026-09-28 13:31:23 +03:00

53 lines
2.6 KiB
JavaScript

// 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')
}