diff --git a/lib/listDevices.js b/lib/listDevices.js index a4f978d14..dab4d81eb 100644 --- a/lib/listDevices.js +++ b/lib/listDevices.js @@ -17,26 +17,45 @@ under the License. */ -const execa = require('execa'); - -const DEVICE_REGEX = /-o (iPhone|iPad|iPod)@.*?"USB Serial Number" = "([^"]*)"/gs; +const childProcess = require('node:child_process'); +const devicectl = require('devicectl'); /** - * Gets list of connected iOS devices - * @return {Promise} Promise fulfilled with list of available iOS devices + * Gets list of available iOS devices for deployment. + * @return {Promise} Promise fulfilled with list of available iOS devices. */ function listDevices () { - return execa('ioreg', ['-p', 'IOUSB', '-l']) - .then(({ stdout }) => { - return [...matchAll(stdout, DEVICE_REGEX)] - .map(m => m.slice(1).reverse().join(' ')); - }); + const availableDevices = listFromDeviceCtl(); + const connectedDevices = listFromUSB(); + + // We prefer devicectl for newer devices, so filter out any duplicate + // devices from the ioreg list. + // Sadly the UDID format is very slightly different between the two... + const targets = availableDevices.map(d => d.target.replace('-', '')); + const devices = [].concat(availableDevices, connectedDevices.filter(d => !targets.includes(d.target))); + + return Promise.resolve(devices.map(d => `${d.target} ${d.name}`)); } -// TODO: Should be replaced with String#matchAll once available -function * matchAll (s, r) { - let match; - while ((match = r.exec(s))) yield match; +function listFromDeviceCtl () { + const result = devicectl.list().json.result.devices; + + return result + .filter((d) => d.connectionProperties.transportType === 'wired') + .map((d) => ({ + target: d.hardwareProperties.udid, + name: `${d.deviceProperties.name} (${d.hardwareProperties.marketingName}, ${d.deviceProperties.osVersionNumber})` + })); +} + +const DEVICE_REGEX = /-o (iPhone|iPad|iPod)@.*?"USB Serial Number" = "([^"]*)"/gs; +function listFromUSB () { + const result = childProcess.spawnSync('ioreg', ['-p', 'IOUSB', '-l'], { encoding: 'utf8' }); + + return [...result.stdout.matchAll(DEVICE_REGEX)].map((m) => ({ + target: m.pop(), + name: m.slice(1).reverse().join(' ') + })); } exports.run = listDevices; diff --git a/lib/run.js b/lib/run.js index 9df8231fd..f61896d8d 100644 --- a/lib/run.js +++ b/lib/run.js @@ -22,6 +22,7 @@ const path = require('node:path'); const bplist = require('bplist-parser'); const plist = require('plist'); const execa = require('execa'); +const devicectl = require('devicectl'); const { CordovaError, events } = require('cordova-common'); const build = require('./build'); const check_reqs = require('./check_reqs'); @@ -64,7 +65,11 @@ module.exports.run = function (runOptions) { // we also explicitly set device flag in options as we pass // those parameters to other api (build as an example) runOptions.device = true; - return check_reqs.check_ios_deploy(); + runOptions.target = devices[0].split(' ')[0]; + + if (!runOptions.target.includes('-')) { + return check_reqs.check_ios_deploy(); + } } }); } @@ -90,7 +95,7 @@ module.exports.run = function (runOptions) { const buildOutputDir = path.join(projectPath, 'build', `${configuration}-iphoneos`); const appPath = path.join(buildOutputDir, `${productName}.app`); - return module.exports.checkDeviceConnected() + return module.exports.checkDeviceConnected(runOptions.target) .then(() => { // Unpack IPA const ipafile = path.join(buildOutputDir, `${productName}.ipa`); @@ -172,8 +177,20 @@ function filterSupportedArgs (args) { * Checks if any iOS device is connected * @return {Promise} Fullfilled when any device is connected, rejected otherwise */ -function checkDeviceConnected () { - return execa('ios-deploy', ['-c', '-t', '1'], { stdio: 'inherit' }); +function checkDeviceConnected (target = '') { + if (target.includes('-')) { + const details = devicectl.info(devicectl.InfoTypes.Details, target).json?.result; + + return new Promise((resolve, reject) => { + if (details?.connectionProperties?.transportType === 'wired') { + resolve(); + } else { + reject(new CordovaError(`Run destination device '${target}' is not connected.`)); + } + }); + } else { + return execa('ios-deploy', ['-c', '-t', '1'], { stdio: 'inherit' }); + } } /** @@ -184,13 +201,19 @@ function checkDeviceConnected () { */ function deployToDevice (appPath, target, extraArgs) { events.emit('log', 'Deploying to device'); - const args = ['--justlaunch', '-d', '-b', appPath]; - if (target) { - args.push('-i', target); + if (target.includes('-')) { + devicectl.install(target, appPath, { stdio: 'inherit' }); + return getBundleIdentifierFromApp(appPath) + .then(appIdentifier => devicectl.launch(target, appIdentifier, extraArgs, { stdio: 'inherit', console: true })); } else { - args.push('--no-wifi'); + const args = ['--justlaunch', '-d', '-b', appPath]; + if (target) { + args.push('-i', target); + } else { + args.push('--no-wifi'); + } + return execa('ios-deploy', args.concat(extraArgs), { stdio: 'inherit' }); } - return execa('ios-deploy', args.concat(extraArgs), { stdio: 'inherit' }); } /** @@ -223,48 +246,52 @@ async function deployToSim (appPath, target) { return module.exports.startSim(appPath, target); } -async function startSim (appPath, target) { - const projectPath = path.join(path.dirname(appPath), '../..'); - const logPath = path.join(projectPath, 'cordova/console.log'); - const deviceTypeId = `com.apple.CoreSimulator.SimDeviceType.${target}`; - - try { - const infoPlistPath = path.join(appPath, 'Info.plist'); - if (!fs.existsSync(infoPlistPath)) { - throw new Error(`${infoPlistPath} file not found.`); - } - - bplist.parseFile(infoPlistPath, function (err, obj) { - let appIdentifier; +function getBundleIdentifierFromApp (appPath) { + const infoPlistPath = path.join(appPath, 'Info.plist'); + if (!fs.existsSync(infoPlistPath)) { + return Promise.reject(new Error(`${infoPlistPath} file not found.`)); + } + return new Promise((resolve, reject) => { + bplist.parseFile(infoPlistPath, (err, obj) => { if (err) { obj = plist.parse(fs.readFileSync(infoPlistPath, 'utf8')); if (obj) { - appIdentifier = obj.CFBundleIdentifier; + return resolve(obj.CFBundleIdentifier); } else { - throw err; + return reject(err); } } else { - appIdentifier = obj[0].CFBundleIdentifier; + return resolve(obj[0].CFBundleIdentifier); } + }); + }); +} - // get the deviceid from --devicetypeid - // --devicetypeid is a string in the form "devicetype, runtime_version" (optional: runtime_version) - const device = getDeviceFromDeviceTypeId(deviceTypeId); +async function startSim (appPath, target) { + const projectPath = path.join(path.dirname(appPath), '../..'); + const logPath = path.join(projectPath, 'cordova/console.log'); + const deviceTypeId = `com.apple.CoreSimulator.SimDeviceType.${target}`; - // so now we have the deviceid, we can proceed - try { - startSimulator(device, { appPath, appIdentifier, logPath, waitForDebugger: false }); - } catch (e) { - events.emit('warn', `Failed to start simulator with error: "${e.message}"`); - } + try { + const appIdentifier = await getBundleIdentifierFromApp(appPath); - if (logPath) { - events.emit('log', `Log Path: ${path.resolve(logPath)}`); - } + // get the deviceid from --devicetypeid + // --devicetypeid is a string in the form "devicetype, runtime_version" (optional: runtime_version) + const device = getDeviceFromDeviceTypeId(deviceTypeId); - process.exit(0); - }); + // so now we have the deviceid, we can proceed + try { + startSimulator(device, { appPath, appIdentifier, logPath, waitForDebugger: false }); + } catch (e) { + events.emit('warn', `Failed to start simulator with error: "${e.message}"`); + } + + if (logPath) { + events.emit('log', `Log Path: ${path.resolve(logPath)}`); + } + + process.exit(0); } catch (e) { events.emit('warn', `Failed to launch simulator with error: ${e.message}`); process.exit(1); diff --git a/package-lock.json b/package-lock.json index a200f57af..d6f114c95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bplist-parser": "^0.3.2", "cordova-common": "^6.0.0", + "devicectl": "^0.0.2", "elementtree": "^0.1.7", "execa": "^5.1.1", "nopt": "^9.0.0", @@ -102,15 +103,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -143,9 +144,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -156,7 +157,7 @@ "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -180,9 +181,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -459,13 +460,16 @@ "license": "MIT" }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -860,18 +864,18 @@ } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/color-convert": { @@ -1100,6 +1104,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/devicectl": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/devicectl/-/devicectl-0.0.2.tgz", + "integrity": "sha512-DbGQ7LokAC7fDki1owNXyt5XuyDiwGyR2ki9hMlnJtYe2gPFD3rK0oM9JjJl9OJ/33/9+nnjHbUyucH40xtbqg==", + "license": "MIT", + "engines": { + "node": ">= 14.17.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1154,9 +1167,9 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, @@ -1172,9 +1185,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1358,25 +1371,25 @@ } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -1395,7 +1408,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -1865,9 +1878,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true, "license": "ISC" }, @@ -1994,6 +2007,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2118,9 +2144,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { @@ -2546,16 +2572,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -2988,9 +3004,9 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3516,16 +3532,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -3902,18 +3908,21 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string.prototype.trim": { @@ -3976,16 +3985,19 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-bom": { @@ -4086,9 +4098,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { @@ -4469,23 +4481,36 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/xcode": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", @@ -4519,22 +4544,21 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { @@ -4547,6 +4571,16 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 769c38a66..56b6575e2 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,15 @@ "engines": { "node": "^20.17.0 || >=22.9.0" }, + "exports": { + ".": "./lib/Api.js", + "./package.json": "./package.json", + "./lib/*": "./lib/*.js" + }, "dependencies": { "bplist-parser": "^0.3.2", "cordova-common": "^6.0.0", + "devicectl": "^0.0.2", "elementtree": "^0.1.7", "execa": "^5.1.1", "nopt": "^9.0.0", @@ -56,5 +62,10 @@ "lcov", "text" ] + }, + "overrides": { + "c8": { + "yargs": "^18.0.0" + } } } diff --git a/tests/spec/fixtures/sample-ioreg-output.txt b/tests/spec/fixtures/sample-ioreg-output.txt index e997c2a3a..4eae6ef1c 100644 --- a/tests/spec/fixtures/sample-ioreg-output.txt +++ b/tests/spec/fixtures/sample-ioreg-output.txt @@ -24,6 +24,72 @@ | "IOKitDiagnostics" = {"Foobar"="Foobar"} | } | + +-o AppleT6000USBXHCI@02000000 + | | { + | | "IOClass" = "AppleT6000USBXHCI" + | | "kUSBSleepPortCurrentLimit" = 3000 + | | "UsbHostControllerSoftRetryPolicy" = 0 + | | "IOPersonalityPublisher" = "com.apple.driver.usb.AppleSynopsysUSB40XHCI" + | | "IOMatchedAtBoot" = Yes + | | "IOPowerManagement" = {"ChildrenPowerState"=3,"DevicePowerState"=0,"CurrentPowerState"=3,"CapabilityFlags"=32768,"MaxPowerState"=3,"DriverPowerState"=0} + | | "IOProviderClass" = "AppleARMIODevice" + | | "IOProbeScore" = 10002 + | | "locationID" = 33554432 + | | "kUSBWakePortCurrentLimit" = 3000 + | | "IONameMatch" = "usb-drd,t6000" + | | "CFBundleIdentifierKernel" = "com.apple.driver.usb.AppleSynopsysUSB40XHCI" + | | "UsbHostControllerDeferRegisterService" = Yes + | | "IOMatchCategory" = "usb-host" + | | "CFBundleIdentifier" = "com.apple.driver.usb.AppleSynopsysUSB40XHCI" + | | "Revision" = <0103> + | | "IONameMatched" = "usb-drd,t6000" + | | "UsbHostControllerUSB4LPMPolicy" = 1 + | | "UsbHostControllerTierLimit" = 6 + | | "controller-statistics" = {"kControllerStatIOCount"=46575,"kControllerStatPowerStateTime"={"kPowerStateInitialize"="0ms (0%)","kPowerStateOff"="327655037ms (97%)","kPowerStateSle$ + | | "kUSBSleepSupported" = Yes + | | } + | | + | +-o iPad@02100000 + | { + | "kUSBSerialNumberString" = "IPADMINI_UDID" + | "bDeviceClass" = 0 + | "UsbAppleDeviceECID" = 6259374711472158 + | "bDeviceSubClass" = 0 + | "iSerialNumber" = 3 + | "UsbPowerSinkCapability" = 2400 + | "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb")) + | "iProduct" = 2 + | "USB Serial Number" = "IPADMINI_UDID" + | "USB Vendor Name" = "Apple Inc." + | "USBSpeed" = 3 + | "IOPowerManagement" = {"PowerOverrideOn"=Yes,"DevicePowerState"=2,"CurrentPowerState"=2,"CapabilityFlags"=32768,"MaxPowerState"=2,"DriverPowerState"=0} + | "bNumConfigurations" = 5 + | "kUSBProductString" = "iPad" + | "kCDCDoNotMatchThisDevice" = Yes + | "kCallInterfaceOpenWithGate" = Yes + | "kUSBVendorString" = "Apple Inc." + | "USB Product Name" = "iPad" + | "iManufacturer" = 1 + | "idVendor" = 1452 + | "Device Speed" = 2 + | "kUSBCurrentConfiguration" = 5 + | "idProduct" = 4779 + | "bcdDevice" = 5121 + | "UsbDeviceSignature" = + | "sessionID" = 7864622857660 + | "USB Address" = 1 + | "kUSBPreferredConfiguration" = 3 + | "IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"} + | "SupportsIPhoneOS" = Yes + | "USBPortType" = 0 + | "bDeviceProtocol" = 0 + | "locationID" = 34603008 + | "kUSBAddress" = 1 + | "bcdUSB" = 528 + | "IOGeneralInterest" = "IOCommand is not serializable" + | "bMaxPacketSize0" = 64 + | } + | +-o AppleUSBXHCI Root Hub Simulation@14000000 | { | "iManufacturer" = 0 diff --git a/tests/spec/list-devices.spec.js b/tests/spec/list-devices.spec.js deleted file mode 100644 index 9bcaacae7..000000000 --- a/tests/spec/list-devices.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ - -const fs = require('node:fs'); -const path = require('node:path'); -const rewire = require('rewire'); - -const list_devices = rewire('../../lib/listDevices'); - -const sampleData = fs.readFileSync(path.resolve(__dirname, 'fixtures/sample-ioreg-output.txt'), 'utf-8'); - -describe('cordova/lib/listDevices', () => { - describe('run method', () => { - let execaSpy; - - beforeEach(() => { - execaSpy = jasmine.createSpy('execa').and.resolveTo({ stdout: sampleData }); - list_devices.__set__('execa', execaSpy); - }); - - it('should trim and split standard output and return as array', () => { - return list_devices.run() - .then(results => { - expect(execaSpy).toHaveBeenCalledWith('ioreg', ['-p', 'IOUSB', '-l']); - expect(results).toEqual([ - 'THE_IPHONE_SERIAL iPhone', - 'THE_IPAD_SERIAL iPad' - ]); - }); - }); - }); -}); diff --git a/tests/spec/listDevices.spec.js b/tests/spec/listDevices.spec.js new file mode 100644 index 000000000..17faf32ae --- /dev/null +++ b/tests/spec/listDevices.spec.js @@ -0,0 +1,97 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +const fs = require('node:fs'); +const path = require('node:path'); +const childProcess = require('node:child_process'); +const devicectl = require('devicectl'); + +const list_devices = require('../../lib/listDevices'); + +const sampleData = fs.readFileSync(path.resolve(__dirname, 'fixtures/sample-ioreg-output.txt'), 'utf-8'); + +const sampleDevicectl = { + result: { + devices: [ + { + deviceProperties: { + name: 'test_iPad', + osBuildUpdate: '23D127', + osVersionNumber: '26.3' + }, + connectionProperties: { + transportType: 'wired' + }, + hardwareProperties: { + deviceType: 'iPad', + marketingName: 'iPad mini (6th generation)', + platform: 'iOS', + productType: 'iPad14,1', + thinningProductType: 'iPad14,1', + udid: 'IPAD-MINI_UDID' + }, + identifier: '8F721AAF-DEDC-4D98-9098-AEF2E78DAA36' + }, + { + deviceProperties: { + name: 'test_iPad2', + osBuildUpdate: '23D127', + osVersionNumber: '26.3' + }, + connectionProperties: { + transportType: 'localNetwork' + }, + hardwareProperties: { + deviceType: 'iPad', + marketingName: 'iPad mini (6th generation)', + platform: 'iOS', + productType: 'iPad14,1', + thinningProductType: 'iPad14,1', + udid: 'IPAD_MINI_2_UDID' + }, + identifier: '4F3355A3-505E-4533-B36A-3DB1C902D1C4' + } + ] + } +}; + +describe('listDevices', () => { + describe('run method', () => { + let spawnMock; + beforeEach(function () { + spawnMock = spyOn(childProcess, 'spawnSync'); + }); + + it('should trim and split standard output and return as array', () => { + spawnMock.and.returnValue({ stdout: sampleData }); + spyOn(devicectl, 'list').and.returnValue({ json: sampleDevicectl }); + + return list_devices.run() + .then(results => { + expect(spawnMock).toHaveBeenCalledWith('ioreg', ['-p', 'IOUSB', '-l'], { encoding: 'utf8' }); + + expect(results).toEqual([ + 'IPAD-MINI_UDID test_iPad (iPad mini (6th generation), 26.3)', + 'THE_IPHONE_SERIAL iPhone', + 'THE_IPAD_SERIAL iPad' + ]); + }); + }); + }); +});