// Baut die Android-App. // node build.js --setup Node.js-Plugin herunterladen (Prüfsumme) und Abhängigkeiten installieren // node build.js Weboberfläche + Node-Backend nach www/ bauen und ins Android-Projekt synchronisieren // node build.js --apk zusätzlich signierte Release-APK nach ../dist/MrTerm-.apk bauen const fs = require('fs'); const path = require('path'); const os = require('os'); const crypto = require('crypto'); const { execFileSync } = require('child_process'); const ROOT = path.join(__dirname, '..'); const WWW = path.join(__dirname, 'www'); const version = require(path.join(ROOT, 'package.json')).version; const run = (cmd, args, opts = {}) => execFileSync(cmd, args, { stdio: 'inherit', cwd: __dirname, ...opts }); const PLUGIN = { url: 'https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.10/capacitor-nodejs.tgz', file: path.join(__dirname, 'vendor', 'capacitor-nodejs-1.0.0-beta.10.tgz'), sha256: 'cc47cba4190d5e0ea8e2b33f4e9bd47e159570c01571c51214467e4c21e87bd3', }; async function setup() { if (!fs.existsSync(PLUGIN.file)) { console.log('Lade capacitor-nodejs …'); const res = await fetch(PLUGIN.url); if (!res.ok) throw new Error(`Download fehlgeschlagen: ${res.status}`); fs.mkdirSync(path.dirname(PLUGIN.file), { recursive: true }); fs.writeFileSync(PLUGIN.file, Buffer.from(await res.arrayBuffer())); } const sum = crypto.createHash('sha256').update(fs.readFileSync(PLUGIN.file)).digest('hex'); if (sum !== PLUGIN.sha256) { fs.rmSync(PLUGIN.file); throw new Error(`Prüfsumme von capacitor-nodejs stimmt nicht (${sum})`); } run('npm', ['install']); } function copy(from, to) { fs.mkdirSync(path.dirname(to), { recursive: true }); fs.cpSync(from, to, { recursive: true }); } function buildWeb() { fs.rmSync(WWW, { recursive: true, force: true }); copy(path.join(__dirname, 'web'), WWW); const nm = path.join(ROOT, 'node_modules', '@xterm'); copy(path.join(nm, 'xterm', 'lib', 'xterm.js'), path.join(WWW, 'lib', 'xterm.js')); copy(path.join(nm, 'xterm', 'css', 'xterm.css'), path.join(WWW, 'lib', 'xterm.css')); copy(path.join(nm, 'addon-fit', 'lib', 'addon-fit.js'), path.join(WWW, 'lib', 'addon-fit.js')); copy(path.join(ROOT, 'src', 'i18n.js'), path.join(WWW, 'lib', 'i18n.js')); copy(path.join(ROOT, 'src', 'renderer', 'themes.js'), path.join(WWW, 'lib', 'themes.js')); copy(path.join(ROOT, 'src', 'renderer', 'logo.png'), path.join(WWW, 'logo.png')); // Node-Backend inkl. ssh2 in eine Datei bündeln (nodejs-mobile = Node 18) require('esbuild').buildSync({ entryPoints: [path.join(__dirname, 'nodejs', 'server.js')], outfile: path.join(WWW, 'nodejs', 'index.js'), bundle: true, platform: 'node', target: 'node18', minify: true, legalComments: 'none', external: ['bridge', 'cpu-features'], alias: { electron: path.join(__dirname, 'nodejs', 'electron-shim.js') }, logLevel: 'warning', }); fs.writeFileSync(path.join(WWW, 'nodejs', 'package.json'), JSON.stringify({ name: 'mrterm-backend', main: 'index.js' })); run('npx', ['cap', 'sync', 'android']); } function toolchain() { const base = path.join(os.homedir(), '.local', 'share', 'android-toolchain'); const env = { ...process.env }; env.JAVA_HOME ||= path.join(base, 'jdk'); env.ANDROID_HOME ||= path.join(base, 'sdk'); if (!fs.existsSync(env.JAVA_HOME) || !fs.existsSync(env.ANDROID_HOME)) throw new Error('JDK/Android-SDK nicht gefunden: JAVA_HOME und ANDROID_HOME setzen'); fs.writeFileSync(path.join(__dirname, 'android', 'local.properties'), `sdk.dir=${env.ANDROID_HOME}\n`); return env; } function buildApk() { if (!fs.existsSync(path.join(__dirname, 'android', 'keystore.properties'))) { throw new Error('android/keystore.properties fehlt (Signaturschlüssel). Ohne ihn können Updates nicht installiert werden.'); } run('./gradlew', ['--no-daemon', '-q', 'assembleRelease', `-PmrtermVersion=${version}`], { cwd: path.join(__dirname, 'android'), env: toolchain() }); const dir = path.join(__dirname, 'android', 'app', 'build', 'outputs', 'apk', 'release'); for (const abi of ['arm64-v8a', 'armeabi-v7a']) { const out = path.join(ROOT, 'dist', `MrTerm-${version}-${abi}.apk`); copy(path.join(dir, `app-${abi}-release.apk`), out); console.log(`APK: ${out}`); } } (async () => { const args = process.argv.slice(2); if (args.includes('--setup')) await setup(); else { buildWeb(); if (args.includes('--apk')) buildApk(); } })().catch((e) => { console.error(e.message || e); process.exit(1); });