droidsuite 1.0.0-beta.10 → 1.0.0-beta.11
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/app/DroidSuite.apk +0 -0
- package/bin/droidsuite.mjs +10 -0
- package/package.json +2 -2
- package/skills/droidsuite/SKILL.md +3 -1
- package/src/server.mjs +2 -2
- package/src/updatecheck.mjs +55 -0
- package/web/index.html +3 -0
package/app/DroidSuite.apk
CHANGED
|
Binary file
|
package/bin/droidsuite.mjs
CHANGED
|
@@ -26,9 +26,11 @@ import { runMenu } from '../src/menu.mjs';
|
|
|
26
26
|
import { createShortcuts } from '../src/shortcut.mjs';
|
|
27
27
|
import { openUrl } from '../src/browser.mjs';
|
|
28
28
|
import { hasAgents, installSkill, SKILL_SOURCE } from '../src/skill.mjs';
|
|
29
|
+
import { checkForUpdate, updateMessage } from '../src/updatecheck.mjs';
|
|
29
30
|
|
|
30
31
|
const { version: VERSION } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
31
32
|
const CLI_PATH = fileURLToPath(import.meta.url);
|
|
33
|
+
const updateAvailable = () => checkForUpdate({ current: VERSION, load: () => loadConfig().updateCheck, save: (updateCheck) => updateConfig({ updateCheck }) });
|
|
32
34
|
|
|
33
35
|
const c = process.stdout.isTTY && !process.env.NO_COLOR
|
|
34
36
|
? { dim: (s) => `\x1b[2m${s}\x1b[22m`, bold: (s) => `\x1b[1m${s}\x1b[22m`, green: (s) => `\x1b[32m${s}\x1b[39m`, red: (s) => `\x1b[31m${s}\x1b[39m`, yellow: (s) => `\x1b[33m${s}\x1b[39m` }
|
|
@@ -80,7 +82,13 @@ async function serve(argv, { defaultFolder = true } = {}) {
|
|
|
80
82
|
pushes.sweep(); // leftovers from a serve that was killed
|
|
81
83
|
const installVia = values['install-via'] ?? 'auto';
|
|
82
84
|
if (!['auto', 'app'].includes(installVia)) throw new Error('--install-via is auto (adb when Wireless debugging is connected, else the app) or app');
|
|
85
|
+
let newerVersion = null;
|
|
86
|
+
updateAvailable().then((latest) => { // in the background: serving never waits for npm
|
|
87
|
+
newerVersion = latest;
|
|
88
|
+
if (latest) console.log(c.dim(updateMessage(latest)));
|
|
89
|
+
});
|
|
83
90
|
const bridge = createBridgeServer({
|
|
91
|
+
updateAvailable: () => newerVersion,
|
|
84
92
|
index, config, hosts, debug: debugService, app, inbox, pushes, installVia,
|
|
85
93
|
// Uses adb only if it's already here; serve never downloads it or accepts its license.
|
|
86
94
|
adb: async () => (findAdb() ? debugService.adb() : null),
|
|
@@ -467,6 +475,8 @@ async function setup(argv) {
|
|
|
467
475
|
if (!installThenSetup(argv)) process.exitCode = 1;
|
|
468
476
|
return;
|
|
469
477
|
}
|
|
478
|
+
const newer = await updateAvailable();
|
|
479
|
+
if (newer) console.log(`${updateMessage(newer)}\n`);
|
|
470
480
|
const { values } = parseArgs({ args: argv, options: { yes: { type: 'boolean', short: 'y' }, 'skip-adb': { type: 'boolean' }, shortcut: { type: 'boolean' }, 'no-shortcut': { type: 'boolean' } } });
|
|
471
481
|
// With no terminal (CI, a script) nobody asked for an icon, unless --yes or --shortcut says so.
|
|
472
482
|
const wantsIcon = !values['no-shortcut'] && (values.shortcut || values.yes || process.stdin.isTTY);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "droidsuite",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.11",
|
|
4
4
|
"description": "Your Android phone on your computer, no cable: see and control its screen, type on it, move files and clipboard both ways, install builds and debug over Wi-Fi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"start": "node bin/droidsuite.mjs serve",
|
|
37
|
-
"test": "node
|
|
37
|
+
"test": "node run-tests.mjs"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"bonjour-service": "^1.3.0",
|
|
@@ -100,7 +100,9 @@ droidsuite adb -s <serial> exec-out screencap -p > screen.png
|
|
|
100
100
|
```
|
|
101
101
|
- Read only the logs of the app you're testing, as above. Other apps' logs can hold the person's private data, and
|
|
102
102
|
whatever you read goes into this conversation. No output from the crash command means this app hasn't crashed.
|
|
103
|
-
- Find `applicationId` in `app/build.gradle(.kts)
|
|
103
|
+
- Find `applicationId` in `app/build.gradle(.kts)`, or read `builds[].packageName` from the status script while `serve`
|
|
104
|
+
watches the build. Failing that, ask the person, or search with part of the app's name:
|
|
105
|
+
`droidsuite adb shell pm list packages <part>`. Never list every package: which apps someone has is private.
|
|
104
106
|
- `INSTALL_FAILED_UPDATE_INCOMPATIBLE` = signed with another key: uninstalling deletes the app's data, so ask first,
|
|
105
107
|
then `adb uninstall <applicationId>`.
|
|
106
108
|
- Use `logcat -d` (dump and exit). A plain `logcat` streams forever and blocks you. To see only one launch, note the
|
package/src/server.mjs
CHANGED
|
@@ -138,7 +138,7 @@ function parseRange(header, size) {
|
|
|
138
138
|
*/
|
|
139
139
|
export function createBridgeServer({
|
|
140
140
|
index, config, hosts, debug = null, app = null, inbox = null, pushes = null,
|
|
141
|
-
installVia = 'auto', adb = null, autoConnect = null, log = () => {}, screen = null, rememberFolder = null, quit = null, nearby: nearbyOption = null,
|
|
141
|
+
installVia = 'auto', adb = null, autoConnect = null, log = () => {}, screen = null, rememberFolder = null, quit = null, updateAvailable = () => null, nearby: nearbyOption = null,
|
|
142
142
|
}) {
|
|
143
143
|
const phones = new Map(); // response -> phone record (see openPhone)
|
|
144
144
|
let appSha = null; // the bundled phone app's sha256, computed on its first download
|
|
@@ -174,7 +174,7 @@ export function createBridgeServer({
|
|
|
174
174
|
id: config.id, name: config.name, hosts, port, link: link(),
|
|
175
175
|
qrSvg: await QRCode.toString(link(), { type: 'svg', margin: 1, errorCorrectionLevel: 'M' }),
|
|
176
176
|
typing: { address: hosts[0] ? `${hosts[0]}:${port}` : null, code: typingCode, expiresAt: typingExpiresAt },
|
|
177
|
-
roots: index.roots, builds: index.list(), phones: phoneList(),
|
|
177
|
+
roots: index.roots, builds: index.list(), phones: phoneList(), update: updateAvailable(),
|
|
178
178
|
app: app ? { available: true, versionName: app.versionName ?? null, size: app.size, ...await appQr() } : { available: false },
|
|
179
179
|
inbox: inbox ? { dir: inbox.dir, recent: inbox.recent.map(({ name, size, from, time }) => ({ name, size, from, time })) } : null,
|
|
180
180
|
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Tells people when a newer DroidSuite is on npm. Asks npm at most once a day, sends nothing about the user, and never
|
|
2
|
+
// gets in the way: no network, or npm being slow, just means no notice. DROIDSUITE_NO_UPDATE_CHECK=1 turns it off.
|
|
3
|
+
const DAY = 24 * 3600_000;
|
|
4
|
+
const LATEST_URL = 'https://registry.npmjs.org/droidsuite/latest';
|
|
5
|
+
|
|
6
|
+
function parts(version) {
|
|
7
|
+
const [main, pre] = String(version).split('-');
|
|
8
|
+
return { main: main.split('.').map(Number), pre: pre ? pre.split('.') : null };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** True when [candidate] is a later version than [current], counting prereleases: beta.10 comes after beta.9. */
|
|
12
|
+
export function isNewer(candidate, current) {
|
|
13
|
+
const a = parts(candidate);
|
|
14
|
+
const b = parts(current);
|
|
15
|
+
for (let i = 0; i < 3; i++) {
|
|
16
|
+
if ((a.main[i] ?? 0) !== (b.main[i] ?? 0)) return (a.main[i] ?? 0) > (b.main[i] ?? 0);
|
|
17
|
+
}
|
|
18
|
+
if (!a.pre || !b.pre) return !a.pre && !!b.pre; // a final release comes after its prereleases
|
|
19
|
+
for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
|
|
20
|
+
const [x, y] = [a.pre[i], b.pre[i]];
|
|
21
|
+
if (x === undefined) return false;
|
|
22
|
+
if (y === undefined) return true;
|
|
23
|
+
if (x === y) continue;
|
|
24
|
+
const bothNumbers = /^\d+$/.test(x) && /^\d+$/.test(y);
|
|
25
|
+
return bothNumbers ? Number(x) > Number(y) : x > y;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function latestOnNpm() {
|
|
31
|
+
const res = await fetch(LATEST_URL, { signal: AbortSignal.timeout(8000) });
|
|
32
|
+
if (!res.ok) throw new Error(`npm answered ${res.status}`);
|
|
33
|
+
return (await res.json()).version;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The newer version available, or null. [load] and [save] keep `{ at, latest }` between runs.
|
|
38
|
+
* Never throws.
|
|
39
|
+
*/
|
|
40
|
+
export async function checkForUpdate({ current, load, save, fetchLatest = latestOnNpm, now = Date.now(), env = process.env }) {
|
|
41
|
+
if (env.DROIDSUITE_NO_UPDATE_CHECK || env.CI) return null;
|
|
42
|
+
try {
|
|
43
|
+
const saved = load();
|
|
44
|
+
let latest = saved?.latest ?? null;
|
|
45
|
+
if (!saved?.at || now - saved.at >= DAY) {
|
|
46
|
+
latest = await fetchLatest();
|
|
47
|
+
save({ at: now, latest });
|
|
48
|
+
}
|
|
49
|
+
return latest && isNewer(latest, current) ? latest : null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const updateMessage = (latest) => `A newer DroidSuite is available (${latest}). To update, run: npx droidsuite@latest setup`;
|
package/web/index.html
CHANGED
|
@@ -261,6 +261,7 @@
|
|
|
261
261
|
</head>
|
|
262
262
|
<body>
|
|
263
263
|
<div class="wrap">
|
|
264
|
+
<div class="note" id="update-note" role="status" hidden>A newer DroidSuite is available (<span id="update-version"></span>). To update, run <code class="mono">npx droidsuite@latest setup</code> in a terminal.</div>
|
|
264
265
|
<header>
|
|
265
266
|
<div class="mark" aria-hidden="true"><svg viewBox="0 0 96 96"><path fill="#fff" fill-rule="evenodd" d="M23.5 16h7a6.5 6.5 0 0 1 6.5 6.5v51a6.5 6.5 0 0 1-6.5 6.5h-7a6.5 6.5 0 0 1-6.5-6.5v-51a6.5 6.5 0 0 1 6.5-6.5zM24.6 70.5a1.6 1.6 0 0 0 0 3.2h4.8a1.6 1.6 0 0 0 0-3.2z"/><path fill="none" stroke="#fff" stroke-width="8" stroke-linecap="round" d="M46 20a28 28 0 0 1 0 56"/><path fill="none" stroke="#E89B2F" stroke-width="8" stroke-linecap="round" d="M46 38a10 10 0 0 1 0 20"/></svg></div>
|
|
266
267
|
<h1>DroidSuite</h1>
|
|
@@ -539,6 +540,8 @@
|
|
|
539
540
|
let folderOpen = false; // the Builds folder field, opened with "Change"
|
|
540
541
|
function render(s) {
|
|
541
542
|
last = s;
|
|
543
|
+
$('update-note').hidden = !s.update;
|
|
544
|
+
$('update-version').textContent = s.update ?? '';
|
|
542
545
|
renderBridge();
|
|
543
546
|
if (!pairTouched) $('pair-card').open = !s.phones.length;
|
|
544
547
|
$('serving').innerHTML = `${esc(s.name)} · serving on <span class="mono">${esc(s.hosts[0] ?? 'localhost')}:${s.port}</span>`;
|