@livx.cc/appwrap 0.39.11 → 0.39.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1776,11 +1776,99 @@ function pickDevice(devices: DeviceInfo[], explicitId?: string): DeviceInfo {
|
|
|
1776
1776
|
* distribution signing) — for testing on your own device. Run the PWA build first (or via the script).
|
|
1777
1777
|
* iOS has a bespoke Debug-IPA path (below); Android delegates to `run` (NativeScript builds + installs
|
|
1778
1778
|
* + launches), so the `deploy <platform>` surface is symmetric across both. */
|
|
1779
|
+
/** Resolve adb: $ANDROID_HOME/$ANDROID_SDK_ROOT platform-tools, else `adb` on PATH. */
|
|
1780
|
+
function androidAdb(): string {
|
|
1781
|
+
const home = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
1782
|
+
if (home) {
|
|
1783
|
+
const p = join(home, 'platform-tools', 'adb');
|
|
1784
|
+
if (existsSync(p)) return p;
|
|
1785
|
+
}
|
|
1786
|
+
return 'adb';
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/** Authorized (`device` state) adb serials. Skips `unauthorized`/`offline`. */
|
|
1790
|
+
function listAndroidDevices(adb: string): string[] {
|
|
1791
|
+
try {
|
|
1792
|
+
return execFileSync(adb, ['devices'], { encoding: 'utf8' })
|
|
1793
|
+
.split('\n').slice(1)
|
|
1794
|
+
.filter((l) => /\tdevice\b/.test(l))
|
|
1795
|
+
.map((l) => l.split('\t')[0].trim())
|
|
1796
|
+
.filter(Boolean);
|
|
1797
|
+
} catch {
|
|
1798
|
+
return [];
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/** Pick the target Android device (fail fast, like iOS's pickDevice): explicit --device, else the
|
|
1803
|
+
* single connected one; clear errors for none / multiple / unauthorized. */
|
|
1804
|
+
function pickAndroidDevice(adb: string, flag?: string): string {
|
|
1805
|
+
const devices = listAndroidDevices(adb);
|
|
1806
|
+
if (flag) {
|
|
1807
|
+
if (devices.includes(flag)) return flag;
|
|
1808
|
+
console.error(`✖ Device "${flag}" not connected/authorized. Authorized: ${devices.join(', ') || '(none)'}`);
|
|
1809
|
+
process.exit(1);
|
|
1810
|
+
}
|
|
1811
|
+
if (devices.length === 0) {
|
|
1812
|
+
console.error('✖ No authorized Android device. Connect via USB + accept the "Allow USB debugging" prompt (check with `adb devices`).');
|
|
1813
|
+
process.exit(1);
|
|
1814
|
+
}
|
|
1815
|
+
if (devices.length > 1) {
|
|
1816
|
+
console.error(`✖ Multiple devices — pass --device <id>: ${devices.join(', ')}`);
|
|
1817
|
+
process.exit(1);
|
|
1818
|
+
}
|
|
1819
|
+
return devices[0];
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/** `appwrap deploy android` — ONE-SHOT device deploy, the Android twin of `deploy ios`: sync + debug
|
|
1823
|
+
* config → `ns build android` (debug APK) → `adb install -r` to the device → launch (unless --no-launch)
|
|
1824
|
+
* → exit. Unlike `run android` (ns watch-mode), it doesn't stay attached. NOTE: like `deploy ios`, it
|
|
1825
|
+
* ships the CURRENT `dist/` — build the web first (`bun run build`, or use the `bun run android` script). */
|
|
1826
|
+
async function deployAndroid(cwd: string, flags: Record<string, string>): Promise<void> {
|
|
1827
|
+
const cfg = await loadConfig(cwd, flags);
|
|
1828
|
+
const outDir = resolve(cwd, flags.out ?? 'native');
|
|
1829
|
+
if (!existsSync(outDir)) {
|
|
1830
|
+
console.error(`✖ Wrapper not found at ${outDir} — run \`appwrap init\` first`);
|
|
1831
|
+
process.exit(1);
|
|
1832
|
+
}
|
|
1833
|
+
const adb = androidAdb();
|
|
1834
|
+
const device = pickAndroidDevice(adb, flags.device || undefined); // fail fast before the build
|
|
1835
|
+
|
|
1836
|
+
await sync(cwd, flags); // re-stamp config + copy latest PWA dist
|
|
1837
|
+
stampShellConfig(outDir, { ...cfg, debug: true }); // debug: keep-awake + WebView inspector (parity with deploy ios)
|
|
1838
|
+
|
|
1839
|
+
console.log('▶ ns build android (debug)');
|
|
1840
|
+
runNs(outDir, ['build', 'android']); // bun-installs deps if needed, then gradle build
|
|
1841
|
+
const apk = join(outDir, 'platforms/android/app/build/outputs/apk/debug/app-debug.apk');
|
|
1842
|
+
if (!existsSync(apk)) { console.error(`✖ No APK produced at ${apk}`); process.exit(1); }
|
|
1843
|
+
|
|
1844
|
+
console.log(`▶ installing → ${device}`);
|
|
1845
|
+
try {
|
|
1846
|
+
execFileSync(adb, ['-s', device, 'install', '-r', apk], { stdio: 'inherit' });
|
|
1847
|
+
} catch (e: unknown) {
|
|
1848
|
+
const log = execErrText(e);
|
|
1849
|
+
if (/INSTALL_FAILED_USER_RESTRICTED|user is restricted/i.test(log)) {
|
|
1850
|
+
console.error('\n✖ Install blocked by the device. On MIUI/Xiaomi: Developer options → enable "Install via USB" (may need mobile data + a SIM, no VPN).');
|
|
1851
|
+
}
|
|
1852
|
+
process.exit(1);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
if (!('no-launch' in flags)) {
|
|
1856
|
+
console.log(`▶ launching ${cfg.id}`);
|
|
1857
|
+
try {
|
|
1858
|
+
execFileSync(adb, ['-s', device, 'shell', 'monkey', '-p', cfg.id, '-c', 'android.intent.category.LAUNCHER', '1'], { stdio: 'ignore' });
|
|
1859
|
+
} catch {
|
|
1860
|
+
console.error('⚠ Launch failed — the app is installed; tap its icon.');
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
console.log(`✓ Deployed to ${device}.`);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1779
1866
|
async function deploy(cwd: string, flags: Record<string, string>, positionals: string[]): Promise<void> {
|
|
1780
1867
|
const platform = positionals[0];
|
|
1781
1868
|
if (platform === 'android') {
|
|
1782
|
-
//
|
|
1783
|
-
|
|
1869
|
+
// Parity with `deploy ios`: a clean ONE-SHOT build → install-to-device → launch → exit (NOT `ns run`,
|
|
1870
|
+
// which is watch-mode + hangs on some devices). Mirrors the iOS path with the adb toolchain.
|
|
1871
|
+
return deployAndroid(cwd, flags);
|
|
1784
1872
|
}
|
|
1785
1873
|
if (platform !== 'ios') {
|
|
1786
1874
|
console.error('Usage: appwrap deploy <ios|android> [--device <id|name>] [--no-launch]');
|
|
@@ -30,18 +30,28 @@ jobs:
|
|
|
30
30
|
with: { node-version: 22 }
|
|
31
31
|
- uses: actions/setup-java@v4
|
|
32
32
|
with: { distribution: temurin, java-version: 17 }
|
|
33
|
+
# Cache the Gradle distribution + dependency/build caches — the Android build is ~70% of CI time;
|
|
34
|
+
# without this every run re-downloads everything (~4–5 min). Warms on the first run.
|
|
35
|
+
- uses: actions/cache@v4
|
|
36
|
+
with:
|
|
37
|
+
path: |
|
|
38
|
+
~/.gradle/caches
|
|
39
|
+
~/.gradle/wrapper
|
|
40
|
+
key: gradle-${{ runner.os }}-${{ hashFiles('bun.lock', 'package.json') }}
|
|
41
|
+
restore-keys: gradle-${{ runner.os }}-
|
|
33
42
|
- uses: android-actions/setup-android@v3
|
|
34
43
|
- run: sudo apt-get update -qq && sudo apt-get install -y -qq imagemagick # appwrap icon-gen needs an image tool (no sips on Linux) — else the default NS icon ships
|
|
35
44
|
- run: bun install && bun run build # → dist/ (the PWA bundle the wrapper ships)
|
|
36
45
|
# Pin global tools — unpinned installs can grab a breaking release mid-flight. Bump deliberately.
|
|
37
|
-
- run:
|
|
46
|
+
- run: bun add -g nativescript@9.0.6
|
|
38
47
|
- run: bunx @livx.cc/appwrap init # native/ is generated (gitignored) — regenerate fresh in CI
|
|
39
48
|
- working-directory: native
|
|
40
|
-
run:
|
|
49
|
+
run: bun install
|
|
41
50
|
- name: Decode upload keystore
|
|
42
51
|
run: echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > "$RUNNER_TEMP/upload.keystore"
|
|
43
52
|
- name: Build signed AAB
|
|
44
53
|
env:
|
|
54
|
+
GRADLE_OPTS: "-Dorg.gradle.caching=true -Dorg.gradle.parallel=true -Dorg.gradle.daemon=false"
|
|
45
55
|
APPWRAP_ANDROID_KEYSTORE: ${{ runner.temp }}/upload.keystore
|
|
46
56
|
APPWRAP_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
47
57
|
APPWRAP_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|