104 lines
5.0 KiB
JavaScript
104 lines
5.0 KiB
JavaScript
// 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-<version>.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 });
|
|
}
|
|
|
|
// capacitor-nodejs baut seine Brückenbibliothek mit 4-KB-Seitenausrichtung; neuere Geräte (z. B. Pixel mit
|
|
// 16-KB-Speicherseiten) laden sie dann nicht. Linker-Option einmalig ergänzen.
|
|
function patchPlugin() {
|
|
const f = path.join(__dirname, 'node_modules', 'capacitor-nodejs', 'android', 'CMakeLists.txt');
|
|
const s = fs.readFileSync(f, 'utf8');
|
|
if (!s.includes('max-page-size=16384')) fs.writeFileSync(f, `${s}\ntarget_link_options(native-lib PRIVATE "-Wl,-z,max-page-size=16384")\n`);
|
|
}
|
|
|
|
function buildWeb() {
|
|
patchPlugin();
|
|
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); });
|