#!/usr/bin/env node /** * Inject the Android release signing configuration. * * WHY THIS EXISTS * ─────────────── * The Android project (android/) is generated and NOT tracked in git — * `npm run android:init` recreates it with `cap add android`. Release signing * therefore has to be re-applied after every regeneration, and its secrets must * never be committed. Previously the keystore path, alias and passwords were * hardcoded inline in android/app/build.gradle. * * This script (idempotent, part of the android build, same pattern as the other * scripts/inject-*.mjs helpers): * 1. Removes any previously managed signing block. * 2. Removes a legacy hardcoded signingConfigs block (inline passwords). * 3. Writes a managed signingConfigs block into android/app/build.gradle that * reads secrets from the gitignored keystore.properties at the repo root. * * It exits quietly (0) when android/ isn't generated yet, or when * keystore.properties is absent — so debug-only builds without a keystore still * work. */ import { readFileSync, writeFileSync, existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const buildGradle = join(root, 'android', 'app', 'build.gradle'); const keystoreProps = join(root, 'keystore.properties'); const BEGIN = '// >>> managed by scripts/inject-signing-config.mjs'; const END = '// <<< managed by scripts/inject-signing-config.mjs <<<'; if (!existsSync(buildGradle)) { console.log('[inject-signing-config] android/ not generated yet — skipping.'); process.exit(0); } if (!existsSync(keystoreProps)) { console.log('[inject-signing-config] keystore.properties not found — skipping release signing.'); process.exit(0); } const managedLines = [ `${BEGIN} >>>`, '// Release signing is read from keystore.properties at the repo root', '// (gitignored). Edit that file, not this block.', 'def _keystoreProps = new Properties()', "def _keystorePropsFile = rootProject.file('../keystore.properties')", 'if (_keystorePropsFile.exists()) {', ' _keystorePropsFile.withInputStream { _keystoreProps.load(it) }', '}', 'signingConfigs {', ' release {', ' if (_keystorePropsFile.exists()) {', " storeFile rootProject.file(_keystoreProps['storeFile'])", " storePassword _keystoreProps['storePassword']", " keyAlias _keystoreProps['keyAlias']", " keyPassword _keystoreProps['keyPassword']", ' }', ' }', '}', END, ]; const managed = managedLines.map((l) => ' ' + l).join('\n'); let src = readFileSync(buildGradle, 'utf8'); const before = src; // 1. Drop any previously managed block so the script is idempotent. const bIdx = src.indexOf(BEGIN); if (bIdx !== -1) { const eIdx = src.indexOf(END, bIdx); if (eIdx !== -1) { const lineStart = src.lastIndexOf('\n', bIdx); const lineEnd = src.indexOf('\n', eIdx); src = src.slice(0, lineStart) + (lineEnd === -1 ? '' : src.slice(lineEnd)); } } // 2. Drop a legacy hardcoded signingConfigs block (has an inline keyPassword). src = src.replace(/\n[ \t]*signingConfigs\s*\{[\s\S]*?keyPassword\s+"[^"]*"\s*\}\s*\}/g, ''); // 3. Insert the managed block immediately after the `android {` opening. const androidIdx = src.indexOf('android {'); if (androidIdx === -1) { console.error('[inject-signing-config] could not find `android {` in build.gradle — aborting.'); process.exit(1); } const lineEnd = src.indexOf('\n', androidIdx); src = src.slice(0, lineEnd + 1) + managed + '\n' + src.slice(lineEnd + 1); if (src === before) { console.log('[inject-signing-config] already up to date.'); } else { writeFileSync(buildGradle, src); console.log('[inject-signing-config] ✓ Wrote managed release signing config into android/app/build.gradle.'); }