src/cordova/restore-util.js
function installPluginsFromConfigXML (args) {
    events.emit('verbose', 'Checking for saved plugins that haven\'t been added to the project');

    const projectRoot = cordova_util.getProjectRoot();
    const pluginsRoot = path.join(projectRoot, 'plugins');
    const pkgJsonPath = path.join(projectRoot, 'package.json');
    const confXmlPath = cordova_util.projectConfig(projectRoot);

    let pkgJson = {};
    let indent = '  ';

    if (fs.existsSync(pkgJsonPath)) {
        const fileData = fs.readFileSync(pkgJsonPath, 'utf8');
        indent = detectIndent(fileData).indent;
        pkgJson = JSON.parse(fileData);
    }

    pkgJson.devDependencies = pkgJson.devDependencies || {};
    pkgJson.cordova = pkgJson.cordova || {};
    pkgJson.cordova.plugins = pkgJson.cordova.plugins || {};

    const pkgPluginIDs = Object.keys(pkgJson.cordova.plugins);
    const pkgSpecs = Object.assign({}, pkgJson.dependencies, pkgJson.devDependencies);

    // Check for plugins listed in config.xml
    const cfg = new ConfigParser(confXmlPath);
    const cfgPluginIDs = cfg.getPluginIdList();

    cfgPluginIDs.forEach(plID => {
        // If package.json includes the plugin, we use that config
        // Otherwise, we need to add the plugin to package.json
        if (!pkgPluginIDs.includes(plID)) {
            events.emit('info', `Plugin '${plID}' found in config.xml... Migrating it to package.json`);

            const cfgPlugin = cfg.getPlugin(plID);

            // If config.xml has a spec for the plugin and package.json has not,
            // add the spec to devDependencies of package.json
            if (cfgPlugin.spec && !(plID in pkgSpecs)) {
                pkgJson.devDependencies[plID] = cfgPlugin.spec;
            }

            pkgJson.cordova.plugins[plID] = Object.assign({}, cfgPlugin.variables);
        }
    });

    // Now that plugins have been updated, re-fetch them from package.json
    const pluginIDs = Object.keys(pkgJson.cordova.plugins);

    if (pluginIDs.length !== pkgPluginIDs.length) {
        // We've modified package.json and need to save it
        fs.outputJsonSync(pkgJsonPath, pkgJson, {
            indent: indent,
            encoding: 'utf8'
        });
    }

    const specs = Object.assign({}, pkgJson.dependencies, pkgJson.devDependencies);

    const plugins = pluginIDs.map(plID => ({
        name: plID,
        spec: specs[plID],
        variables: pkgJson.cordova.plugins[plID] || {}
    }));

    let pluginName = '';

    // CB-9560 : Run `plugin add` serially, one plugin after another
    // We need to wait for the plugin and its dependencies to be installed
    // before installing the next root plugin otherwise we can have common
    // plugin dependencies installed twice which throws a nasty error.
    return promiseutil.Q_chainmap_graceful(plugins, function (pluginConfig) {
        pluginName = pluginConfig.name;

        const pluginPath = path.join(pluginsRoot, pluginName);
        if (fs.existsSync(pluginPath)) {
            // Plugin already exists
            return Promise.resolve();
        }

        events.emit('log', `Discovered saved plugin "${pluginName}". Adding it to the project`);

        // Install from given URL if defined or using a plugin id. If spec isn't a valid version or version range,
        // assume it is the location to install from.
        // CB-10761 If plugin spec is not specified, use plugin name
        var installFrom = pluginConfig.spec || pluginName;
        if (pluginConfig.spec && semver.validRange(pluginConfig.spec, true)) {
            installFrom = pluginName + '@' + pluginConfig.spec;
        }

        // Add feature preferences as CLI variables if have any
        var options = {
            cli_variables: pluginConfig.variables,
            searchpath: args.searchpath,
            save: args.save || false
        };

        const plugin = require('./plugin');
        return plugin('add', installFrom, options);
    }, function (error) {
        // CB-10921 emit a warning in case of error
        var msg = 'Failed to restore plugin "' + pluginName + '" from config.xml. ' +
            'You might need to try adding it again. Error: ' + error;
        process.exitCode = 1;
        events.emit('warn', msg);
    });
}
function installPluginsFromConfigXML (args) {
    events.emit('verbose', 'Checking for saved plugins that haven\'t been added to the project');

    const projectRoot = cordova_util.getProjectRoot();
    const pluginsRoot = path.join(projectRoot, 'plugins');
    const pkgJsonPath = path.join(projectRoot, 'package.json');
    const confXmlPath = cordova_util.projectConfig(projectRoot);

    let pkgJson = {};
    let indent = '  ';

    if (fs.existsSync(pkgJsonPath)) {
        const fileData = fs.readFileSync(pkgJsonPath, 'utf8');
        indent = detectIndent(fileData).indent;
        pkgJson = JSON.parse(fileData);
    }

    pkgJson.devDependencies = pkgJson.devDependencies || {};
    pkgJson.cordova = pkgJson.cordova || {};
    pkgJson.cordova.plugins = pkgJson.cordova.plugins || {};

    const pkgPluginIDs = Object.keys(pkgJson.cordova.plugins);
    const pkgSpecs = Object.assign({}, pkgJson.dependencies, pkgJson.devDependencies);

    // Check for plugins listed in config.xml
    const cfg = new ConfigParser(confXmlPath);
    const cfgPluginIDs = cfg.getPluginIdList();

    cfgPluginIDs.forEach(plID => {
        // If package.json includes the plugin, we use that config
        // Otherwise, we need to add the plugin to package.json
        if (!pkgPluginIDs.includes(plID)) {
            events.emit('info', `Plugin '${plID}' found in config.xml... Migrating it to package.json`);

            const cfgPlugin = cfg.getPlugin(plID);

            // If config.xml has a spec for the plugin and package.json has not,
            // add the spec to devDependencies of package.json
            if (cfgPlugin.spec && !(plID in pkgSpecs)) {
                pkgJson.devDependencies[plID] = cfgPlugin.spec;
            }

            pkgJson.cordova.plugins[plID] = Object.assign({}, cfgPlugin.variables);
        }
    });

    // Now that plugins have been updated, re-fetch them from package.json
    const pluginIDs = Object.keys(pkgJson.cordova.plugins);

    if (pluginIDs.length !== pkgPluginIDs.length) {
        // We've modified package.json and need to save it
        fs.outputJsonSync(pkgJsonPath, pkgJson, {
            indent: indent,
            encoding: 'utf8'
        });
    }

    const specs = Object.assign({}, pkgJson.dependencies, pkgJson.devDependencies);

    const plugins = pluginIDs.map(plID => ({
        name: plID,
        spec: specs[plID],
        variables: pkgJson.cordova.plugins[plID] || {}
    }));

    let pluginName = '';

    // CB-9560 : Run `plugin add` serially, one plugin after another
    // We need to wait for the plugin and its dependencies to be installed
    // before installing the next root plugin otherwise we can have common
    // plugin dependencies installed twice which throws a nasty error.
    return promiseutil.Q_chainmap_graceful(plugins, function (pluginConfig) {
        pluginName = pluginConfig.name;

        const pluginPath = path.join(pluginsRoot, pluginName);
        if (fs.existsSync(pluginPath)) {
            // Plugin already exists
            return Promise.resolve();
        }

        events.emit('log', `Discovered saved plugin "${pluginName}". Adding it to the project`);

        // Install from given URL if defined or using a plugin id. If spec isn't a valid version or version range,
        // assume it is the location to install from.
        // CB-10761 If plugin spec is not specified, use plugin name
        var installFrom = pluginConfig.spec || pluginName;
        if (pluginConfig.spec && semver.validRange(pluginConfig.spec, true)) {
            installFrom = pluginName + '@' + pluginConfig.spec;
        }

        // Add feature preferences as CLI variables if have any
        var options = {
            cli_variables: pluginConfig.variables,
            searchpath: args.searchpath,
            save: args.save || false
        };

        const plugin = require('./plugin');
        return plugin('add', installFrom, options);
    }, function (error) {
        // CB-10921 emit a warning in case of error
        var msg = 'Failed to restore plugin "' + pluginName + '" from config.xml. ' +
            'You might need to try adding it again. Error: ' + error;
        process.exitCode = 1;
        events.emit('warn', msg);
    });
}
Returns a promise.