feat: Android APP V1 - Experimental Alpha
This commit is contained in:
25
tools/build-android-apk.sh
Executable file
25
tools/build-android-apk.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a debug Android APK for the Capacitor shell.
|
||||
#
|
||||
# Prerequisites (CI installs these automatically):
|
||||
# - Node.js 22 + root npm dependencies (`npm ci`)
|
||||
# - JDK 21 (JAVA_HOME)
|
||||
# - Android SDK with platform 36 + build-tools (ANDROID_SDK_ROOT / ANDROID_HOME)
|
||||
#
|
||||
# Output:
|
||||
# toju-app/android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
npm run bundle:rnnoise
|
||||
npm run build:prod
|
||||
|
||||
cd toju-app
|
||||
npx cap sync android
|
||||
|
||||
cd android
|
||||
chmod +x gradlew
|
||||
./gradlew assembleDebug --no-daemon --stacktrace
|
||||
45
tools/cap-open-android.js
Normal file
45
tools/cap-open-android.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const { resolveAndroidStudioPath } = require('./resolve-android-studio-path');
|
||||
|
||||
function main() {
|
||||
const studioPath = resolveAndroidStudioPath();
|
||||
|
||||
if (!studioPath) {
|
||||
console.error(
|
||||
'[error] Unable to locate Android Studio (studio.sh).\n'
|
||||
+ ' Install Android Studio or set CAPACITOR_ANDROID_STUDIO_PATH to studio.sh.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tojuAppDir = path.resolve(__dirname, '..', 'toju-app');
|
||||
const child = spawn('npx', ['cap', 'open', 'android'], {
|
||||
cwd: tojuAppDir,
|
||||
env: {
|
||||
...process.env,
|
||||
CAPACITOR_ANDROID_STUDIO_PATH: studioPath
|
||||
},
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
99
tools/resolve-android-studio-path.js
Normal file
99
tools/resolve-android-studio-path.js
Normal file
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* @param {string} filePath
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isExecutableStudioSh(filePath) {
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
return stat.isFile() && (stat.mode & 0o111) !== 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} homeDir
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function findJetBrainsToolboxStudioSh(homeDir) {
|
||||
const toolboxRoot = path.join(
|
||||
homeDir,
|
||||
'.local/share/JetBrains/Toolbox/apps/AndroidStudio'
|
||||
);
|
||||
|
||||
let entries;
|
||||
|
||||
try {
|
||||
entries = fs.readdirSync(toolboxRoot, { withFileTypes: true });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const studioPaths = entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => path.join(toolboxRoot, entry.name, 'bin/studio.sh'))
|
||||
.filter((candidate) => isExecutableStudioSh(candidate))
|
||||
.sort();
|
||||
|
||||
return studioPaths.at(-1) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ env?: NodeJS.ProcessEnv; homeDir?: string }} [options]
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function resolveAndroidStudioPath(options = {}) {
|
||||
const env = options.env ?? process.env;
|
||||
const homeDir = options.homeDir ?? os.homedir();
|
||||
|
||||
const fromEnv = String(env.CAPACITOR_ANDROID_STUDIO_PATH ?? '').trim();
|
||||
|
||||
if (fromEnv && isExecutableStudioSh(fromEnv)) {
|
||||
return fromEnv;
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
'/usr/local/android-studio/bin/studio.sh',
|
||||
'/opt/android-studio/bin/studio.sh',
|
||||
path.join(homeDir, 'android-studio/bin/studio.sh'),
|
||||
'/var/lib/flatpak/app/com.google.AndroidStudio/x86_64/stable/active/files/extra/bin/studio.sh',
|
||||
path.join(
|
||||
homeDir,
|
||||
'.local/share/flatpak/app/com.google.AndroidStudio/x86_64/stable/active/files/extra/bin/studio.sh'
|
||||
),
|
||||
'/snap/android-studio/current/bin/studio.sh'
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (isExecutableStudioSh(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return findJetBrainsToolboxStudioSh(homeDir);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveAndroidStudioPath,
|
||||
isExecutableStudioSh
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
const resolved = resolveAndroidStudioPath();
|
||||
|
||||
if (!resolved) {
|
||||
console.error(
|
||||
'Could not find Android Studio (studio.sh). Install Android Studio or set CAPACITOR_ANDROID_STUDIO_PATH.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.stdout.write(`${resolved}\n`);
|
||||
}
|
||||
41
tools/resolve-android-studio-path.test.js
Normal file
41
tools/resolve-android-studio-path.test.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { resolveAndroidStudioPath } = require('./resolve-android-studio-path');
|
||||
|
||||
test('resolveAndroidStudioPath prefers CAPACITOR_ANDROID_STUDIO_PATH when executable', () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'studio-sh-'));
|
||||
const studioSh = path.join(tempDir, 'studio.sh');
|
||||
|
||||
fs.writeFileSync(studioSh, '#!/bin/sh\n', { mode: 0o755 });
|
||||
|
||||
const resolved = resolveAndroidStudioPath({
|
||||
env: { CAPACITOR_ANDROID_STUDIO_PATH: studioSh },
|
||||
homeDir: tempDir
|
||||
});
|
||||
|
||||
assert.equal(resolved, studioSh);
|
||||
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('resolveAndroidStudioPath finds flatpak active studio.sh when present', () => {
|
||||
const flatpakPath =
|
||||
'/var/lib/flatpak/app/com.google.AndroidStudio/x86_64/stable/active/files/extra/bin/studio.sh';
|
||||
|
||||
if (!fs.existsSync(flatpakPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolved = resolveAndroidStudioPath({
|
||||
env: {},
|
||||
homeDir: '/nonexistent-home-for-test'
|
||||
});
|
||||
|
||||
assert.equal(resolved, flatpakPath);
|
||||
});
|
||||
Reference in New Issue
Block a user