wireless-dev-cli
Version:
CLI tool for wireless React Native/Expo development without USB cables
599 lines (598 loc) • 24.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const child_process_1 = require("child_process");
const inquirer_1 = __importDefault(require("inquirer"));
const qrcode_terminal_1 = __importDefault(require("qrcode-terminal"));
const os_1 = __importDefault(require("os"));
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const cli_table3_1 = __importDefault(require("cli-table3"));
const util_1 = require("util");
const os_2 = require("os");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const execPromise = (0, util_1.promisify)(child_process_1.exec);
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.wireless-dev');
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
if (!fs_1.default.existsSync(CONFIG_DIR)) {
fs_1.default.mkdirSync(CONFIG_DIR);
}
let config = {};
try {
if (fs_1.default.existsSync(CONFIG_FILE)) {
config = JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, 'utf8'));
}
}
catch (err) {
console.error('Error loading config:', err);
}
const saveConfig = () => {
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
};
const getLocalIpAddress = () => {
const nets = (0, os_2.networkInterfaces)();
for (const name of Object.keys(nets)) {
const networkInterface = nets[name];
if (networkInterface) {
for (const net of networkInterface) {
if (net.family === 'IPv4' && !net.internal) {
return net.address;
}
}
}
}
return '127.0.0.1';
};
const getConnectedDevices = async () => {
try {
const { stdout } = await execPromise('adb devices');
const lines = stdout.split('\n').slice(1);
const devices = [];
for (const line of lines) {
const match = line.match(/^(\S+)\s+(\S+)/);
if (match) {
const [, id, status] = match;
let deviceInfo = { id, status };
if (status === 'device') {
try {
const { stdout: model } = await execPromise(`adb -s ${id} shell getprop ro.product.model`);
deviceInfo.model = model.trim();
const { stdout: version } = await execPromise(`adb -s ${id} shell getprop ro.build.version.release`);
deviceInfo.androidVersion = version.trim();
const { stdout: manufacturer } = await execPromise(`adb -s ${id} shell getprop ro.product.manufacturer`);
deviceInfo.manufacturer = manufacturer.trim();
deviceInfo.wireless = id.includes(':');
}
catch (err) {
}
}
devices.push(deviceInfo);
}
}
return devices;
}
catch (error) {
console.error('Error getting devices:', error);
return [];
}
};
const discoverDevices = async () => {
const spinner = (0, ora_1.default)('Discovering devices on network...').start();
try {
const localIp = getLocalIpAddress();
const ipBase = localIp.substring(0, localIp.lastIndexOf('.') + 1);
const discoveredDevices = [];
const promises = [];
for (let i = 1; i < 255; i++) {
const ip = `${ipBase}${i}`;
const connectedDevices = await getConnectedDevices();
const isAlreadyConnected = connectedDevices.some(device => device.id.includes(ip) || device.id.startsWith(ip));
if (isAlreadyConnected) {
discoveredDevices.push({ ip, status: 'connected' });
}
else if (ip === localIp) {
continue;
}
else {
promises.push(execPromise(`adb connect ${ip}:5555`, { timeout: 500 })
.then(() => {
discoveredDevices.push({ ip, status: 'discoverable' });
})
.catch(() => {
}));
}
}
await Promise.allSettled(promises);
spinner.succeed('Device discovery completed');
return discoveredDevices;
}
catch (error) {
spinner.fail('Device discovery failed');
console.error('Error discovering devices:', error);
return [];
}
};
const checkAdbInstalled = () => {
try {
(0, child_process_1.execSync)('adb version', { stdio: 'ignore' });
return true;
}
catch (error) {
return false;
}
};
const enableWirelessDebugging = async (deviceId) => {
try {
const devices = await getConnectedDevices();
const device = devices.find(d => d.id === deviceId);
if (!device) {
console.error(chalk_1.default.red(`Device ${deviceId} not found`));
return false;
}
if (device.wireless) {
console.log(chalk_1.default.yellow(`Device ${deviceId} is already connected wirelessly`));
return true;
}
const { stdout: version } = await execPromise(`adb -s ${deviceId} shell getprop ro.build.version.release`);
const androidVersion = parseInt(version.trim().split('.')[0]);
if (androidVersion >= 11) {
console.log(chalk_1.default.blue('Using Android 11+ wireless debugging...'));
const { stdout: wifiIp } = await execPromise(`adb -s ${deviceId} shell ip route | grep wlan0 | awk '{print $9}'`);
const ip = wifiIp.trim();
if (!ip) {
console.error(chalk_1.default.red('Failed to get device IP address. Make sure Wi-Fi is enabled.'));
return false;
}
await execPromise(`adb -s ${deviceId} tcpip 5555`);
console.log(chalk_1.default.green(`Wireless debugging enabled. Device IP: ${ip}`));
console.log(chalk_1.default.blue(`Wait a few seconds and then connect with: adb connect ${ip}:5555`));
if (!config.knownDevices)
config.knownDevices = [];
config.knownDevices.push({
id: deviceId,
ip: `${ip}:5555`,
model: device.model || 'Unknown',
lastConnected: new Date().toISOString()
});
saveConfig();
return true;
}
else {
console.log(chalk_1.default.yellow('Using legacy wireless debugging for Android 10 and below...'));
const { stdout: wifiIp } = await execPromise(`adb -s ${deviceId} shell ip addr show wlan0 | grep "inet " | cut -d' ' -f6 | cut -d/ -f1`);
const ip = wifiIp.trim();
if (!ip) {
console.error(chalk_1.default.red('Failed to get device IP address. Make sure Wi-Fi is enabled.'));
return false;
}
await execPromise(`adb -s ${deviceId} tcpip 5555`);
console.log(chalk_1.default.green(`Wireless debugging enabled. Device IP: ${ip}`));
console.log(chalk_1.default.blue(`Wait a few seconds and then connect with: adb connect ${ip}:5555`));
if (!config.knownDevices)
config.knownDevices = [];
config.knownDevices.push({
id: deviceId,
ip: `${ip}:5555`,
model: device.model || 'Unknown',
lastConnected: new Date().toISOString()
});
saveConfig();
return true;
}
}
catch (error) {
console.error('Error enabling wireless debugging:', error);
return false;
}
};
const connectToDevice = async (ipAndPort) => {
try {
const { stdout } = await execPromise(`adb connect ${ipAndPort}`);
console.log(chalk_1.default.green(stdout.trim()));
if (stdout.includes('connected')) {
if (config.knownDevices) {
const deviceIndex = config.knownDevices.findIndex(d => d.ip === ipAndPort);
if (deviceIndex !== -1) {
config.knownDevices[deviceIndex].lastConnected = new Date().toISOString();
saveConfig();
}
}
return true;
}
return false;
}
catch (error) {
console.error('Error connecting to device:', error);
return false;
}
};
const disconnectDevice = async (ipAndPort) => {
try {
const { stdout } = await execPromise(`adb disconnect ${ipAndPort}`);
console.log(chalk_1.default.yellow(stdout.trim()));
return true;
}
catch (error) {
console.error('Error disconnecting from device:', error);
return false;
}
};
const getRunningServices = async (deviceId) => {
try {
const { stdout } = await execPromise(`adb -s ${deviceId} shell "ps | grep -E 'app_process|react|expo|metro'"`);
return stdout.split('\n').filter(line => line.trim() !== '');
}
catch (error) {
return [];
}
};
const generateConnectionQr = (ip, port = 5555) => {
const connectionString = `adbwireless://${ip}:${port}`;
console.log(chalk_1.default.blue(`Scan this QR code on your device to connect wirelessly:`));
qrcode_terminal_1.default.generate(connectionString, { small: true });
console.log(chalk_1.default.blue(`Or connect manually with: adb connect ${ip}:${port}`));
};
commander_1.program
.name('wireless-dev')
.description('CLI tool for wireless React Native/Expo development')
.version('1.0.0');
commander_1.program
.command('list')
.description('List all connected devices')
.action(async () => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
const spinner = (0, ora_1.default)('Getting connected devices...').start();
try {
const devices = await getConnectedDevices();
spinner.stop();
if (devices.length === 0) {
console.log(chalk_1.default.yellow('No devices connected. Use "wireless-dev discover" to find devices.'));
return;
}
const table = new cli_table3_1.default({
head: ['Device ID', 'Status', 'Model', 'Android', 'Type'],
colWidths: [30, 15, 25, 10, 10]
});
devices.forEach(device => {
table.push([
device.id,
chalk_1.default.green(device.status),
device.model || 'Unknown',
device.androidVersion || 'N/A',
device.wireless ? chalk_1.default.blue('Wireless') : chalk_1.default.yellow('USB')
]);
});
console.log(table.toString());
}
catch (error) {
spinner.fail('Failed to get devices');
console.error('Error:', error);
}
});
commander_1.program
.command('discover')
.description('Discover devices on the network')
.action(async () => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
const devices = await discoverDevices();
if (devices.length === 0) {
console.log(chalk_1.default.yellow('No devices discoverable on the network. Make sure they have wireless debugging enabled.'));
return;
}
const table = new cli_table3_1.default({
head: ['IP Address', 'Status'],
colWidths: [20, 15]
});
devices.forEach(device => {
table.push([
device.ip,
device.status === 'connected' ? chalk_1.default.green('Connected') : chalk_1.default.blue('Discoverable')
]);
});
console.log(table.toString());
}
catch (error) {
console.error('Error discovering devices:', error);
}
});
commander_1.program
.command('connect')
.description('Connect to a device wirelessly')
.option('-i, --ip <ip>', 'Device IP address and port (e.g., 192.168.1.100:5555)')
.action(async (options) => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
let ipAndPort = options.ip;
if (!ipAndPort) {
if (config.knownDevices && config.knownDevices.length > 0) {
const { device } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'device',
message: 'Select a device to connect to:',
choices: config.knownDevices.map(device => ({
name: `${device.model || 'Unknown'} (${device.ip})`,
value: device.ip
}))
}
]);
ipAndPort = device;
}
else {
const { ip } = await inquirer_1.default.prompt([
{
type: 'input',
name: 'ip',
message: 'Enter device IP and port (e.g., 192.168.1.100:5555):',
validate: (input) => {
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/.test(input)) {
return true;
}
return 'Please enter a valid IP address and optional port';
}
}
]);
ipAndPort = ip;
}
}
if (!(ipAndPort === null || ipAndPort === void 0 ? void 0 : ipAndPort.includes(':'))) {
ipAndPort = `${ipAndPort}:5555`;
}
const spinner = (0, ora_1.default)(`Connecting to ${ipAndPort}...`).start();
const connected = await connectToDevice(ipAndPort);
if (connected) {
spinner.succeed(`Connected to ${ipAndPort}`);
}
else {
spinner.fail(`Failed to connect to ${ipAndPort}`);
}
}
catch (error) {
console.error('Error connecting to device:', error);
}
});
commander_1.program
.command('disconnect')
.description('Disconnect from a wireless device')
.option('-i, --ip <ip>', 'Device IP address and port (e.g., 192.168.1.100:5555)')
.action(async (options) => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
let ipAndPort = options.ip;
if (!ipAndPort) {
const devices = await getConnectedDevices();
const wirelessDevices = devices.filter(device => device.wireless);
if (wirelessDevices.length === 0) {
console.log(chalk_1.default.yellow('No wireless devices connected.'));
return;
}
const { device } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'device',
message: 'Select a device to disconnect:',
choices: wirelessDevices.map(device => ({
name: `${device.model || 'Unknown'} (${device.id})`,
value: device.id
}))
}
]);
ipAndPort = device;
}
const spinner = (0, ora_1.default)(`Disconnecting from ${ipAndPort}...`).start();
const disconnected = await disconnectDevice(ipAndPort);
if (disconnected) {
spinner.succeed(`Disconnected from ${ipAndPort}`);
}
else {
spinner.fail(`Failed to disconnect from ${ipAndPort}`);
}
}
catch (error) {
console.error('Error disconnecting from device:', error);
}
});
commander_1.program
.command('enable-wireless')
.description('Enable wireless debugging on a USB connected device')
.option('-d, --device <deviceId>', 'Device ID (from adb devices)')
.action(async (options) => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
let deviceId = options.device;
if (!deviceId) {
const devices = await getConnectedDevices();
const usbDevices = devices.filter(device => !device.wireless && device.status === 'device');
if (usbDevices.length === 0) {
console.log(chalk_1.default.yellow('No USB devices connected. Connect a device via USB first.'));
return;
}
const { device } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'device',
message: 'Select a USB device to enable wireless debugging:',
choices: usbDevices.map(device => ({
name: `${device.model || 'Unknown'} (${device.id})`,
value: device.id
}))
}
]);
deviceId = device;
}
const spinner = (0, ora_1.default)(`Enabling wireless debugging on ${deviceId}...`).start();
const enabled = await enableWirelessDebugging(deviceId);
if (enabled) {
spinner.succeed(`Wireless debugging enabled on ${deviceId}`);
}
else {
spinner.fail(`Failed to enable wireless debugging on ${deviceId}`);
}
}
catch (error) {
console.error('Error enabling wireless debugging:', error);
}
});
commander_1.program
.command('info')
.description('Show details of connected device(s)')
.option('-d, --device <deviceId>', 'Device ID (from adb devices)')
.action(async (options) => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
let deviceId = options.device;
let devices = await getConnectedDevices();
if (devices.length === 0) {
console.log(chalk_1.default.yellow('No devices connected.'));
return;
}
if (deviceId) {
devices = devices.filter(device => device.id === deviceId);
if (devices.length === 0) {
console.log(chalk_1.default.red(`Device ${deviceId} not found.`));
return;
}
}
else if (devices.length > 1) {
const { device } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'device',
message: 'Select a device to show info:',
choices: devices.map(device => ({
name: `${device.model || 'Unknown'} (${device.id})`,
value: device.id
}))
}
]);
deviceId = device;
devices = devices.filter(d => d.id === deviceId);
}
const device = devices[0];
console.log(chalk_1.default.green('\n==== Device Information ===='));
console.log(chalk_1.default.blue(`ID: `) + device.id);
console.log(chalk_1.default.blue(`Status: `) + device.status);
console.log(chalk_1.default.blue(`Model: `) + (device.model || 'Unknown'));
console.log(chalk_1.default.blue(`Manufacturer: `) + (device.manufacturer || 'Unknown'));
console.log(chalk_1.default.blue(`Android Version: `) + (device.androidVersion || 'Unknown'));
console.log(chalk_1.default.blue(`Connection Type: `) + (device.wireless ? 'Wireless' : 'USB'));
console.log(chalk_1.default.green('\n==== Running Development Services ===='));
const services = await getRunningServices(device.id);
if (services.length === 0) {
console.log(chalk_1.default.yellow('No React Native/Expo services detected.'));
}
else {
services.forEach(service => {
console.log(service);
});
}
try {
const { stdout: packages } = await execPromise(`adb -s ${device.id} shell pm list packages | grep -E 'react|expo|debug'`);
console.log(chalk_1.default.green('\n==== Installed Development Packages ===='));
if (packages.trim()) {
packages.split('\n').forEach(pkg => {
if (pkg.trim()) {
console.log(pkg.replace('package:', '').trim());
}
});
}
else {
console.log(chalk_1.default.yellow('No development packages detected.'));
}
}
catch (error) {
console.log(chalk_1.default.yellow('No development packages detected.'));
}
}
catch (error) {
console.error('Error showing device info:', error);
}
});
commander_1.program
.command('qr')
.description('Generate QR code for wireless connection')
.option('-d, --device <deviceId>', 'Device ID to generate QR code for (must be connected via USB)')
.action(async (options) => {
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
if (options.device) {
await enableWirelessDebugging(options.device);
await new Promise(resolve => setTimeout(resolve, 2000));
}
const localIp = getLocalIpAddress();
console.log(chalk_1.default.yellow('\nNote: On your device, you need a QR scanner app that can handle "adbwireless://" protocol.'));
console.log(chalk_1.default.yellow('For some devices, you may need to connect manually with the command shown above.'));
}
catch (error) {
console.error('Error generating QR code:', error);
}
});
commander_1.program
.command('expo-start')
.description('Start Expo development server with QR code for wireless connection')
.option('-d, --device <deviceId>', 'Device ID to connect (must be already wireless or connected via USB)')
.action(async (options) => {
var _a, _b;
if (!checkAdbInstalled()) {
console.error(chalk_1.default.red('ADB is not installed or not in PATH. Please install Android SDK and add ADB to your PATH.'));
return;
}
try {
if (options.device) {
const devices = await getConnectedDevices();
const device = devices.find(d => d.id === options.device);
if (device && !device.wireless) {
console.log(chalk_1.default.blue('Device is connected via USB. Enabling wireless debugging...'));
await enableWirelessDebugging(options.device);
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
const localIp = getLocalIpAddress();
console.log(chalk_1.default.green(`Starting Expo server on ${localIp}...`));
console.log(chalk_1.default.blue('This will automatically use the wireless connection for your device.'));
const expoProcess = (0, child_process_1.exec)(`npx expo start --host ${localIp}`);
(_a = expoProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
console.log(data);
});
(_b = expoProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
console.error(chalk_1.default.red(data));
});
process.on('SIGINT', () => {
expoProcess.kill();
process.exit();
});
}
catch (error) {
console.error('Error starting Expo server:', error);
}
});
commander_1.program.parse(process.argv);
if (!process.argv.slice(2).length) {
commander_1.program.outputHelp();
}