src/cordova/restore-util.js
function installPlatformsFromConfigXML (platforms, opts) {
    events.emit('verbose', 'Checking config.xml and package.json for saved platforms that haven\'t been added to the project');

    var projectHome = cordova_util.cdProjectRoot();
    var configPath = cordova_util.projectConfig(projectHome);
    var cfg = new ConfigParser(configPath);
    var engines = cfg.getEngines();
    var pkgJsonPath = path.join(projectHome, 'package.json');
    var pkgJson;
    var pkgJsonPlatforms;
    var comboArray = [];
    var configPlatforms = [];
    var modifiedPkgJson = false;
    var mergedPlatformSpecs = {};
    var key;
    var installAllPlatforms = !platforms || platforms.length === 0;
    var file;
    var indent;

    // Check if path exists and require pkgJsonPath.
    if (fs.existsSync(pkgJsonPath)) {
        pkgJson = require(pkgJsonPath);
        file = fs.readFileSync(pkgJsonPath, 'utf8');
        indent = detectIndent(file).indent || '  ';
    }

    if (pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.platforms !== undefined) {
        pkgJsonPlatforms = pkgJson.cordova.platforms;
    }

    if (cfg !== undefined) {

        if (pkgJsonPlatforms !== undefined) {
            // Combining arrays and checking duplicates.
            comboArray = pkgJsonPlatforms.slice();
        }

        engines = cfg.getEngines(projectHome);

        // TODO: CB-12592: Eventually refactor out to pacakge manager module.
        // If package.json doesn't exist, auto-create one.
        if (engines.length > 0 && pkgJson === undefined) {
            pkgJson = {};
            if (cfg.packageName()) {
                pkgJson.name = cfg.packageName().toLowerCase();
            }
            if (cfg.version()) {
                pkgJson.version = cfg.version();
            }
            if (cfg.name()) {
                pkgJson.displayName = cfg.name();
            }
            fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, indent), 'utf8');
        }

        configPlatforms = engines.map(function (Engine) {
            var configPlatName = Engine.name;
            // Add specs from config into mergedPlatformSpecs.
            if (mergedPlatformSpecs[configPlatName] === undefined && Engine.spec) {
                mergedPlatformSpecs[configPlatName] = Engine.spec;
            }
            return configPlatName;
        });
        configPlatforms.forEach(function (item) {
            if (comboArray.indexOf(item) < 0) {
                comboArray.push(item);
            }
        });
        // ComboArray should have all platforms from config.xml & package.json.
        // Remove duplicates in comboArray & sort.
        var uniq = comboArray.reduce(function (a, b) {
            if (a.indexOf(b) < 0) a.push(b);
            return a;
        }, []);
        comboArray = uniq;

        // No platforms to restore from either config.xml or package.json.
        if (comboArray.length <= 0) {
            return Promise.resolve('No platforms found in config.xml or package.json. Nothing to restore');
        }

        // If no package.json, don't continue.
        if (pkgJson !== undefined) {
            // If config.xml & pkgJson exist and the cordova key is undefined, create a cordova key.
            if (pkgJson.cordova === undefined) {
                pkgJson.cordova = {};
            }
            // If there is no platforms array, create an empty one.
            if (pkgJson.cordova.platforms === undefined) {
                pkgJson.cordova.platforms = [];
            }
            // If comboArray has the same platforms as pkg.json, no modification to pkg.json.
            if (comboArray.toString() === pkgJson.cordova.platforms.toString()) {
                events.emit('verbose', 'Config.xml and package.json platforms are the same. No pkg.json modification.');
            } else {
                // Modify pkg.json to include the elements.
                // From the comboArray array so that the arrays are identical.
                events.emit('verbose', 'Config.xml and package.json platforms are different. Updating package.json with most current list of platforms.');
                modifiedPkgJson = true;
            }

            events.emit('verbose', 'Package.json and config.xml platforms are different. Updating config.xml with most current list of platforms.');
            comboArray.forEach(function (item) {
                var prefixItem = ('cordova-' + item);

                // Modify package.json if any of these cases are true:
                if ((pkgJson.dependencies === undefined && Object.keys(mergedPlatformSpecs).length) ||
                (pkgJson.dependencies && mergedPlatformSpecs && pkgJson.dependencies[item] === undefined && mergedPlatformSpecs[item]) ||
                (pkgJson.dependencies && mergedPlatformSpecs && pkgJson.dependencies[prefixItem] === undefined && mergedPlatformSpecs[prefixItem])) {
                    modifiedPkgJson = true;
                }

                // Get the cordova- prefixed spec from package.json and add it to mergedPluginSpecs.
                if (pkgJson.dependencies && pkgJson.dependencies[prefixItem]) {
                    if (mergedPlatformSpecs[prefixItem] !== pkgJson.dependencies[prefixItem]) {
                        modifiedPkgJson = true;
                    }
                    mergedPlatformSpecs[item] = pkgJson.dependencies[prefixItem];
                }

                // Get the spec from package.json and add it to mergedPluginSpecs.
                if (pkgJson.dependencies && pkgJson.dependencies[item] && pkgJson.dependencies[prefixItem] === undefined) {
                    if (mergedPlatformSpecs[item] !== pkgJson.dependencies[item]) {
                        modifiedPkgJson = true;
                    }
                    mergedPlatformSpecs[item] = pkgJson.dependencies[item];
                }
            });
        }

        // Write and update pkg.json if it has been modified.
        if (modifiedPkgJson === true) {
            pkgJson.cordova.platforms = comboArray;
            if (pkgJson.dependencies === undefined) {
                pkgJson.dependencies = {};
            }
            // Check if key is part of cordova alias list.
            // Add prefix if it is.
            for (key in mergedPlatformSpecs) {
                var prefixKey = key;
                if (key in platformsList) {
                    prefixKey = 'cordova-' + key;
                }
                pkgJson.dependencies[prefixKey] = mergedPlatformSpecs[key];
            }
            fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, indent), 'utf8');
        }
        if (!comboArray || !comboArray.length) {
            return Promise.resolve('No platforms found in config.xml and/or package.json that haven\'t been added to the project');
        }
    }
    // Run `platform add` for all the platforms separately
    // so that failure on one does not affect the other.

    // CB-9278 : Run `platform add` serially, one platform after another
    // Otherwise, we get a bug where the following line: https://github.com/apache/cordova-lib/blob/0b0dee5e403c2c6d4e7262b963babb9f532e7d27/cordova-lib/src/util/npm-helper.js#L39
    // gets executed simultaneously by each platform and leads to an exception being thrown

    return promiseutil.Q_chainmap_graceful(comboArray, function (target) {
        var cwd = process.cwd();
        var platformsFolderPath = path.join(cwd, 'platforms');
        var platformsInstalled = path.join(platformsFolderPath, target);
        if (target) {
            var platformName = target;
            // Add the spec to the target
            if (mergedPlatformSpecs[target]) {
                target = target + '@' + mergedPlatformSpecs[target];
            }
            // If the platform is already installed, no need to re-install it.
            if (!fs.existsSync(platformsInstalled) && (installAllPlatforms || platforms.indexOf(platformName) > -1)) {
                events.emit('log', 'Discovered platform "' + target + '" in config.xml or package.json. Adding it to the project');
                return cordovaPlatform('add', target, opts);
            }
        }
        return Promise.resolve();
    }, function (err) {
        events.emit('warn', err);
    });
}
function installPlatformsFromConfigXML (platforms, opts) {
    events.emit('verbose', 'Checking config.xml and package.json for saved platforms that haven\'t been added to the project');

    var projectHome = cordova_util.cdProjectRoot();
    var configPath = cordova_util.projectConfig(projectHome);
    var cfg = new ConfigParser(configPath);
    var engines = cfg.getEngines();
    var pkgJsonPath = path.join(projectHome, 'package.json');
    var pkgJson;
    var pkgJsonPlatforms;
    var comboArray = [];
    var configPlatforms = [];
    var modifiedPkgJson = false;
    var mergedPlatformSpecs = {};
    var key;
    var installAllPlatforms = !platforms || platforms.length === 0;
    var file;
    var indent;

    // Check if path exists and require pkgJsonPath.
    if (fs.existsSync(pkgJsonPath)) {
        pkgJson = require(pkgJsonPath);
        file = fs.readFileSync(pkgJsonPath, 'utf8');
        indent = detectIndent(file).indent || '  ';
    }

    if (pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.platforms !== undefined) {
        pkgJsonPlatforms = pkgJson.cordova.platforms;
    }

    if (cfg !== undefined) {

        if (pkgJsonPlatforms !== undefined) {
            // Combining arrays and checking duplicates.
            comboArray = pkgJsonPlatforms.slice();
        }

        engines = cfg.getEngines(projectHome);

        // TODO: CB-12592: Eventually refactor out to pacakge manager module.
        // If package.json doesn't exist, auto-create one.
        if (engines.length > 0 && pkgJson === undefined) {
            pkgJson = {};
            if (cfg.packageName()) {
                pkgJson.name = cfg.packageName().toLowerCase();
            }
            if (cfg.version()) {
                pkgJson.version = cfg.version();
            }
            if (cfg.name()) {
                pkgJson.displayName = cfg.name();
            }
            fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, indent), 'utf8');
        }

        configPlatforms = engines.map(function (Engine) {
            var configPlatName = Engine.name;
            // Add specs from config into mergedPlatformSpecs.
            if (mergedPlatformSpecs[configPlatName] === undefined && Engine.spec) {
                mergedPlatformSpecs[configPlatName] = Engine.spec;
            }
            return configPlatName;
        });
        configPlatforms.forEach(function (item) {
            if (comboArray.indexOf(item) < 0) {
                comboArray.push(item);
            }
        });
        // ComboArray should have all platforms from config.xml & package.json.
        // Remove duplicates in comboArray & sort.
        var uniq = comboArray.reduce(function (a, b) {
            if (a.indexOf(b) < 0) a.push(b);
            return a;
        }, []);
        comboArray = uniq;

        // No platforms to restore from either config.xml or package.json.
        if (comboArray.length <= 0) {
            return Promise.resolve('No platforms found in config.xml or package.json. Nothing to restore');
        }

        // If no package.json, don't continue.
        if (pkgJson !== undefined) {
            // If config.xml & pkgJson exist and the cordova key is undefined, create a cordova key.
            if (pkgJson.cordova === undefined) {
                pkgJson.cordova = {};
            }
            // If there is no platforms array, create an empty one.
            if (pkgJson.cordova.platforms === undefined) {
                pkgJson.cordova.platforms = [];
            }
            // If comboArray has the same platforms as pkg.json, no modification to pkg.json.
            if (comboArray.toString() === pkgJson.cordova.platforms.toString()) {
                events.emit('verbose', 'Config.xml and package.json platforms are the same. No pkg.json modification.');
            } else {
                // Modify pkg.json to include the elements.
                // From the comboArray array so that the arrays are identical.
                events.emit('verbose', 'Config.xml and package.json platforms are different. Updating package.json with most current list of platforms.');
                modifiedPkgJson = true;
            }

            events.emit('verbose', 'Package.json and config.xml platforms are different. Updating config.xml with most current list of platforms.');
            comboArray.forEach(function (item) {
                var prefixItem = ('cordova-' + item);

                // Modify package.json if any of these cases are true:
                if ((pkgJson.dependencies === undefined && Object.keys(mergedPlatformSpecs).length) ||
                (pkgJson.dependencies && mergedPlatformSpecs && pkgJson.dependencies[item] === undefined && mergedPlatformSpecs[item]) ||
                (pkgJson.dependencies && mergedPlatformSpecs && pkgJson.dependencies[prefixItem] === undefined && mergedPlatformSpecs[prefixItem])) {
                    modifiedPkgJson = true;
                }

                // Get the cordova- prefixed spec from package.json and add it to mergedPluginSpecs.
                if (pkgJson.dependencies && pkgJson.dependencies[prefixItem]) {
                    if (mergedPlatformSpecs[prefixItem] !== pkgJson.dependencies[prefixItem]) {
                        modifiedPkgJson = true;
                    }
                    mergedPlatformSpecs[item] = pkgJson.dependencies[prefixItem];
                }

                // Get the spec from package.json and add it to mergedPluginSpecs.
                if (pkgJson.dependencies && pkgJson.dependencies[item] && pkgJson.dependencies[prefixItem] === undefined) {
                    if (mergedPlatformSpecs[item] !== pkgJson.dependencies[item]) {
                        modifiedPkgJson = true;
                    }
                    mergedPlatformSpecs[item] = pkgJson.dependencies[item];
                }
            });
        }

        // Write and update pkg.json if it has been modified.
        if (modifiedPkgJson === true) {
            pkgJson.cordova.platforms = comboArray;
            if (pkgJson.dependencies === undefined) {
                pkgJson.dependencies = {};
            }
            // Check if key is part of cordova alias list.
            // Add prefix if it is.
            for (key in mergedPlatformSpecs) {
                var prefixKey = key;
                if (key in platformsList) {
                    prefixKey = 'cordova-' + key;
                }
                pkgJson.dependencies[prefixKey] = mergedPlatformSpecs[key];
            }
            fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, indent), 'utf8');
        }
        if (!comboArray || !comboArray.length) {
            return Promise.resolve('No platforms found in config.xml and/or package.json that haven\'t been added to the project');
        }
    }
    // Run `platform add` for all the platforms separately
    // so that failure on one does not affect the other.

    // CB-9278 : Run `platform add` serially, one platform after another
    // Otherwise, we get a bug where the following line: https://github.com/apache/cordova-lib/blob/0b0dee5e403c2c6d4e7262b963babb9f532e7d27/cordova-lib/src/util/npm-helper.js#L39
    // gets executed simultaneously by each platform and leads to an exception being thrown

    return promiseutil.Q_chainmap_graceful(comboArray, function (target) {
        var cwd = process.cwd();
        var platformsFolderPath = path.join(cwd, 'platforms');
        var platformsInstalled = path.join(platformsFolderPath, target);
        if (target) {
            var platformName = target;
            // Add the spec to the target
            if (mergedPlatformSpecs[target]) {
                target = target + '@' + mergedPlatformSpecs[target];
            }
            // If the platform is already installed, no need to re-install it.
            if (!fs.existsSync(platformsInstalled) && (installAllPlatforms || platforms.indexOf(platformName) > -1)) {
                events.emit('log', 'Discovered platform "' + target + '" in config.xml or package.json. Adding it to the project');
                return cordovaPlatform('add', target, opts);
            }
        }
        return Promise.resolve();
    }, function (err) {
        events.emit('warn', err);
    });
}
Install platforms looking at config.xml and package.json (if there is one).