// Publishes a live update of the Android app's web part: // 1. builds the Capacitor web bundle (its version comes from the build time), // 2. zips it into updates/android/bundle-.zip, // 3. writes updates/android/version.json (version, zip, SHA-256, minimum native build). // The mobile-client dev/preview server serves updates/android at /m/android/, i.e. // https://dev.aggtaa.com/m/android/version.json. Installed apps pick the update up on the next check. // // minNativeBuild is the versionCode of the latest published APK (updates/android/apk.json, see // publish-android-apk.mjs): after an APK release, web updates go only to that APK or newer, older // installations are offered the APK instead. Publish an APK whenever the native part changes // (plugins, AndroidManifest, Java code). import { execSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { zipSync } from 'fflate'; const root = path.resolve(import.meta.dirname, '..'); const distDir = path.join(root, 'dist-capacitor'); const outDir = path.join(root, 'updates', 'android'); const KEEP_BUNDLES = 3; execSync('pnpm exec vite build --mode capacitor', { cwd: root, stdio: 'inherit' }); const { version } = JSON.parse(readFileSync(path.join(distDir, 'cardy-version.json'), 'utf8')); function collect(dir, prefix = '') { const files = {}; for (const name of readdirSync(dir)) { const full = path.join(dir, name); const rel = prefix ? `${prefix}/${name}` : name; if (statSync(full).isDirectory()) Object.assign(files, collect(full, rel)); else files[rel] = readFileSync(full); } return files; } const zip = Buffer.from(zipSync(collect(distDir), { level: 6 })); const checksum = createHash('sha256').update(zip).digest('hex'); let minNativeBuild = 1; try { minNativeBuild = Number(JSON.parse(readFileSync(path.join(outDir, 'apk.json'), 'utf8')).versionCode) || 1; } catch { // no APK published yet: any installed APK can take the bundle } mkdirSync(outDir, { recursive: true }); const zipName = `bundle-${version}.zip`; writeFileSync(path.join(outDir, zipName), zip); // version.json last: clients never see a version whose zip is not there yet. writeFileSync( path.join(outDir, 'version.json'), JSON.stringify({ version, url: zipName, checksum, minNativeBuild, publishedAt: new Date().toISOString() }, null, 2), ); // Keep a few recent bundles (clients may be mid-download of the previous one). const bundles = readdirSync(outDir) .filter((f) => /^bundle-.+\.zip$/.test(f)) .sort((a, b) => statSync(path.join(outDir, b)).mtimeMs - statSync(path.join(outDir, a)).mtimeMs); for (const old of bundles.slice(KEEP_BUNDLES)) unlinkSync(path.join(outDir, old)); console.log(`\nPublished Android update ${version} (${(zip.length / 1024).toFixed(0)} KiB, native build >= ${minNativeBuild})`); console.log(` ${path.join(outDir, zipName)}`); console.log('Served at /m/android/version.json by the mobile-client dev/preview server.');