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
8 changes: 7 additions & 1 deletion lib/devicectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {ExecuteOptions, ExecuteResult} from './types';
import * as processMixins from './mixins/process';
import * as infoMixins from './mixins/info';
import * as copyMixins from './mixins/copy';
import * as listMixins from './mixins/list';

const XCRUN = 'xcrun';
const LOG_TAG = 'Devicectl';
Expand Down Expand Up @@ -44,11 +45,12 @@ export class Devicectl {
logStdout = false,
asynchronous = false,
asJson = true,
noDevice = false,
subcommandOptions,
timeout,
} = opts ?? {};

const finalArgs = ['devicectl', ...subcommand, '--device', this.udid];
const finalArgs = ['devicectl', ...subcommand, ...(noDevice ? [] : ['--device', this.udid])];

if (subcommandOptions && !_.isEmpty(subcommandOptions)) {
finalArgs.push(
Expand Down Expand Up @@ -85,8 +87,12 @@ export class Devicectl {
sendMemoryWarning = processMixins.sendMemoryWarning;
sendSignalToProcess = processMixins.sendSignalToProcess;
launchApp = processMixins.launchApp;

listProcesses = infoMixins.listProcesses;
listApps = infoMixins.listApps;

listFiles = copyMixins.listFiles;
pullFile = copyMixins.pullFile;

listDevices = listMixins.listDevices;
}
13 changes: 13 additions & 0 deletions lib/mixins/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type {DeviceInfo} from '../types';
import type {Devicectl} from '../devicectl';

/**
* Retrieves the list of connected device infos.
* Might be empty if no devices are connected.
*/
export async function listDevices(this: Devicectl): Promise<DeviceInfo[]> {
const {stdout} = await this.execute(['list', 'devices'], {
noDevice: true,
});
return JSON.parse(stdout).result.devices;
}
203 changes: 200 additions & 3 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,25 @@ export interface AppInfo {
* Options for executing devicectl commands
*/
export interface ExecuteOptions {
/** Whether to log stdout output */
/**
* Whether to drop the --device option from the actual devicectl command
* @default false
*/
noDevice?: boolean;
/**
* Whether to log stdout output
* @default false
*/
logStdout?: boolean;
/** Whether to return JSON output */
/**
* Whether to return JSON output
* @default true
*/
asJson?: boolean;
/** Whether to run the command asynchronously */
/**
* Whether to run the command asynchronously
* @default false
*/
asynchronous?: boolean;
/** Additional subcommand options */
subcommandOptions?: string[] | string;
Expand Down Expand Up @@ -111,3 +125,186 @@ export type AsyncExecuteResult = SubProcess;
export type ExecuteResult<T extends ExecuteOptions> = T extends AsyncExecuteOptions
? AsyncExecuteResult
: SyncExecuteResult;

/**
* CPU type information
*/
export interface CPUType {
/** The CPU type name
* @example "arm64e" */
name: string;
/** The CPU subtype
* @example 2 */
subType: number;
/** The CPU type identifier
* @example 16777228 */
type: number;
}

/**
* Device capability information
*/
export interface Capability {
/** The feature identifier
* @example "com.apple.coredevice.feature.installapp" */
featureIdentifier: string;
/** The capability name
* @example "Install Application" */
name: string;
}

/**
* Connection properties for the device
*/
export interface ConnectionProperties {
/** The authentication type
* @example "manualPairing" */
authenticationType: string;
/** Whether this is a mobile device only
* @example false */
isMobileDeviceOnly: boolean;
/** The last connection date in ISO format
* @example "2025-01-01T12:00:00.000Z" */
lastConnectionDate?: string;
/** List of local hostnames
* @example ["MyDevice.coredevice.local", "ABCD1234-5678-90EF-GHIJ-KLMNOPQRSTUV.coredevice.local"] */
localHostnames?: string[];
/** The pairing state
* @example "paired" */
pairingState: string;
/** List of potential hostnames
* @example ["MyDevice.coredevice.local", "ABCD1234-5678-90EF-GHIJ-KLMNOPQRSTUV.coredevice.local"] */
potentialHostnames: string[];
/** The transport type
* @example "wired" */
transportType?: string;
/** The tunnel IP address
* @example "fdda:f9b3:f5d9::1" */
tunnelIPAddress?: string;
/** The tunnel state
* @example "connected" */
tunnelState: string;
/** The tunnel transport protocol
* @example "tcp" */
tunnelTransportProtocol?: string;
}

/**
* Device properties
*/
export interface DeviceProperties {
/** The boot state
* @example "booted" */
bootState?: string;
/** Whether booted from snapshot
* @example true */
bootedFromSnapshot?: boolean;
/** The booted snapshot name
* @example "com.apple.os.update-ABCDEF1234567890" */
bootedSnapshotName?: string;
/** Whether DDI services are available
* @example true */
ddiServicesAvailable?: boolean;
/** The developer mode status
* @example "enabled" */
developerModeStatus?: string;
/** Whether has internal OS build
* @example false */
hasInternalOSBuild?: boolean;
/** The device name
* @example "My iPhone" */
name: string;
/** The OS build update
* @example "22A100" */
osBuildUpdate: string;
/** The OS version number
* @example "18.0.0" */
osVersionNumber: string;
/** Whether root file system is writable
* @example false */
rootFileSystemIsWritable?: boolean;
/** The screen viewing URL
* @example "devices://device/open?id=ABCD1234-5678-90EF-GHIJ-KLMNOPQRSTUV" */
screenViewingURL?: string;
/** Whether supports checked allocations
* @example false */
supportsCheckedAllocations?: boolean;
}

/**
* Hardware properties for the device
*/
export interface HardwareProperties {
/** The CPU type
* @example { name: "arm64e", subType: 2, type: 16777228 } */
cpuType: CPUType;
/** The device type
* @example "iPhone" */
deviceType: string;
/** The ECID
* @example 1234567890123456 */
ecid: number;
/** The hardware model
* @example "D63AP" */
hardwareModel: string;
/** The internal storage capacity in bytes
* @example 128000000000 */
internalStorageCapacity?: number;
/** Whether is production fused
* @example true */
isProductionFused?: boolean;
/** The marketing name
* @example "iPhone 15" */
marketingName?: string;
/** The platform
* @example "iOS", "tvOS" */
platform: string;
/** The product type
* @example "iPhone16,1" */
productType: string;
/** The reality type (physical or simulator)
* @example "physical" */
reality?: string;
/** The serial number
* @example "ABC1234XYZ" */
serialNumber?: string;
/** List of supported CPU types
* @example [{ name: "arm64e", subType: 2, type: 16777228 }, { name: "arm64", subType: 0, type: 16777228 }] */
supportedCPUTypes?: CPUType[];
/** List of supported device families
* @example [1, 2], [3] */
supportedDeviceFamilies: number[];
/** The thinning product type
* @example "iPhone16,1" */
thinningProductType?: string;
/** The UDID
* @example "00000000-0000000000000000" */
udid: string;
}

/**
* Complete device information
*/
export interface DeviceInfo {
/** List of device capabilities
* @example [{ featureIdentifier: "com.apple.coredevice.feature.installapp", name: "Install Application" }] */
capabilities: Capability[];
/** Connection properties
* @example { authenticationType: "manualPairing", pairingState: "paired", transportType: "wired" } */
connectionProperties: ConnectionProperties;
/** Device properties
* @example { name: "My iPhone", bootState: "booted", osVersionNumber: "18.0.0" } */
deviceProperties: DeviceProperties;
/** Hardware properties
* @example { deviceType: "iPhone", platform: "iOS", udid: "00000000-0000000000000000" } */
hardwareProperties: HardwareProperties;
/** The device identifier
* @example "ABCD1234-5678-90EF-GHIJ-KLMNOPQRSTUV" */
identifier: string;
/** List of tags
* @example [] */
tags: string[];
/** The visibility class
* @example "default" */
visibilityClass: string;
}
6 changes: 6 additions & 0 deletions test/unit/devicectl-specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ describe('Devicectl', function () {
expect(devicectl.launchApp).to.be.a('function');
});
});

describe('listDevices', function () {
it('should be a function', function () {
expect(devicectl.listDevices).to.be.a('function');
});
});
});