src/cordova/plugin/add.js
function determinePluginVersionToFetch (pluginInfo, pluginMap, platformMap, cordovaVersion) {
    var allVersions = pluginInfo.versions;
    var engine = pluginInfo.engines.cordovaDependencies;
    var name = pluginInfo.name;

    // Filters out pre-release versions
    var latest = semver.maxSatisfying(allVersions, '>=0.0.0');

    var versions = [];
    var upperBound = null;
    var upperBoundRange = null;
    var upperBoundExists = false;

    // TODO: lots of 'versions' being thrown around in this function: cordova version,
    // platform version, plugin version. The below for loop: what version is it
    // iterating over? plugin version? please clarify the variable name.
    for (var version in engine) {
        // if a single version && less than latest
        if (semver.valid(semver.clean(version)) && semver.lte(version, latest)) {
            versions.push(version);
        } else {
            // Check if this is an upperbound; validRange() handles whitespace
            var cleanedRange = semver.validRange(version);
            if (cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
                upperBoundExists = true;
                // We only care about the highest upper bound that our project does not support
                if (module.exports.getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) {
                    var maxMatchingUpperBound = cleanedRange.substring(1);
                    if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) {
                        upperBound = maxMatchingUpperBound;
                        upperBoundRange = version;
                    }
                }
            } else {
                events.emit('verbose', 'Ignoring invalid version in ' + name + ' cordovaDependencies: ' + version + ' (must be a single version <= latest or an upper bound)');
            }
        }
    }
    // If there were no valid requirements, we fall back to old behavior
    if (!upperBoundExists && versions.length === 0) {
        events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies entry because it did not contain any valid plugin version entries');
        return null;
    }

    // Handle the lower end of versions by giving them a satisfied engine
    if (!module.exports.findVersion(versions, '0.0.0')) {
        versions.push('0.0.0');
        engine['0.0.0'] = {};
    }

    // Add an entry after the upper bound to handle the versions above the
    // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
    // needs a 1.0.0 entry that has the same engine as 0.0.0
    if (upperBound && !module.exports.findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) {
        versions.push(upperBound);
        var below = semver.maxSatisfying(versions, upperBoundRange);

        // Get the original entry without trimmed whitespace
        below = below ? module.exports.findVersion(versions, below) : null;
        engine[upperBound] = below ? engine[below] : {};
    }

    // Sort in descending order; we want to start at latest and work back
    versions.sort(semver.rcompare);

    for (var i = 0; i < versions.length; i++) {
        if (upperBound && semver.lt(versions[i], upperBound)) {
            // Because we sorted in desc. order, if the upper bound we found
            // applies to this version (and thus the ones below) we can just
            // quit
            break;
        }

        var range = i ? ('>=' + versions[i] + ' <' + versions[i - 1]) : ('>=' + versions[i]);
        var maxMatchingVersion = semver.maxSatisfying(allVersions, range);

        if (maxMatchingVersion && module.exports.getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) {
            // Because we sorted in descending order, we can stop searching once
            // we hit a satisfied constraint
            if (maxMatchingVersion !== latest) {
                var failedReqs = module.exports.getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion);

                // Warn the user that we are not fetching latest
                module.exports.listUnmetRequirements(name, failedReqs);
                events.emit('warn', 'Fetching highest version of ' + name + ' that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')');
            }
            return maxMatchingVersion;
        }
    }

    // No version of the plugin is satisfied. In this case, we fall back to
    // fetching the latest version, but also output a warning
    var latestFailedReqs = versions.length > 0 ? module.exports.getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : [];

    // If the upper bound is greater than latest, we need to combine its engine
    // requirements with latest to print out in the warning
    if (upperBound && semver.satisfies(latest, upperBoundRange)) {
        var upperFailedReqs = module.exports.getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion);
        upperFailedReqs.forEach(function (failedReq) {
            for (var i = 0; i < latestFailedReqs.length; i++) {
                if (latestFailedReqs[i].dependency === failedReq.dependency) {
                    // Not going to overcomplicate things and actually merge the ranges
                    latestFailedReqs[i].required += ' AND ' + failedReq.required;
                    return;
                }
            }

            // There is no req to merge it with
            latestFailedReqs.push(failedReq);
        });
    }

    module.exports.listUnmetRequirements(name, latestFailedReqs);
    events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of ' + name + '. Fetching latest version of plugin anyway (may be incompatible)');

    // No constraints were satisfied
    return null;
}
function determinePluginVersionToFetch (pluginInfo, pluginMap, platformMap, cordovaVersion) {
    var allVersions = pluginInfo.versions;
    var engine = pluginInfo.engines.cordovaDependencies;
    var name = pluginInfo.name;

    // Filters out pre-release versions
    var latest = semver.maxSatisfying(allVersions, '>=0.0.0');

    var versions = [];
    var upperBound = null;
    var upperBoundRange = null;
    var upperBoundExists = false;

    // TODO: lots of 'versions' being thrown around in this function: cordova version,
    // platform version, plugin version. The below for loop: what version is it
    // iterating over? plugin version? please clarify the variable name.
    for (var version in engine) {
        // if a single version && less than latest
        if (semver.valid(semver.clean(version)) && semver.lte(version, latest)) {
            versions.push(version);
        } else {
            // Check if this is an upperbound; validRange() handles whitespace
            var cleanedRange = semver.validRange(version);
            if (cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
                upperBoundExists = true;
                // We only care about the highest upper bound that our project does not support
                if (module.exports.getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) {
                    var maxMatchingUpperBound = cleanedRange.substring(1);
                    if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) {
                        upperBound = maxMatchingUpperBound;
                        upperBoundRange = version;
                    }
                }
            } else {
                events.emit('verbose', 'Ignoring invalid version in ' + name + ' cordovaDependencies: ' + version + ' (must be a single version <= latest or an upper bound)');
            }
        }
    }
    // If there were no valid requirements, we fall back to old behavior
    if (!upperBoundExists && versions.length === 0) {
        events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies entry because it did not contain any valid plugin version entries');
        return null;
    }

    // Handle the lower end of versions by giving them a satisfied engine
    if (!module.exports.findVersion(versions, '0.0.0')) {
        versions.push('0.0.0');
        engine['0.0.0'] = {};
    }

    // Add an entry after the upper bound to handle the versions above the
    // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
    // needs a 1.0.0 entry that has the same engine as 0.0.0
    if (upperBound && !module.exports.findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) {
        versions.push(upperBound);
        var below = semver.maxSatisfying(versions, upperBoundRange);

        // Get the original entry without trimmed whitespace
        below = below ? module.exports.findVersion(versions, below) : null;
        engine[upperBound] = below ? engine[below] : {};
    }

    // Sort in descending order; we want to start at latest and work back
    versions.sort(semver.rcompare);

    for (var i = 0; i < versions.length; i++) {
        if (upperBound && semver.lt(versions[i], upperBound)) {
            // Because we sorted in desc. order, if the upper bound we found
            // applies to this version (and thus the ones below) we can just
            // quit
            break;
        }

        var range = i ? ('>=' + versions[i] + ' <' + versions[i - 1]) : ('>=' + versions[i]);
        var maxMatchingVersion = semver.maxSatisfying(allVersions, range);

        if (maxMatchingVersion && module.exports.getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) {
            // Because we sorted in descending order, we can stop searching once
            // we hit a satisfied constraint
            if (maxMatchingVersion !== latest) {
                var failedReqs = module.exports.getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion);

                // Warn the user that we are not fetching latest
                module.exports.listUnmetRequirements(name, failedReqs);
                events.emit('warn', 'Fetching highest version of ' + name + ' that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')');
            }
            return maxMatchingVersion;
        }
    }

    // No version of the plugin is satisfied. In this case, we fall back to
    // fetching the latest version, but also output a warning
    var latestFailedReqs = versions.length > 0 ? module.exports.getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : [];

    // If the upper bound is greater than latest, we need to combine its engine
    // requirements with latest to print out in the warning
    if (upperBound && semver.satisfies(latest, upperBoundRange)) {
        var upperFailedReqs = module.exports.getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion);
        upperFailedReqs.forEach(function (failedReq) {
            for (var i = 0; i < latestFailedReqs.length; i++) {
                if (latestFailedReqs[i].dependency === failedReq.dependency) {
                    // Not going to overcomplicate things and actually merge the ranges
                    latestFailedReqs[i].required += ' AND ' + failedReq.required;
                    return;
                }
            }

            // There is no req to merge it with
            latestFailedReqs.push(failedReq);
        });
    }

    module.exports.listUnmetRequirements(name, latestFailedReqs);
    events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of ' + name + '. Fetching latest version of plugin anyway (may be incompatible)');

    // No constraints were satisfied
    return null;
}
/*
The engine entry maps plugin versions to constraints like so:
{
'1.0.0' : { 'cordova': '<5.0.0' },
'<2.0.0': {
'cordova': '>=5.0.0',
'cordova-ios': '~5.0.0',
'cordova-plugin-camera': '~5.0.0'
},
'3.0.0' : { 'cordova-ios': '>5.0.0' }
}

TODO: provide a better function description once logic is groked
TODO: update comment below once tests are rewritten/moved around.
See cordova-spec/plugin_fetch.spec.js for test cases and examples