diff --git a/lib/devicectl.ts b/lib/devicectl.ts index 44aad05..fef7200 100644 --- a/lib/devicectl.ts +++ b/lib/devicectl.ts @@ -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'; @@ -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( @@ -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; } diff --git a/lib/mixins/list.ts b/lib/mixins/list.ts new file mode 100644 index 0000000..16d0f76 --- /dev/null +++ b/lib/mixins/list.ts @@ -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 { + const {stdout} = await this.execute(['list', 'devices'], { + noDevice: true, + }); + return JSON.parse(stdout).result.devices; +} diff --git a/lib/types.ts b/lib/types.ts index 54e8ed6..cf40e51 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -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; @@ -111,3 +125,186 @@ export type AsyncExecuteResult = SubProcess; export type ExecuteResult = 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; +} \ No newline at end of file diff --git a/test/unit/devicectl-specs.ts b/test/unit/devicectl-specs.ts index 30fb2a3..7a6102b 100644 --- a/test/unit/devicectl-specs.ts +++ b/test/unit/devicectl-specs.ts @@ -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'); + }); + }); });