This is an automated email from the ASF dual-hosted git repository.

erisu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-paramedic.git


The following commit(s) were added to refs/heads/master by this push:
     new e376dc0  feat!: drop shelljs, execa, exec & some refactor (#286)
e376dc0 is described below

commit e376dc016fa8d70d6c461f202da970ef186c650a
Author: エリス <[email protected]>
AuthorDate: Tue Dec 16 11:24:35 2025 +0900

    feat!: drop shelljs, execa, exec & some refactor (#286)
    
    * feat!: drop shelljs, execa, exec, & partial refactor
    * refactor!: stabilize testing connection & on events
---
 .github/workflows/ios.yml      |   2 +-
 lib/ParamedicApp.js            | 134 ++++++++++-----
 lib/ParamedicAppUninstall.js   |  78 ++++-----
 lib/ParamedicKill.js           |  80 +++++----
 lib/ParamedicLogCollector.js   | 103 ++++++-----
 lib/ParamedicTargetChooser.js  |  44 +++--
 lib/ParamediciOSPermissions.js |  59 ++++---
 lib/PluginsManager.js          |  64 ++++---
 lib/paramedic.js               | 378 +++++++++++++++++++----------------------
 lib/utils/execWrapper.js       |  53 ------
 lib/utils/index.js             |   5 +-
 lib/utils/spawn.js             | 136 +++++++++++++++
 lib/utils/utilities.js         |  85 +++------
 package-lock.json              | 134 +--------------
 package.json                   |   2 -
 15 files changed, 673 insertions(+), 684 deletions(-)

diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml
index a1846fa..df865aa 100644
--- a/.github/workflows/ios.yml
+++ b/.github/workflows/ios.yml
@@ -64,7 +64,7 @@ jobs:
 
           - os-version: macos-26
             ios-version: 26.x
-            xcode-version: 26.x
+            xcode-version: 26.1
 
     steps:
       - uses: actions/checkout@v6
diff --git a/lib/ParamedicApp.js b/lib/ParamedicApp.js
index a5cb143..cb166e5 100644
--- a/lib/ParamedicApp.js
+++ b/lib/ParamedicApp.js
@@ -20,10 +20,10 @@
 */
 
 const tmp = require('tmp');
-const shell = require('shelljs');
 const path = require('path');
+const fs = require('node:fs');
 const PluginsManager = require('./PluginsManager');
-const { logger, exec, execPromise, utilities } = require('./utils');
+const { logger, spawnAsync, utilities } = require('./utils');
 
 class ParamedicApp {
     constructor (config, storedCWD, runner) {
@@ -34,21 +34,28 @@ class ParamedicApp {
 
         this.platformId = this.config.getPlatformId();
         this.isAndroid = this.platformId === utilities.ANDROID;
-        this.isBrowser = this.platformId === utilities.BROWSER;
         this.isIos = this.platformId === utilities.IOS;
 
         
logger.info('---------------------------------------------------------');
         logger.info('1. Create Cordova app with platform and plugin(s) to 
test');
-        logger.info('- platform: ' + this.config.getPlatformId());
+        logger.info('- platform: ' + this.platformId);
         logger.info('- plugin(s): ' + this.config.getPlugins().join(', '));
         
logger.info('---------------------------------------------------------');
     }
 
-    createTempProject () {
+    /**
+     * Creates a Cordova project inside a newly created temporary directory.
+     *
+     * @returns {Promise<Object>} Object contains the directory path on the 
name property.
+     */
+    async createTempProject () {
         this.tempFolder = tmp.dirSync();
         tmp.setGracefulCleanup();
-        logger.info('cordova-paramedic: creating temp project at ' + 
this.tempFolder.name);
-        exec(this.config.getCli() + ' create ' + this.tempFolder.name + 
utilities.PARAMEDIC_COMMON_CLI_ARGS);
+        logger.info('[paramedic] Creating temp project at ' + 
this.tempFolder.name);
+        await spawnAsync(
+            this.config.getCli(),
+            ['create', this.tempFolder.name, 
...utilities.PARAMEDIC_COMMON_ARGS]
+        );
         return this.tempFolder;
     }
 
@@ -61,72 +68,107 @@ class ParamedicApp {
             .then(() => this.checkDumpAndroidConfigXml());
     }
 
-    installPlugins () {
-        logger.info('cordova-paramedic: installing plugins');
+    /**
+     * Installs testing related framework plugins and user defined plugin.
+     *
+     * (For All Platforms)
+     *   - cordova-plugin-test-framework
+     *   - paramedic-plugin
+     * (For iOS Platform)
+     *   - ios-geolocation-permissions-plugin (iOS)
+     * (For CI)
+     *   - ci-plugin
+     * (User Defined Plugins)
+     * (User Defined Plugin's Test)
+     */
+    async installPlugins () {
         const pluginsManager = new PluginsManager(this.tempFolder.name, 
this.storedCWD, this.config);
-
         const ciFrameworkPlugins = 
['github:apache/cordova-plugin-test-framework', path.join(__dirname, '..', 
'paramedic-plugin')];
 
         if (this.isIos) {
             ciFrameworkPlugins.push(path.join(__dirname, '..', 
'ios-geolocation-permissions-plugin'));
         }
-
         if (this.config.isCI()) {
             ciFrameworkPlugins.push(path.join(__dirname, '..', 'ci-plugin'));
         }
 
         // Install testing framework
-        logger.info('cordova-paramedic: installing ci framework plugins: ' + 
ciFrameworkPlugins.join(', '));
-        pluginsManager.installPlugins(ciFrameworkPlugins);
-        logger.info('cordova-paramedic: installing plugins:' + 
this.config.getPlugins().join(', '));
-        pluginsManager.installPlugins(this.config.getPlugins());
-        logger.info('cordova-paramedic: installing tests for existing 
plugins');
-        pluginsManager.installTestsForExistingPlugins();
+        logger.info(`[paramedic] Installing CI Plugins:\n\t - 
${ciFrameworkPlugins.join('\n\t - ')}`);
+        await pluginsManager.installPlugins(ciFrameworkPlugins);
+        logger.info(`[paramedic] Installing Plugins:\n\t - 
${this.config.getPlugins().join('\n\t - ')}`);
+        await pluginsManager.installPlugins(this.config.getPlugins());
+        logger.info('[paramedic] Installing tests for existing plugins.');
+        await pluginsManager.installTestsForExistingPlugins();
     }
 
+    /**
+     * Edits the the testing application's content source to 
"cdvtests/index.html"
+     */
     setUpStartPage () {
-        logger.normal('cordova-paramedic: setting the app start page to the 
test page');
-        shell.sed('-i', 'src="index.html"', 'src="cdvtests/index.html"', 
'config.xml');
+        logger.normal('[paramedic] Setting the app start page to the test 
page');
+        const filePath = path.join(this.tempFolder.name, 'config.xml');
+        let config = fs.readFileSync(filePath, utilities.DEFAULT_ENCODING);
+        config = config.replace('src="index.html"', 
'src="cdvtests/index.html"');
+        fs.writeFileSync(filePath, config, utilities.DEFAULT_ENCODING);
     }
 
+    /**
+     * Installs the Cordova platform for testing
+     *
+     * @returns {Promise<Object|Error>}
+     */
     installPlatform () {
-        const platform = this.config.getPlatform();
-        logger.info('cordova-paramedic: adding platform ' + platform + ' 
(with: ' + utilities.PARAMEDIC_COMMON_CLI_ARGS + 
utilities.PARAMEDIC_PLATFORM_ADD_ARGS + ')');
-
-        return execPromise(this.config.getCli() + ' platform add ' + platform 
+ utilities.PARAMEDIC_COMMON_CLI_ARGS + utilities.PARAMEDIC_PLATFORM_ADD_ARGS)
-            .then(() => {
-                logger.info('cordova-paramedic: successfully finished adding 
platform ' + platform);
-            });
+        return spawnAsync(
+            this.config.getCli(),
+            ['platform', 'add', this.platformId, 
...utilities.PARAMEDIC_COMMON_ARGS],
+            { cwd: this.tempFolder.name }
+        );
     }
 
-    checkPlatformRequirements () {
-        if (this.isBrowser) return Promise.resolve();
-
-        logger.normal('cordova-paramedic: checking the requirements for 
platform: ' + this.platformId);
-        return execPromise(this.config.getCli() + ' requirements ' + 
this.platformId + utilities.PARAMEDIC_COMMON_CLI_ARGS)
-            .then(() => {
-                logger.info('cordova-paramedic: successfully finished checking 
the requirements for platform: ' + this.platformId);
-            });
+    /**
+     * Gets the platform reqirements
+     *
+     * @returns {Promise<Object|Error>}
+     */
+    async checkPlatformRequirements () {
+        const requirements = await spawnAsync(
+            this.config.getCli(),
+            ['requirements', this.platformId, 
...utilities.PARAMEDIC_COMMON_ARGS],
+            { cwd: this.tempFolder.name }
+        );
+        logger.normal(requirements.stdout);
     }
 
+    /**
+     * Fetches and dumps out the AndroidManifest.xml content.
+     *
+     * @return If not running for Android platform, return out.
+     */
     checkDumpAndroidManifest () {
-        if (!this.isAndroid) return Promise.resolve();
+        if (!this.isAndroid) {
+            return;
+        }
 
-        logger.normal('cordova-paramedic: start AndroidManifest.xml Dump');
-        return execPromise('cat 
./platforms/android/app/src/main/AndroidManifest.xml')
-            .then(() => {
-                logger.normal('cordova-paramedic: end AndroidManifest.xml 
Dump');
-            });
+        logger.normal('[paramedic] AndroidManifest.xml Dump');
+        const androidManifest = path.join(this.tempFolder.name, 
'platforms/android/app/src/main/AndroidManifest.xml');
+        const xml = fs.readFileSync(androidManifest, 
utilities.DEFAULT_ENCODING);
+        logger.normal(xml);
     }
 
+    /**
+     * Fetches and dumps out the Android's compiled config.xml content.
+     *
+     * @return If not running for Android platform, return out.
+     */
     checkDumpAndroidConfigXml () {
-        if (!this.isAndroid) return Promise.resolve();
+        if (!this.isAndroid) {
+            return;
+        }
 
-        logger.normal('cordova-paramedic: start config.xml Dump');
-        return execPromise('cat 
./platforms/android/app/src/main/res/xml/config.xml')
-            .then(() => {
-                logger.info('cordova-paramedic: end config.xml Dump');
-            });
+        logger.normal('[paramedic] config.xml Dump');
+        const config = path.join(this.tempFolder.name, 
'platforms/android/app/src/main/res/xml/config.xml');
+        const xml = fs.readFileSync(config, utilities.DEFAULT_ENCODING);
+        logger.normal(xml);
     }
 }
 
diff --git a/lib/ParamedicAppUninstall.js b/lib/ParamedicAppUninstall.js
index f386b95..c14cb24 100644
--- a/lib/ParamedicAppUninstall.js
+++ b/lib/ParamedicAppUninstall.js
@@ -17,9 +17,7 @@
     under the License.
 */
 
-const { exec } = require('node:child_process');
-
-const { logger, utilities } = require('./utils');
+const { utilities, spawnAsync } = require('./utils');
 
 class ParamedicAppUninstall {
     constructor (appPath, platform) {
@@ -27,18 +25,25 @@ class ParamedicAppUninstall {
         this.platform = platform;
     }
 
-    async uninstallApp (targetObj, app) {
-        if (!targetObj || !targetObj.target) {
+    /**
+     * Uninstall application from emulator based on provided target and 
application identifier.
+     *
+     * @param {Object} target Object of emulator/target related information.
+     * @param {String} appId The application/bundle identifier.
+     * @returns {Promise<Boolean>}
+     */
+    async uninstallApp (target, appId) {
+        if (!target || !target.target) {
             return false;
         }
 
         switch (this.platform) {
         case utilities.ANDROID:
-            await this.uninstallAppAndroid(targetObj, app);
+            await this.uninstallAppAndroid(target, appId);
             return true;
 
         case utilities.IOS:
-            await this.uninstallAppIOS(targetObj, app);
+            await this.uninstallAppIOS(target, appId);
             return true;
 
         default:
@@ -46,51 +51,32 @@ class ParamedicAppUninstall {
         }
     }
 
-    uninstallAppAndroid (targetObj, app) {
-        const uninstallCommand = 'adb -s ' + targetObj.target + ' uninstall ' 
+ app;
-        return this.executeUninstallCommand(uninstallCommand);
+    /**
+     * Uninstalls the application from the Android target by application ID
+     *
+     * @param {Object} target The device/emulator data which contains the 
device target.
+     * @param {String} appId The application ID
+     */
+    uninstallAppAndroid (target, appId) {
+        return spawnAsync(
+            'adb',
+            ['-s', target.target, 'uninstall', appId],
+            { cwd: this.appPath, timeout: 60000 }
+        );
     }
 
     /**
-     * Uninstalls the Application form target by Bundle Identifier
+     * Uninstalls the application from the iOS target by Bundle ID
      *
      * @param {Object} target The device/emulator data which contains the 
device and UUID.
-     * @param {String} appBundleIdentifier The Application Bundle Identifier
+     * @param {String} bundleId The application's bundle ID
      */
-    uninstallAppIOS (target, appBundleIdentifier) {
-        return this.executeUninstallCommand(`xcrun simctl uninstall 
${target.simId} ${appBundleIdentifier}`);
-    }
-
-    // TODO: Remove this for a centralized spawnAsync utility
-    async executeUninstallCommand (uninstallCommand) {
-        logger.info('[paramedic] Running command: ' + uninstallCommand);
-
-        const execPromise = new Promise((resolve, reject) => {
-            exec(uninstallCommand, (error, stdout, stderr) => {
-                if (!error) {
-                    resolve();
-                } else {
-                    logger.error('[paramedic] Failed to uninstall the app');
-                    logger.error('[paramedic] Error code: ' + error.code);
-                    logger.error('[paramedic] stderr: ' + stderr);
-                    reject(error);
-                }
-            });
-        });
-
-        const timeoutPromise = new Promise((resolve, reject) => {
-            setTimeout(() => reject(new Error('timeout')), 60000);
-        });
-
-        try {
-            await Promise.race([execPromise, timeoutPromise]);
-        } catch (err) {
-            if (err.message === 'timeout') {
-                logger.warn('[paramedic] App uninstall timed out!');
-            } else {
-                logger.warn('[paramedic] App uninstall error: ' + err.message);
-            }
-        }
+    uninstallAppIOS (target, bundleId) {
+        return spawnAsync(
+            'xcrun',
+            ['simctl', 'uninstall', target.simId, bundleId],
+            { cwd: this.appPath, timeout: 60000 }
+        );
     }
 }
 
diff --git a/lib/ParamedicKill.js b/lib/ParamedicKill.js
index 67bc0a9..73e565b 100644
--- a/lib/ParamedicKill.js
+++ b/lib/ParamedicKill.js
@@ -19,8 +19,7 @@
     under the License.
 */
 
-const shelljs = require('shelljs');
-const { logger, exec, utilities } = require('./utils');
+const { logger, utilities, spawnAsync } = require('./utils');
 
 class ParamedicKill {
     constructor (platform) {
@@ -28,10 +27,6 @@ class ParamedicKill {
     }
 
     kill () {
-        // shell config
-        shelljs.config.fatal = false;
-        shelljs.config.silent = false;
-
         // get platform tasks
         const platformTasks = this.tasksOnPlatform(this.platform);
 
@@ -70,45 +65,60 @@ class ParamedicKill {
         return tasks;
     }
 
-    killTasks (taskNames) {
-        if (!taskNames || taskNames.length < 1) return;
-
-        const command = this.getKillCommand(taskNames);
-
-        logger.info('Running the following command:');
-        logger.info('    ' + command);
-
-        const killTasksResult = exec(command);
-        if (killTasksResult.code !== 0) {
-            console.warn('WARNING: kill command returned ' + 
killTasksResult.code);
+    /**
+     * Attempts to kill the provided tasks by name.
+     *
+     * The kill command is determined by the isWindows flag.
+     * If running on a Windows environment, 'taskkill' will be
+     * used. If on a macOS or Linux, 'killall' is used.
+     *
+     * If process fails, only a warning will be displayed.
+     *
+     * @param {Array} taskNames List of tasks to kill.
+     * @returns {Promise}
+     */
+    async killTasks (taskNames) {
+        if (!taskNames || taskNames.length < 1) {
+            return;
         }
-    }
 
-    getKillCommand (taskNames) {
-        const cli = utilities.isWindows()
-            ? 'taskkill /t /F'
-            : 'killall -9';
+        const cmd = utilities.isWindows()
+            ? 'taskkill'
+            : 'killall';
 
-        const args = utilities.isWindows()
-            ? taskNames.map(name => `/IM "${name}"`)
-            : taskNames.map(name => `"${name}"`);
+        let args = utilities.isWindows()
+            ? ['/t', '/F']
+            : ['-9'];
 
-        return cli + ' ' + args.join(' ');
-    }
+        const taskArgs = utilities.isWindows()
+            ? taskNames.map(name => ['/IM', `"${name}"`])
+            : taskNames.map(name => [`"${name}"`]);
 
-    killAdbServer () {
-        logger.info('Killing the adb server');
-        const killServerCommand = 'adb kill-server';
+        // Attach to the args the processes that will be killed
+        for (const task of taskArgs) {
+            args = [...args, ...task];
+        }
 
-        logger.info('Running the following command:');
-        logger.info('    ' + killServerCommand);
+        // Attempt to kill the processes
+        const killTasksResult = await spawnAsync(cmd, args);
+        if (killTasksResult.code !== 0) {
+            console.warn('[paramedic] WARNING: Kill command returned ' + 
killTasksResult.code);
+        }
+    }
 
-        const killServerResult = exec(killServerCommand);
+    /**
+     * Attempts to kill the ADB Server.
+     *
+     * If process fails, only a warning will be displayed.
+     */
+    async killAdbServer () {
+        logger.info('[paramedic] Killing the adb server');
+        const killServerResult = await spawnAsync('adb', ['kill-server']);
         if (killServerResult.code !== 0) {
-            logger.error('Failed to kill the adb server with the code: ' + 
killServerResult.code);
+            logger.error('[paramedic] Failed to kill the adb server with the 
code: ' + killServerResult.code);
         }
 
-        logger.info('Killed the adb server.');
+        logger.info('[paramedic] Killed the adb server.');
     }
 }
 
diff --git a/lib/ParamedicLogCollector.js b/lib/ParamedicLogCollector.js
index 41c6d45..19b603e 100644
--- a/lib/ParamedicLogCollector.js
+++ b/lib/ParamedicLogCollector.js
@@ -19,10 +19,11 @@
     under the License.
 */
 
-const shelljs = require('shelljs');
-const fs = require('fs');
-const path = require('path');
-const { logger, exec, utilities } = require('./utils');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+
+const { logger, spawnAsync, utilities } = require('./utils');
 
 class ParamedicLogCollector {
     constructor (platform, appPath, outputDir, targetObj) {
@@ -32,77 +33,75 @@ class ParamedicLogCollector {
         this.targetObj = targetObj;
     }
 
-    logIOS () {
-        if (!this.targetObj) {
-            logger.warn('It looks like there is no target to get logs from.');
+    /**
+     * If the simulator id and the simulator's system.log exists, it will be 
copied
+     * over to the provided output path.
+     */
+    #logIOS () {
+        if (!this.targetObj.simId) {
+            logger.info('[paramedic] Missing Simulator ID from target to 
locate logs.');
             return;
         }
 
-        const simId = this.targetObj.simId;
+        const homedir = os.homedir();
+        const systemLogs = path.join(homedir, 'Library', 'Logs', 
'CoreSimulator', this.targetObj.simId, 'system.log');
 
-        if (simId) {
-            const homedir = require('os').homedir();
-            // Now we can print out the log file
-            const logPath = path.join(homedir, 'Library', 'Logs', 
'CoreSimulator', simId, 'system.log');
-            const logCommand = 'cat ' + logPath;
-            this.generateLogs(logCommand);
+        if (fs.existsSync(systemLogs)) {
+            const outputFilePath = this.#getLogFileName();
+            fs.cpSync(systemLogs, outputFilePath);
         } else {
-            logger.error('Failed to find the ID of the simulator');
-        }
-    }
-
-    logAndroid () {
-        if (!this.targetObj) {
-            logger.warn('It looks like there is no target to get logs from.');
-            return;
-        }
-
-        const logCommand = 'adb -s ' + this.targetObj.target + ' logcat -d -v 
time';
-        const numDevices = utilities.countAndroidDevices();
-
-        if (numDevices !== 1) {
-            logger.error('There must be exactly one emulator/device attached');
-            return;
+            logger.info('[paramedic] No logs found for the requested Simulator 
ID.');
         }
-
-        this.generateLogs(logCommand);
     }
 
-    generateLogs (logCommand) {
-        logger.info('Running Command: ' + logCommand);
+    /**
+     * Captures the logs from adb logcat and stores it to the provided output 
path
+     *
+     * @returns {Promise}
+     */
+    async #logAndroid () {
+        const content = await spawnAsync(
+            'adb',
+            ['-s', this.targetObj.target, 'logcat', '-d', '-v', 'time']
+        );
 
-        const logFile = this.getLogFileName();
-        const result = exec(logCommand);
-
-        if (result.code > 0) {
-            logger.error('Failed to run command: ' + logCommand);
-            logger.error('Failure code: ' + result.code);
-            return;
-        }
+        const logFileOutput = this.#getLogFileName();
 
         try {
-            fs.writeFileSync(logFile, result.stdout);
-            logger.info('Logfiles are written to: ' + logFile);
-        } catch (ex) {
-            logger.error('Cannot write the log results to the file. ' + ex);
+            fs.writeFileSync(logFileOutput, content.stdout);
+            logger.info(`[paramedic] Log files written to: ${logFileOutput}`);
+        } catch (err) {
+            logger.error(`[paramedic] Faild to write logs with error: 
${err.message}`);
         }
     }
 
-    getLogFileName () {
+    /**
+     * Returns file path where the log content will be written/copied to.
+     *
+     * @returns {String}
+     */
+    #getLogFileName () {
         return path.join(this.outputDir, this.platform + '_logs.txt');
     }
 
-    collectLogs () {
-        shelljs.config.fatal = false;
-        shelljs.config.silent = false;
+    /**
+     * Collects the logs logs and writes out to output location.
+     *
+     * @returns {Promise}
+     */
+    async collectLogs () {
+        if (!this.targetObj) {
+            logger.warn('[paramedic] There is no target to fetch logs from.');
+            return;
+        }
 
         switch (this.platform) {
         case utilities.ANDROID:
-            this.logAndroid();
+            await this.#logAndroid();
             break;
 
         case utilities.IOS:
-            this.logIOS(this.appPath);
+            this.#logIOS();
             break;
 
         default:
diff --git a/lib/ParamedicTargetChooser.js b/lib/ParamedicTargetChooser.js
index cb031a0..e82a0df 100644
--- a/lib/ParamedicTargetChooser.js
+++ b/lib/ParamedicTargetChooser.js
@@ -30,23 +30,35 @@ class ParamedicTargetChooser {
         this.cli = config.getCli();
     }
 
-    async chooseTarget (emulator, target) {
+    /**
+     * Collects target information by platform id.
+     *
+     * @param {String} target E.g. "iPhone-17-Pro, 26.1"
+     * @returns {Promise<Object>} Target data
+     */
+    async chooseTarget (target) {
         switch (this.platform) {
         case utilities.ANDROID:
-            return this.chooseTargetForAndroid(emulator, target);
+            return this.chooseTargetForAndroid(target);
 
         case utilities.IOS:
-            return this.chooseTargetForIOS(emulator, target);
+            return this.chooseTargetForIOS(target);
 
         default:
         }
     }
 
-    async chooseTargetForAndroid (emulator, target) {
-        logger.info('cordova-paramedic: Choosing Target for Android');
+    /**
+     * Tries to start if emualtor not set and returns the Android emulator ID.
+     *
+     * @param {String} target The desired emulator to use.
+     * @returns {Promise<Object>}
+     */
+    async chooseTargetForAndroid (target) {
+        logger.info('[paramedic] Choosing Target for Android');
 
         if (target) {
-            logger.info('cordova-paramedic: Target defined as: ' + target);
+            logger.info('[paramedic] Target defined as: ' + target);
             return { target };
         }
 
@@ -54,7 +66,7 @@ class ParamedicTargetChooser {
     }
 
     async startAnAndroidEmulator (target) {
-        logger.info('cordova-paramedic: Starting an Android emulator');
+        logger.info('[paramedic] Starting an Android emulator');
 
         const emuPathInNodeModules = path.join(this.appPath, 'node_modules', 
'cordova-android', 'lib', 'emulator.js');
         const emuPathInPlatform = path.join(this.appPath, 'platforms', 
'android', 'cordova', 'lib', 'emulator.js');
@@ -75,7 +87,7 @@ class ParamedicTargetChooser {
                 return tryStart(numberTriesRemaining - 1);
             }
 
-            logger.error('cordova-paramedic: Could not start an Android 
emulator');
+            logger.error('[paramedic] Could not start an Android emulator');
             return null;
         };
 
@@ -89,11 +101,17 @@ class ParamedicTargetChooser {
         return await tryStart(ANDROID_RETRY_TIMES);
     }
 
-    async chooseTargetForIOS (emulator, target) {
-        logger.info('cordova-paramedic: Choosing Target for iOS');
-
-        const simulatorModelId = utilities.getSimulatorModelId(this.cli, 
target);
-        const simulatorData = utilities.getSimulatorData(simulatorModelId);
+    /**
+     * Returns iOS related target data.
+     *
+     * @param {String} target The desired emulator device type and iOS version
+     * @returns {Promise<Object>}
+     */
+    async chooseTargetForIOS (target) {
+        logger.info('[paramedic] Choosing Target for iOS');
+
+        const simulatorModelId = await 
utilities.getSimulatorModelId(this.appPath, this.cli, target);
+        const simulatorData = await 
utilities.getSimulatorData(simulatorModelId);
 
         return {
             target: simulatorModelId,
diff --git a/lib/ParamediciOSPermissions.js b/lib/ParamediciOSPermissions.js
index 4e8c9de..18b7aff 100644
--- a/lib/ParamediciOSPermissions.js
+++ b/lib/ParamediciOSPermissions.js
@@ -21,9 +21,7 @@
 
 const path = require('path');
 const fs = require('fs');
-const shelljs = require('shelljs');
-const util = require('util');
-const { logger, utilities } = require('./utils');
+const { logger, utilities, spawnAsync } = require('./utils');
 
 const TCC_FOLDER_PERMISSION = 0o755;
 
@@ -34,7 +32,12 @@ class ParamediciOSPermissions {
         this.targetObj = targetObj;
     }
 
-    updatePermissions (serviceList) {
+    /**
+     * Add or update service list to grant permissions for testing.
+     *
+     * @param {Array} serviceList List of services that should grant permission
+     */
+    async updatePermissions (serviceList) {
         const simId = this.targetObj.simId;
         logger.info('Sim Id is: ' + simId);
 
@@ -49,34 +52,34 @@ class ParamediciOSPermissions {
             }
 
             logger.info('Copying TCC Db file to ' + tccDirectory);
-            shelljs.cp(this.tccDb, tccDirectory);
+            fs.cpSync(this.tccDb, tccDirectory);
         }
 
-        for (let i = 0; i < serviceList.length; i++) {
-            let command = 
utilities.getSqlite3InsertionCommand(destinationTCCFile, serviceList[i], 
this.appName);
-            logger.info('Running Command: ' + command);
+        for (const service of serviceList) {
+            const app = this.appName;
             // If the service has an entry already, the insert command will 
fail.
             // in this case we'll process with updating existing entry
-            console.log('$ ' + command);
-            const proc = shelljs.exec(command, { silent: true, async: false });
-
-            if (proc.code) {
-                logger.warn('Failed to insert permissions for ' + this.appName 
+ ' into ' + destinationTCCFile +
-                    ' Will try to update existing permissions.');
-
-                // (service, client, client_type, allowed, prompt_count, csreq)
-                command = util.format('sqlite3 %s "update access ' +
-                    'set client_type=0, allowed=1, prompt_count=1, csreq=NULL 
' +
-                    'where service=\'%s\' and client=\'%s\'"', 
destinationTCCFile, serviceList[i], this.appName);
-
-                logger.info('Running Command: ' + command);
-                // Now we really don't care about the result as there is 
nothing we can do with this
-                console.log('$ ' + command);
-                const patchProc = shelljs.exec(command, { silent: true, async: 
false });
-
-                if (patchProc.code) {
-                    logger.warn('Failed to update existing permissions for ' + 
this.appName + ' into ' + destinationTCCFile +
-                    ' Continuing anyway.');
+            const insetProc = await spawnAsync(
+                'sqlite3',
+                [
+                    destinationTCCFile,
+                    `"INSERT INTO access (service, client, client_type, 
allowed, prompt_count, csreq) VALUES('${service}', '${app}', 0, 1, 1, NULL)"`
+                ]
+            );
+
+            if (insetProc.code) {
+                logger.warn(`[paramedic] Failed to insert permissions for 
${app} into ${destinationTCCFile}. Will try to update existing permissions.`);
+
+                const updateProc = await spawnAsync(
+                    'sqlite3',
+                    [
+                        destinationTCCFile,
+                        `"UPDATE access SET client_type=0, allowed=1, 
prompt_count=1, csreq=NULL WHERE service='${service}' AND client='${app}'"`
+                    ]
+                );
+
+                if (updateProc.code) {
+                    logger.warn(`[paramedic] Failed to update existing 
permissions for ${app} into ${destinationTCCFile}. Continuing anyway.`);
                 }
             }
         }
diff --git a/lib/PluginsManager.js b/lib/PluginsManager.js
index 490123f..f0a090f 100644
--- a/lib/PluginsManager.js
+++ b/lib/PluginsManager.js
@@ -19,7 +19,7 @@
 
 const path = require('path');
 const fs = require('fs');
-const { logger, exec, utilities } = require('./utils');
+const { logger, spawnAsync, utilities } = require('./utils');
 const { PluginInfoProvider } = require('cordova-common');
 
 class PluginsManager {
@@ -29,27 +29,41 @@ class PluginsManager {
         this.config = config;
     }
 
-    installPlugins (plugins) {
-        for (let n = 0; n < plugins.length; n++) {
-            this.installSinglePlugin(plugins[n]);
+    /**
+     * Installs list of plugins to the temporary Cordova testing project.
+     *
+     * @param {Array} plugins
+     */
+    async installPlugins (plugins) {
+        for (const plugin of plugins) {
+            await this.installSinglePlugin(plugin);
         }
     }
 
-    installTestsForExistingPlugins () {
+    /**
+     * Loops though the installed plugins and installs tests to the temporary 
Cordova
+     * testing project, if the plugins have.
+     */
+    async installTestsForExistingPlugins () {
         const installedPlugins = new 
PluginInfoProvider().getAllWithinSearchPath(path.join(this.appRoot, 'plugins'));
 
-        installedPlugins.forEach((plugin) => {
-            // there is test plugin available
+        for (const plugin of installedPlugins) {
+            // Install test if it exists
             if (fs.existsSync(path.join(plugin.dir, 'tests', 'plugin.xml'))) {
-                this.installSinglePlugin(path.join(plugin.dir, 'tests'));
+                await this.installSinglePlugin(path.join(plugin.dir, 'tests'));
             }
-        });
+        }
 
         // this will list installed plugins and their versions
-        this.showPluginsVersions();
+        await this.showPluginsVersions();
     }
 
-    installSinglePlugin (plugin) {
+    /**
+     * Installs a single plugin to the temporary Cordova testing project.
+     *
+     * @param {String} plugin
+     */
+    async installSinglePlugin (plugin) {
         let pluginPath = plugin;
         let args = '';
 
@@ -64,19 +78,29 @@ class PluginsManager {
             plugin = path.resolve(this.storedCWD, pluginPath) + args;
         }
 
-        plugin += utilities.PARAMEDIC_COMMON_CLI_ARGS + 
utilities.PARAMEDIC_PLUGIN_ADD_ARGS;
-        logger.normal('cordova-paramedic: installing plugin ' + plugin);
+        const results = await spawnAsync(
+            this.config.getCli(),
+            ['plugin', 'add', plugin, ...utilities.PARAMEDIC_COMMON_ARGS],
+            { cwd: this.appRoot }
+        );
 
-        const plugAddCmd = exec(this.config.getCli() + ' plugin add ' + 
plugin);
-        if (plugAddCmd.code !== 0) {
-            logger.error('Failed to install plugin : ' + plugin);
-            throw new Error('Failed to install plugin : ' + plugin);
+        if (results.code !== 0) {
+            logger.error(`[paramedic] Failed to install plugin: ${plugin}`);
+            throw new Error(`[paramedic] Failed to install plugin: ${plugin}`);
         }
     }
 
-    showPluginsVersions () {
-        logger.normal('cordova-paramedic: versions of installed plugins: ');
-        exec(this.config.getCli() + ' plugins' + 
utilities.PARAMEDIC_COMMON_CLI_ARGS);
+    /**
+     * Fetches and displays list of all installed plugins.
+     */
+    async showPluginsVersions () {
+        const results = await spawnAsync(
+            this.config.getCli(),
+            ['plugins', ...utilities.PARAMEDIC_COMMON_ARGS],
+            { cwd: this.appRoot }
+        );
+
+        logger.normal(results.stdout);
     }
 }
 
diff --git a/lib/paramedic.js b/lib/paramedic.js
index 43d549f..630d99d 100644
--- a/lib/paramedic.js
+++ b/lib/paramedic.js
@@ -17,12 +17,12 @@
     under the License.
 */
 
-const cp = require('child_process');
-const shell = require('shelljs');
 const Server = require('./LocalServer');
 const path = require('path');
 const fs = require('fs');
-const { logger, exec, execPromise, utilities } = require('./utils');
+const { setTimeout: timelimit } = require('node:timers/promises');
+
+const { logger, utilities, spawnAsync } = require('./utils');
 const Reporters = require('./Reporters');
 const ParamedicKill = require('./ParamedicKill');
 const ParamedicLogCollector = require('./ParamedicLogCollector');
@@ -43,80 +43,84 @@ class ParamedicRunner {
 
         this.isBrowser = this.config.getPlatformId() === utilities.BROWSER;
         this.isIos = this.config.getPlatformId() === utilities.IOS;
-
-        exec.setVerboseLevel(config.isVerbose());
     }
 
-    run () {
+    /**
+     * The main runner that:
+     * - Creates, sets up, & prepares the project.
+     * - Runs the project
+     * - Executes the tests
+     *
+     * On a successful case, the test results should be returned.
+     *
+     * An error can be thrown if there was any issues within the
+     * process. Failures in the app uninstall process will not
+     * error out.
+     *
+     * @returns {Promise}
+     */
+    async run () {
         this.checkConfig();
 
-        return Promise.resolve()
-            .then(() => {
-                // create project and prepare (install plugins, setup test 
startpage, install platform, check platform requirements)
-                const paramedicApp = new ParamedicApp(this.config, 
this.storedCWD, this);
-                this.tempFolder = paramedicApp.createTempProject();
-                shell.pushd(this.tempFolder.name);
-                return paramedicApp.prepareProjectToRunTests();
-            })
-            .then(() => {
-                if (this.config.runMainTests()) {
-                    // start server
-                    const noListener = false;
-                    return Server.startServer(this.config.getPorts(), 
noListener);
-                }
-            })
-            .then((server) => {
-                if (this.config.runMainTests()) {
-                    // configure server usage
-                    this.server = server;
+        const paramedicApp = new ParamedicApp(this.config, this.storedCWD, 
this);
 
-                    this.injectReporters();
-                    this.subcribeForEvents();
+        try {
+            // Create a Cordova project
+            this.tempFolder = await paramedicApp.createTempProject();
 
-                    const logUrl = 
this.server.getMedicAddress(this.config.getPlatformId());
-                    this.writeMedicJson(logUrl);
+            // Prepare the project by installing plugins, platforms, seting up 
test startpage, & check platform requirements
+            await paramedicApp.prepareProjectToRunTests();
 
-                    logger.normal('Start building app and running tests at ' + 
(new Date()).toLocaleTimeString());
-                }
-                // run tests
-                return Promise.race([
-                    this.runTests(),
-                    new Promise((resolve, reject) =>
-                        setTimeout(() => reject(
-                            new Error(`[paramedic] Tests failed to complete in 
${this.config.getTimeout()} ms.`)
-                        ), this.config.getTimeout())
-                    )
-                ]);
-            })
-            .catch((error) => {
-                logger.error(error);
-                console.log(error.stack);
-                throw error;
-            })
-            .then((result) => {
-                
logger.warn('---------------------------------------------------------');
-                logger.warn('6. Collect data and clean up');
-                
logger.warn('---------------------------------------------------------');
-                logger.normal('Completed tests at ' + (new 
Date()).toLocaleTimeString());
-
-                // When --justbuild is not set, fetch logs from the device.
-                if (this.config.getAction() !== 'build') {
-                // collect logs and uninstall app
-                    this.collectDeviceLogs();
-                    return this.uninstallApp()
-                        .catch(() => { /* do not fail if uninstall failed */ })
-                        .finally(() => {
-                            this.killEmulatorProcess();
-                        })
-                        .then(() => result);
+            // Start server if the tests are to run
+            if (this.config.runMainTests()) {
+                const noListener = false;
+                this.server = await Server.startServer(this.config.getPorts(), 
noListener);
+
+                this.injectReporters();
+                this.subcribeForEvents();
+
+                const logUrl = 
this.server.getMedicAddress(this.config.getPlatformId());
+                this.writeMedicJson(logUrl);
+
+                logger.normal('[paramedic] Start building app and running 
tests at ' + (new Date()).toLocaleTimeString());
+            }
+
+            const results = await Promise.race([
+                this.runLocalTests(),
+                // If the tests fails to complete in the allowed timelimit, it 
will reject (default 60 minutes)
+                timelimit(this.config.getTimeout())
+                    .then(() => Promise.reject(
+                        new Error(`[paramedic] Tests failed to complete in 
${this.config.getTimeout()} ms.`)
+                    ))
+            ]);
+
+            
logger.warn('---------------------------------------------------------');
+            logger.warn('6. Collect data and clean up');
+            
logger.warn('---------------------------------------------------------');
+            logger.normal('Completed tests at ' + (new 
Date()).toLocaleTimeString());
+
+            // When --justbuild is not set, fetch logs from the device.
+            if (this.config.getAction() !== 'build') {
+            // collect logs and uninstall app
+                await this.collectDeviceLogs();
+
+                try {
+                    await this.uninstallApp();
+                } catch {
+                    // do not fail if uninstall failed
+                } finally {
+                    this.killEmulatorProcess();
                 }
+            }
 
-                // --justbuild does nothing.
-                return result;
-            })
-            .finally(() => {
-                this.cleanUpProject();
-            });
+            return results;
+        } catch (error) {
+            logger.error(error);
+            console.log(error.stack);
+            throw error;
+        } finally {
+            this.cleanUpProject();
+        }
     }
 
     checkConfig () {
@@ -143,18 +147,21 @@ class ParamedicRunner {
             }
         }
 
-        logger.info('cordova-paramedic: Will use the following cli: ' + 
this.config.getCli());
+        logger.info('[paramedic] Will use the following cli: ' + 
this.config.getCli());
     }
 
-    setPermissions () {
+    /**
+     * Setup iOS related Permissions
+     */
+    async setPermissions () {
         const applicationsToGrantPermission = ['kTCCServiceAddressBook'];
         if (this.isIos) {
-            logger.info('cordova-paramedic: Setting required permissions.');
+            logger.info('[paramedic] Setting required permissions.');
             const tccDb = this.config.getTccDb();
             if (tccDb) {
                 const appName = utilities.PARAMEDIC_DEFAULT_APP_NAME;
                 const paramediciOSPermissions = new 
ParamediciOSPermissions(appName, tccDb, this.targetObj);
-                
paramediciOSPermissions.updatePermissions(applicationsToGrantPermission);
+                await 
paramediciOSPermissions.updatePermissions(applicationsToGrantPermission);
             }
         }
     }
@@ -184,23 +191,29 @@ class ParamedicRunner {
         });
 
         this.server.on('deviceInfo', (data) => {
-            logger.normal('cordova-paramedic: Device info: ' + 
JSON.stringify(data));
+            logger.normal('[paramedic] Device info: ' + JSON.stringify(data));
         });
     }
 
     writeMedicJson (logUrl) {
-        logger.normal('cordova-paramedic: writing medic log url to project ' + 
logUrl);
+        logger.normal('[paramedic] writing medic log url to project ' + 
logUrl);
         const medicFilePath = path.join(this.tempFolder.name, 'www', 
'medic.json');
         const medicFileContent = JSON.stringify({ logurl: logUrl });
         fs.writeFileSync(medicFilePath, medicFileContent);
     }
 
-    runLocalTests () {
+    /**
+     * Runs the local tests (Jasmine) and returns the results.
+     * A reject maybe returned for example the tests do not complete in the 
timelimit.
+     *
+     * @returns {Promise}
+     */
+    async runLocalTests () {
+        
logger.warn('---------------------------------------------------------');
+        logger.warn('4. Run (Jasmine) tests...');
         logger.warn('... locally');
         
logger.warn('---------------------------------------------------------');
 
-        let runProcess = null;
-
         // checking for Android platform here because in this case we still 
need to start an emulator
         // will check again a bit lower
         if (!this.config.runMainTests() && this.config.getPlatformId() !== 
utilities.ANDROID) {
@@ -208,122 +221,96 @@ class ParamedicRunner {
             return utilities.TEST_PASSED;
         }
 
-        logger.info('cordova-paramedic: running tests locally');
-
-        return Promise.resolve()
-            .then(() => this.getCommandForStartingTests())
-            .then((command) => {
-                this.setPermissions();
-
-                return Promise.all([
-                    Promise.resolve().then(() => {
-                        logger.normal('cordova-paramedic: running command ' + 
command);
-
-                        if (this.config.getPlatformId() !== utilities.BROWSER) 
{
-                            return execPromise(command);
-                        }
-                        console.log('$ ' + command);
-
-                        // a precaution not to try to kill some other process
-                        runProcess = cp.exec(command, () => {
-                            runProcess = null;
-                        });
-                    }),
-                    Promise.resolve().then(() => {
-                        if (!this.config.runMainTests()) {
-                            logger.normal('Skipping main tests...');
-                            return utilities.TEST_PASSED;
-                        }
-
-                        // skip tests if it was just build
-                        if (this.shouldWaitForTestResult()) {
-                            return new Promise((resolve, reject) => {
-                            // reject if timed out
-                                this.waitForConnection().catch(reject);
-                                // resolve if got results
-                                this.waitForTests().then(resolve);
-                            });
-                        }
-
-                        return utilities.TEST_PASSED; // if we're not waiting 
for a test result, just report tests as passed
-                    })
-                ]);
-            })
-            .then((results) => results[1])
-            .then((result) => {
-                if (runProcess) {
-                    return new Promise((resolve) => {
-                        utilities.killProcess(runProcess.pid, () => {
-                            resolve(result);
-                        });
-                    });
-                }
+        logger.info('[paramedic] running tests locally');
+        await this.setPermissions();
 
-                return result;
-            });
-    }
+        const cmdArgs = await this.getRunLocalTestCommandArgs();
 
-    runTests () {
-        
logger.warn('---------------------------------------------------------');
-        logger.warn('4. Run (Jasmine) tests...');
-        return this.runLocalTests();
+        if (this.config.getAction() === 'build') {
+            await spawnAsync(
+                this.config.getCli(),
+                cmdArgs,
+                { cwd: this.tempFolder.name }
+            );
+
+            // Build only does not trigger tests. Pass will be returned.
+            return utilities.TEST_PASSED;
+        }
+
+        // Main tests are being skipped. Pass will be returned.
+        if (!this.config.runMainTests()) {
+            logger.normal('[paramedic] Skipping main tests...');
+            return utilities.TEST_PASSED;
+        }
+
+        // Waiting for test results for run/emulate commands.
+        if (this.shouldWaitForTestResult()) {
+            return await Promise.race([
+                this.waitForTests(cmdArgs), // resolve on request
+                timelimit(INITIAL_CONNECTION_TIMEOUT).then(() => {
+                    if (!this.server.isDeviceConnected()) {
+                        const ERR_MSG = `[paramedic] The device failed to 
connect to local server in ${INITIAL_CONNECTION_TIMEOUT / 1000} secs`;
+                        return Promise.reject(new Error(ERR_MSG));
+                    }
+                })
+            ]);
+        }
+
+        // Nothing happened so return pass.
+        return utilities.TEST_PASSED;
     }
 
-    waitForTests () {
-        logger.info('cordova-paramedic: waiting for test results');
-        return new Promise((resolve, reject) => {
-            // time out if connection takes too long
-            const ERR_MSG = 'waitForTests: Seems like device not connected to 
local server in ' + INITIAL_CONNECTION_TIMEOUT / 1000 + ' secs';
-            setTimeout(() => {
-                if (!this.server.isDeviceConnected()) {
-                    reject(new Error(ERR_MSG));
-                }
-            }, INITIAL_CONNECTION_TIMEOUT);
+    async waitForTests (cmdArgs) {
+        logger.info('[paramedic] Waiting for test results...');
 
+        const testResults = new Promise((resolve, reject) => {
             this.server.on('jasmineDone', (data) => {
-                logger.info('cordova-paramedic: tests have been completed');
-
-                // Is Test Passed
-                resolve((data.specResults.specFailed === 0));
+                logger.info('[paramedic] Tests has completed.');
+                resolve(data.specResults.specFailed === 0);
             });
-
             this.server.on('disconnect', () => {
-                reject(new Error('Device is disconnected before passing the 
tests'));
+                reject(new Error('[paramedic] Device is disconnected before 
passing the tests'));
             });
         });
-    }
 
-    getCommandForStartingTests () {
-        const cmd = [
+        // This spawns the Cordova run command. It will build, run, and 
trigger the automatic tests.
+        await spawnAsync(
             this.config.getCli(),
+            cmdArgs,
+            { cwd: this.tempFolder.name }
+        );
+
+        return testResults;
+    }
+
+    /**
+     * Creates the run/build command.
+     *
+     * @returns {Array}
+     */
+    async getRunLocalTestCommandArgs () {
+        const args = [
             this.config.getAction(),
-            this.config.getPlatformId()
-        ]
-            .concat(utilities.PARAMEDIC_COMMON_ARGS)
-            .concat([this.config.getArgs()]);
-
-        if (this.isBrowser) {
-            return cmd.join(' ');
-        } else if (this.config.getAction() === 'build') {
-            // The app is to be run as a store app or just build. So no need 
to choose a target.
-            return Promise.resolve(cmd.join(' '));
+            this.config.getPlatformId(),
+            ...this.config.getArgs(),
+            ...utilities.PARAMEDIC_COMMON_ARGS
+        ];
+
+        if (this.isBrowser || this.config.getAction() === 'build') {
+            return args;
         }
 
-        // For now we always trying to run test app on emulator
-        return new ParamedicTargetChooser(this.tempFolder.name, 
this.config).chooseTarget(
-            true, // useEmulator
-            this.config.getTarget() // preferredTarget
-        ).then(targetObj => {
-            this.targetObj = targetObj;
-
-            return cmd
-                .concat(['--target', `"${this.targetObj.target}"`])
-                // CB-11472 In case of iOS provide additional '--emulator' 
flag, otherwise
-                // 'cordova run ios --target' would hang waiting for device 
with name
-                // as specified in 'target' in case if any device is 
physically connected
-                .concat(this.isIos ? ['--emulator'] : [])
-                .join(' ');
-        });
+        const targetChooser = new ParamedicTargetChooser(this.tempFolder.name, 
this.config);
+        this.targetObj = await 
targetChooser.chooseTarget(this.config.getTarget());
+
+        // CB-11472 In case of iOS provide additional '--emulator' flag, 
otherwise
+        // 'cordova run ios --target' would hang waiting for device with name
+        // as specified in 'target' in case if any device is physically 
connected
+        return [
+            ...args,
+            '--target',
+            this.targetObj.target
+        ].concat(this.isIos ? ['--emulator'] : []);
     }
 
     shouldWaitForTestResult () {
@@ -331,45 +318,36 @@ class ParamedicRunner {
         return (action.indexOf('run') === 0) || (action.indexOf('emulate') === 
0);
     }
 
-    waitForConnection () {
-        const ERR_MSG = 'waitForConnection: Seems like device not connected to 
local server in ' + INITIAL_CONNECTION_TIMEOUT / 1000 + ' secs';
-
-        return new Promise((resolve, reject) => {
-            setTimeout(() => {
-                if (!this.server.isDeviceConnected()) {
-                    reject(new Error(ERR_MSG));
-                } else {
-                    resolve();
-                }
-            }, INITIAL_CONNECTION_TIMEOUT);
-        });
-    }
-
+    /**
+     * Removes the temporary project directory if flagged to cleanup after run.
+     */
     cleanUpProject () {
         if (this.config.shouldCleanUpAfterRun()) {
-            logger.info('cordova-paramedic: Deleting the application: ' + 
this.tempFolder.name);
-            shell.popd();
-            shell.rm('-rf', this.tempFolder.name);
+            logger.info('[paramedic] Removing Temporary Project: ' + 
this.tempFolder.name);
+            fs.rmSync(this.tempFolder.name, { force: true, recursive: true });
         }
     }
 
     killEmulatorProcess () {
         if (this.config.shouldCleanUpAfterRun()) {
-            logger.info('cordova-paramedic: Killing the emulator process.');
+            logger.info('[paramedic] Terminating Emulator Process');
             const paramedicKill = new 
ParamedicKill(this.config.getPlatformId());
             paramedicKill.kill();
         }
     }
 
-    collectDeviceLogs () {
-        logger.info('Collecting logs for the devices.');
+    /**
+     * Collects and stores logs when possible
+     */
+    async collectDeviceLogs () {
+        logger.info('[paramedic] Collecting Device Logs');
         const outputDir = this.config.getOutputDir() ? 
this.config.getOutputDir() : this.tempFolder.name;
         const paramedicLogCollector = new 
ParamedicLogCollector(this.config.getPlatformId(), this.tempFolder.name, 
outputDir, this.targetObj);
-        paramedicLogCollector.collectLogs();
+        await paramedicLogCollector.collectLogs();
     }
 
     uninstallApp () {
-        logger.info('Uninstalling the app.');
+        logger.info('[paramedic] Uninstalling App');
         const paramedicAppUninstall = new 
ParamedicAppUninstall(this.tempFolder.name, this.config.getPlatformId());
         return paramedicAppUninstall.uninstallApp(this.targetObj, 
utilities.PARAMEDIC_DEFAULT_APP_NAME);
     }
diff --git a/lib/utils/execWrapper.js b/lib/utils/execWrapper.js
deleted file mode 100644
index 9f29b64..0000000
--- a/lib/utils/execWrapper.js
+++ /dev/null
@@ -1,53 +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 shelljs = require('shelljs');
-let verbose;
-
-function exec (cmd, onFinish, onData) {
-    console.log('$ ' + cmd);
-    if (onFinish instanceof Function || onFinish === null) {
-        const result = shelljs.exec(cmd, { async: true, silent: !verbose }, 
onFinish);
-
-        if (onData instanceof Function) {
-            result.stdout.on('data', onData);
-        }
-    } else {
-        return shelljs.exec(cmd, { silent: !verbose });
-    }
-}
-
-function execPromise (cmd) {
-    return new Promise(function (resolve, reject) {
-        exec(cmd, function (code, output) {
-            if (code) {
-                reject(output);
-            } else {
-                resolve(output);
-            }
-        });
-    });
-}
-
-exec.setVerboseLevel = function (_verbose) {
-    verbose = _verbose;
-};
-
-module.exports.exec = exec;
-module.exports.execPromise = execPromise;
diff --git a/lib/utils/index.js b/lib/utils/index.js
index e4949cc..978ded4 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -18,8 +18,7 @@
 */
 
 module.exports = {
-    exec: require('./execWrapper').exec,
     logger: require('cordova-common').CordovaLogger.get(),
-    execPromise: require('./execWrapper').execPromise,
-    utilities: require('./utilities')
+    utilities: require('./utilities'),
+    ...require('./spawn')
 };
diff --git a/lib/utils/spawn.js b/lib/utils/spawn.js
new file mode 100644
index 0000000..2b0f063
--- /dev/null
+++ b/lib/utils/spawn.js
@@ -0,0 +1,136 @@
+/*
+    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 { spawn } = require('node:child_process');
+const { setTimeout: timelimit } = require('node:timers/promises');
+
+const logger = require('cordova-common').CordovaLogger.get();
+
+/**
+ * Wraps the spawn process inside a promise to make it asynchronous.
+ *
+ * In a successful case, during the on close event, an object will be
+ * returned as long as the code equals 0.
+ * The object will contain the process's "stdout", "stderr", & "code".
+ *
+ * If the code is anything but zero, a error object is returned as
+ * a rejection. The "stdout", "stderr", & "code" will be appeneded.
+ *
+ * An error is also returned in cases where the process is errored
+ * but not closed.
+ *
+ * The "options" argument is used for configuring the spawn process.
+ * It also takes in the "verbose" and "timeout" settings. These
+ * settings will be extracted from options before passed to the spawn
+ * process.
+ *
+ * "verbose" - Determins the level of logging.
+ * "timeout" - Determins if the spawn should timeout after Xms.
+ *   Value should be set in milliseconds.
+ *
+ * @param {String} cmd command process (e.g. cordova, adb, xcrun, etc...)
+ * @param {Array} args command arguments
+ * @param {Object} options Spawn process object, verbose, and timeout settings
+ * @returns {Promise<Object|Error>}
+ */
+async function spawnAsync (cmd, args = [], options = {}) {
+    // Seperate non-spawn and spawn options.
+    const { verbose = false, timeout = false, ...spawnOptions } = options;
+
+    // Tracking start and stop time to attach with the last log message how 
long
+    // a given process took in milliseconds.
+    const timeStart = new Date();
+
+    if (verbose) {
+        logger.info(`[paramedic] Running command: ${cmd} ${args.join(' ')}`);
+    }
+
+    // Stores the spawn process that can be killed by the timeout limit if set 
and reached.
+    let proc = null;
+    // Contians a collection of promises that will be pushed to the races.
+    const promises = [];
+
+    promises.push(
+        // The main promise that wraps the spawn.
+        new Promise((resolve, reject) => {
+            proc = spawn(cmd, args, { stdio: 'pipe', ...spawnOptions });
+
+            let stdout = '';
+            let stderr = '';
+
+            if (proc.stdout) {
+                proc.stdout.on('data', (data) => {
+                    stdout += data.toString();
+                });
+            }
+
+            if (proc.stderr) {
+                proc.stderr.on('data', (data) => {
+                    stderr += data.toString();
+                });
+            }
+
+            proc.on('error', (err) => {
+                reject(err);
+            });
+
+            proc.on('close', (code) => {
+                // This is the time difference in milliseconds of how long the 
process took.
+                const timeDiff = new Date() - timeStart;
+
+                if (code === 0) {
+                    if (verbose) {
+                        logger.info(`[paramedic] Finished running command: 
"${cmd} ${args.join(' ')}" in ${timeDiff}ms.`);
+                    }
+                    // Collect the output & code to return back.
+                    resolve({ stdout, stderr, code });
+                } else {
+                    // If the code was not 0, we will reject the promise.
+                    // As a rejection takes in an "Error" object, we will 
append the stdout, stderr, and code
+                    // so it will be availble as well. This is an extension 
and not normal pattern.
+                    const error = new Error(
+                        `[paramedic] Command failed: "${cmd} ${args.join(' 
')}" in ${timeDiff}ms.\nExit Code: ${code} & Message: \n${stderr}`
+                    );
+                    error.stdout = stdout;
+                    error.stderr = stderr;
+                    error.code = code;
+                    reject(error);
+                }
+            });
+        })
+    );
+
+    // When timout is set correctly, we will push to the promises array a 
timeout promise.
+    // If this timeout finishes before the spawn process finishes, we will 
kill spawn process,
+    // null it out, and return a rejection that the command timed out.
+    if (typeof timeout === 'number' && timeout > 0) {
+        promises.push(
+            timelimit(timeout).then(() => {
+                proc?.kill();
+                proc = null;
+                Promise.reject(new Error(`[paramedic] Command timed out after 
${timeout}ms`));
+            })
+        );
+    }
+
+    // Off to the races
+    return Promise.race([...promises]);
+}
+
+module.exports = { spawnAsync };
diff --git a/lib/utils/utilities.js b/lib/utils/utilities.js
index 524dd9c..4959129 100644
--- a/lib/utils/utilities.js
+++ b/lib/utils/utilities.js
@@ -19,17 +19,14 @@
     under the License.
 */
 
-const fs = require('fs');
-const os = require('os');
-const util = require('util');
-const path = require('path');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+
 const logger = require('cordova-common').CordovaLogger.get();
 const kill = require('tree-kill');
-const exec = require('./execWrapper').exec;
-const execa = require('execa');
+const { spawnAsync } = require('./spawn');
 
-const HEADING_LINE_PATTERN = /List of devices/m;
-const DEVICE_ROW_PATTERN = /(emulator|device|host)/m;
 const KILL_SIGNAL = 'SIGINT';
 
 let simulatorCollection = null;
@@ -39,23 +36,6 @@ function isWindows () {
     return /^win/.test(os.platform());
 }
 
-function countAndroidDevices () {
-    const listCommand = 'adb devices';
-
-    logger.info('running:');
-    logger.info('    ' + listCommand);
-
-    let numDevices = 0;
-    const result = exec(listCommand);
-    result.stdout.split('\n').forEach(function (line) {
-        if (!HEADING_LINE_PATTERN.test(line) && DEVICE_ROW_PATTERN.test(line)) 
{
-            numDevices += 1;
-        }
-    });
-
-    return numDevices;
-}
-
 function secToMin (seconds) {
     return Math.ceil(seconds / 60);
 }
@@ -64,7 +44,7 @@ function getSimulatorsFolder () {
     return path.join(os.homedir(), 'Library', 'Developer', 'CoreSimulator', 
'Devices');
 }
 
-function getSimulatorModelId (cli, target) {
+async function getSimulatorModelId (appPath, cli, target) {
     target = new RegExp(target || '^iPhone');
 
     const args = [
@@ -74,13 +54,13 @@ function getSimulatorModelId (cli, target) {
         '--emulator'
     ].concat(module.exports.PARAMEDIC_COMMON_ARGS);
 
-    // Fetches all known simulators/emulators.
-    logger.info('running:');
-    logger.info(`    ${cli} ${args.join(' ')}`);
+    const result = await spawnAsync(
+        cli,
+        args,
+        { cwd: appPath }
+    );
 
-    const result = execa.sync(cli, args);
-
-    if (result.exitCode > 0) {
+    if (result.code > 0) {
         logger.error('Failed to find simulator we deployed to');
         return;
     }
@@ -101,22 +81,24 @@ function getSimulatorModelId (cli, target) {
         .trim();
 }
 
-function getSimulatorCollection () {
-    if (simulatorCollection) return simulatorCollection;
-
-    // Next, figure out the ID of the simulator we found
-    const cmd = '(xcrun xctrace list devices || instruments -s devices) 2>&1 | 
grep ^iPhone';
-    logger.info('running:');
-    logger.info('    ' + cmd);
+/**
+ * Attempts to fetch and return an array of iPhone simulators from xcrun 
xctrace.
+ *
+ * @returns {Array|Boolean}
+ */
+async function getSimulatorCollection () {
+    if (simulatorCollection) {
+        return simulatorCollection;
+    }
 
-    const cmdResult = exec(cmd);
+    const devices = await spawnAsync('xcrun', ['xctrace', 'list', 'devices']);
 
-    if (cmdResult.code > 0) {
-        logger.error('Failed to get the list of simulators');
+    if (devices.code !== 0) {
+        logger.error('[paramedic] Failed to fetch simulator list.');
         return false;
     }
 
-    simulatorCollection = cmdResult.stdout.split('\n');
+    simulatorCollection = devices.stdout.split('\n').filter(line => 
line.startsWith('iPhone'));
     return simulatorCollection;
 }
 
@@ -142,7 +124,7 @@ function filterForSimulatorIds (simulatorData, 
simulatorCollection) {
         }, []);
 }
 
-function getSimulatorData (findSimResult) {
+async function getSimulatorData (findSimResult) {
     if (simulatorDataCollection[findSimResult]) return 
simulatorDataCollection[findSimResult];
 
     // Format of the output is "iPhone-6s-Plus, 9.1"
@@ -156,7 +138,7 @@ function getSimulatorData (findSimResult) {
     };
 
     // Fetch the environment's installed simulator collection data
-    const simulators = getSimulatorCollection();
+    const simulators = await getSimulatorCollection();
 
     // Try to find the simulator ids from the simulator collection
     const simulatorIds = filterForSimulatorIds(simulatorData, simulators);
@@ -199,12 +181,6 @@ function mkdirSync (path) {
     }
 }
 
-function getSqlite3InsertionCommand (destinationTCCFile, service, appName) {
-    return util.format('sqlite3 %s "insert into access' +
-        '(service, client, client_type, allowed, prompt_count, csreq) 
values(\'%s\', \'%s\', ' +
-        '0,1,1,NULL)"', destinationTCCFile, service, appName);
-}
-
 function contains (collection, item) {
     return collection.indexOf(item) !== (-1);
 }
@@ -244,11 +220,8 @@ module.exports = {
     IOS: 'ios',
     BROWSER: 'browser',
     PARAMEDIC_DEFAULT_APP_NAME: 'io.cordova.hellocordova',
-    PARAMEDIC_COMMON_CLI_ARGS: ' --no-telemetry --no-update-notifier',
     PARAMEDIC_COMMON_ARGS: ['--no-telemetry', '--no-update-notifier'],
-    PARAMEDIC_PLUGIN_ADD_ARGS: '',
-    PARAMEDIC_PLATFORM_ADD_ARGS: '',
-    DEFAULT_ENCODING: 'utf-8',
+    DEFAULT_ENCODING: 'utf8',
     DEFAULT_LOG_TIME: 15,
     DEFAULT_LOG_TIME_ADDITIONAL: 2,
 
@@ -257,10 +230,8 @@ module.exports = {
 
     secToMin,
     isWindows,
-    countAndroidDevices,
     getSimulatorsFolder,
     doesFileExist,
-    getSqlite3InsertionCommand,
     getSimulatorModelId,
     getSimulatorData,
     contains,
diff --git a/package-lock.json b/package-lock.json
index a43fa57..dc14a95 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,11 +10,9 @@
       "license": "Apache-2.0",
       "dependencies": {
         "cordova-common": "^6.0.0",
-        "execa": "^5.1.1",
         "jasmine-reporters": "^2.5.2",
         "jasmine-spec-reporter": "^7.0.0",
         "minimist": "^1.2.8",
-        "shelljs": "^0.10.0",
         "tcp-port-used": "^1.0.2",
         "tmp": "^0.2.5",
         "tree-kill": "^1.2.2",
@@ -771,6 +769,7 @@
       "version": "7.0.6",
       "resolved": 
"https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz";,
       "integrity": 
"sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "path-key": "^3.1.0",
@@ -1499,29 +1498,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/execa": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz";,
-      "integrity": 
"sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
-      "license": "MIT",
-      "dependencies": {
-        "cross-spawn": "^7.0.3",
-        "get-stream": "^6.0.0",
-        "human-signals": "^2.1.0",
-        "is-stream": "^2.0.0",
-        "merge-stream": "^2.0.0",
-        "npm-run-path": "^4.0.1",
-        "onetime": "^5.1.2",
-        "signal-exit": "^3.0.3",
-        "strip-final-newline": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sindresorhus/execa?sponsor=1";
-      }
-    },
     "node_modules/extsprintf": {
       "version": "1.4.1",
       "resolved": 
"https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz";,
@@ -1764,18 +1740,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/get-stream": {
-      "version": "6.0.1",
-      "resolved": 
"https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz";,
-      "integrity": 
"sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus";
-      }
-    },
     "node_modules/get-symbol-description": {
       "version": "1.1.0",
       "resolved": 
"https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz";,
@@ -1971,15 +1935,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/human-signals": {
-      "version": "2.1.0",
-      "resolved": 
"https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz";,
-      "integrity": 
"sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
-      "license": "Apache-2.0",
-      "engines": {
-        "node": ">=10.17.0"
-      }
-    },
     "node_modules/ignore": {
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz";,
@@ -2333,18 +2288,6 @@
         "url": "https://github.com/sponsors/ljharb";
       }
     },
-    "node_modules/is-stream": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz";,
-      "integrity": 
"sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus";
-      }
-    },
     "node_modules/is-string": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz";,
@@ -2473,6 +2416,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz";,
       "integrity": 
"sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/jasmine-reporters": {
@@ -2604,12 +2548,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/merge-stream": {
-      "version": "2.0.0",
-      "resolved": 
"https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz";,
-      "integrity": 
"sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
-      "license": "MIT"
-    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz";,
@@ -2632,15 +2570,6 @@
         "node": ">=8.6"
       }
     },
-    "node_modules/mimic-fn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz";,
-      "integrity": 
"sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz";,
@@ -2689,18 +2618,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/npm-run-path": {
-      "version": "4.0.1",
-      "resolved": 
"https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz";,
-      "integrity": 
"sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
-      "license": "MIT",
-      "dependencies": {
-        "path-key": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/object-inspect": {
       "version": "1.13.4",
       "resolved": 
"https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz";,
@@ -2804,21 +2721,6 @@
       "integrity": 
"sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==",
       "license": "ISC"
     },
-    "node_modules/onetime": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz";,
-      "integrity": 
"sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
-      "license": "MIT",
-      "dependencies": {
-        "mimic-fn": "^2.1.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus";
-      }
-    },
     "node_modules/optionator": {
       "version": "0.9.4",
       "resolved": 
"https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz";,
@@ -2914,6 +2816,7 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz";,
       "integrity": 
"sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -3244,6 +3147,7 @@
       "version": "2.0.0",
       "resolved": 
"https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz";,
       "integrity": 
"sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "shebang-regex": "^3.0.0"
@@ -3256,24 +3160,12 @@
       "version": "3.0.0",
       "resolved": 
"https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz";,
       "integrity": 
"sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
       }
     },
-    "node_modules/shelljs": {
-      "version": "0.10.0",
-      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.10.0.tgz";,
-      "integrity": 
"sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "execa": "^5.1.1",
-        "fast-glob": "^3.3.2"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
     "node_modules/side-channel": {
       "version": "1.1.0",
       "resolved": 
"https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz";,
@@ -3350,12 +3242,6 @@
         "url": "https://github.com/sponsors/ljharb";
       }
     },
-    "node_modules/signal-exit": {
-      "version": "3.0.7",
-      "resolved": 
"https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz";,
-      "integrity": 
"sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
-      "license": "ISC"
-    },
     "node_modules/stop-iteration-iterator": {
       "version": "1.1.0",
       "resolved": 
"https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz";,
@@ -3439,15 +3325,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/strip-final-newline": {
-      "version": "2.0.0",
-      "resolved": 
"https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz";,
-      "integrity": 
"sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/strip-json-comments": {
       "version": "3.1.1",
       "resolved": 
"https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz";,
@@ -3752,6 +3629,7 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz";,
       "integrity": 
"sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "isexe": "^2.0.0"
diff --git a/package.json b/package.json
index ced1201..73b5773 100644
--- a/package.json
+++ b/package.json
@@ -31,11 +31,9 @@
   "author": "Apache Software Foundation",
   "dependencies": {
     "cordova-common": "^6.0.0",
-    "execa": "^5.1.1",
     "jasmine-reporters": "^2.5.2",
     "jasmine-spec-reporter": "^7.0.0",
     "minimist": "^1.2.8",
-    "shelljs": "^0.10.0",
     "tcp-port-used": "^1.0.2",
     "tmp": "^0.2.5",
     "tree-kill": "^1.2.2",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to