diff --git a/README.md b/README.md index e8ab4b1..8338eb8 100644 --- a/README.md +++ b/README.md @@ -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 | @@ -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/.json` and translate all values diff --git a/package.json b/package.json index bd85cc4..3840dd0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/setup.js b/scripts/setup.js index 9659b71..b47b60d 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -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', @@ -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 { diff --git a/src/adbPath.ts b/src/adbPath.ts index 29a63af..c67d971 100644 --- a/src/adbPath.ts +++ b/src/adbPath.ts @@ -6,7 +6,7 @@ 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. @@ -14,7 +14,17 @@ import os from 'os'; 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; @@ -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;