Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ npm run setup # Download ADB binaries for all platforms (one time, needs in
npm run dist # Compile TypeScript + bundle into self-contained executables
```

### Updating the bundled ADB version

`npm run setup` skips the download if ADB binaries are already present in `bin/`. To update to the latest ADB release, delete the `bin/`, then re-run:

```bash
npm run setup # Re-downloads latest ADB and writes bin/adb-version.txt
npm run dist # Bundles the new binaries and version stamp into the executables
```

End users get the new ADB automatically on their next app launch — the app compares the bundled version against what is already extracted on disk and re-extracts if they differ.

After `npm run dist`, the `release/` folder contains one self-contained executable per platform, named with the current version:

| File | Platform |
Expand Down Expand Up @@ -261,6 +272,16 @@ docs/
screenshots/ App screenshots used in this README
```

### Release workflow

1. Make sure the version in `package.json` on `master` is the version you intend to release
2. Cut a `release/vX.Y.Z` branch from master
3. Build, test, and publish the release from that branch
4. Tag the tip of the release branch `vX.Y.Z`
5. Bump the version in `package.json` on `master` to the next version (e.g. `0.3.0` → `0.4.0`)

`master` must always carry a version number one step ahead of the latest published release. This prevents master from ever sharing a version number with a release that is already out.

### Adding a language

1. Copy `public/i18n/en.json` to `public/i18n/<code>.json` and translate all values
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amc",
"version": "0.3.0",
"version": "0.4.0",
"description": "Copy photos and videos from Android phone to local computer via ADB",
"main": "dist/server.js",
"bin": "dist/server.js",
Expand Down
27 changes: 21 additions & 6 deletions scripts/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@

'use strict';

const https = require('https');
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const stream = require('stream');
const unzipper = require('unzipper');
const https = require('https');
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const stream = require('stream');
const unzipper = require('unzipper');
const { execFileSync } = require('child_process');

const PLATFORM_TOOLS = {
win: 'https://dl.google.com/android/repository/platform-tools-latest-windows.zip',
Expand Down Expand Up @@ -126,6 +127,20 @@ async function main() {
await setupPlatform(name, url);
}

// Capture the ADB version from the current platform's binary and write it to bin/adb-version.txt.
// All three platform zips ship the same platform-tools release, so one version string covers all.
const currentPlatform = process.platform === 'win32' ? 'win' : process.platform === 'darwin' ? 'mac' : 'linux';
const currentBinary = path.join(BIN_DIR, currentPlatform, process.platform === 'win32' ? 'adb.exe' : 'adb');
try {
const versionOutput = execFileSync(currentBinary, ['version'], { encoding: 'utf8' });
const match = versionOutput.match(/Version\s+([\d.\-]+)/);
const versionStr = match ? match[1] : versionOutput.split('\n')[0].trim();
fs.writeFileSync(path.join(BIN_DIR, 'adb-version.txt'), versionStr);
console.log(` ADB version: ${versionStr}`);
} catch {
console.warn(' Warning: could not determine ADB version — bin/adb-version.txt not written.');
}

if (currentOnly) {
console.log('\nADB is ready. Starting the app...\n');
} else {
Expand Down
47 changes: 35 additions & 12 deletions src/adbPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ import os from 'os';
* Resolves the path to the ADB binary.
*
* - In development (ts-node / node dist/): returns 'adb' and relies on system PATH.
* - In a pkg bundle: extracts the embedded ADB binary to the system temp directory
* - In a pkg bundle: extracts the embedded ADB binary to the user's app-data directory
* on first run, then returns that path. The extraction is skipped on subsequent runs.
*
* ADB binaries are embedded at build time from the bin/ directory.
*/

const isPkg = typeof (process as NodeJS.Process & { pkg?: unknown }).pkg !== 'undefined';

const TEMP_BIN_DIR = path.join(os.tmpdir(), 'amc-adb-bin');
function getAdbExtractDir(): string {
if (process.platform === 'win32') {
const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
return path.join(localAppData, 'AMC', 'adb');
}
if (process.platform === 'darwin') {
return path.join(os.homedir(), 'Library', 'Application Support', 'AMC', 'adb');
}
const xdgData = process.env.XDG_DATA_HOME ?? path.join(os.homedir(), '.local', 'share');
return path.join(xdgData, 'AMC', 'adb');
}

let resolvedPath: string | null = null;

Expand All @@ -35,34 +45,47 @@ export function getAdbPath(): string {
const platform = process.platform;
const binaryName = platform === 'win32' ? 'adb.exe' : 'adb';
const platformDir = platform === 'win32' ? 'win' : platform === 'darwin' ? 'mac' : 'linux';
const destBinary = path.join(TEMP_BIN_DIR, binaryName);
const adbExtractDir = getAdbExtractDir();
const destBinary = path.join(adbExtractDir, binaryName);
const versionStamp = path.join(adbExtractDir, 'adb-version.txt');

// Read the version bundled at build time (written by npm run setup).
const bundledVersionFile = path.join(__dirname, '..', 'bin', 'adb-version.txt');
const bundledVersion = fs.existsSync(bundledVersionFile)
? fs.readFileSync(bundledVersionFile, 'utf8').trim()
: null;

if (!fs.existsSync(destBinary)) {
fs.mkdirSync(TEMP_BIN_DIR, { recursive: true });
const installedVersion = fs.existsSync(versionStamp)
? fs.readFileSync(versionStamp, 'utf8').trim()
: null;

const needsExtract = !fs.existsSync(destBinary) || bundledVersion !== installedVersion;

if (needsExtract) {
fs.mkdirSync(adbExtractDir, { recursive: true });

// In a pkg bundle, __dirname resolves inside the virtual snapshot filesystem.
// Regular fs calls (readFileSync, copyFileSync) work transparently against it.
const srcDir = path.join(__dirname, '..', 'bin', platformDir);

// Copy the main binary
fs.copyFileSync(path.join(srcDir, binaryName), destBinary);

// On Windows, ADB needs its companion DLLs in the same directory
if (platform === 'win32') {
for (const entry of fs.readdirSync(srcDir)) {
if (entry.endsWith('.dll')) {
fs.copyFileSync(
path.join(srcDir, entry),
path.join(TEMP_BIN_DIR, entry),
);
fs.copyFileSync(path.join(srcDir, entry), path.join(adbExtractDir, entry));
}
}
}

// Ensure the binary is executable on Unix
if (platform !== 'win32') {
fs.chmodSync(destBinary, 0o755);
}

// Write version stamp so the next launch skips extraction unless the bundled version changes.
if (bundledVersion) {
fs.writeFileSync(versionStamp, bundledVersion);
}
}

resolvedPath = destBinary;
Expand Down
Loading