clean.js 5.59 KB
var fs = require('../../util/fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../../core/PackageRepository');
var semver = require('../../util/semver');
var defaultConfig = require('../../config');

function clean(logger, endpoints, options, config) {
    var decEndpoints;
    var names;

    options = options || {};
    config = defaultConfig(config);

    // If endpoints is an empty array, null them
    if (endpoints && !endpoints.length) {
        endpoints = null;
    }

    // Generate decomposed endpoints and names based on the endpoints
    if (endpoints) {
        decEndpoints = endpoints.map(function (endpoint) {
            return endpointParser.decompose(endpoint);
        });
        names = decEndpoints.map(function (decEndpoint) {
            return decEndpoint.name || decEndpoint.source;
        });
    }

    return Q.all([
        clearPackages(decEndpoints, config, logger),
        clearLinks(names, config, logger)
    ])
    .spread(function (entries) {
        return entries;
    });
}

function clearPackages(decEndpoints, config, logger) {
    var repository =  new PackageRepository(config, logger);

    return repository.list()
    .then(function (entries) {
        var promises;

        // Filter entries according to the specified packages
        if (decEndpoints) {
            entries = entries.filter(function (entry) {
                return !!mout.array.find(decEndpoints, function (decEndpoint) {
                    var entryPkgMeta = entry.pkgMeta;

                    // Check if name or source match the entry
                    if  (decEndpoint.name !== entryPkgMeta.name &&
                        decEndpoint.source !== entryPkgMeta.name &&
                        decEndpoint.source !== entryPkgMeta._source
                    ) {
                        return false;
                    }

                    // If target is a wildcard, simply return true
                    if (decEndpoint.target === '*') {
                        return true;
                    }

                    // If it's a semver target, compare using semver spec
                    if (semver.validRange(decEndpoint.target)) {
                        return semver.satisfies(entryPkgMeta.version, decEndpoint.target);
                    }

                    // Otherwise, compare against target/release
                    return decEndpoint.target === entryPkgMeta._target ||
                           decEndpoint.target === entryPkgMeta._release;
                });
            });
        }

        promises = entries.map(function (entry) {
            return repository.eliminate(entry.pkgMeta)
            .then(function () {
                logger.info('deleted', 'Cached package ' + entry.pkgMeta.name + ': ' + entry.canonicalDir, {
                    file: entry.canonicalDir
                });
            });
        });

        return Q.all(promises)
        .then(function () {
            if (!decEndpoints) {
                // Ensure that everything is cleaned,
                // even invalid packages in the cache
                return repository.clear();
            }
        })
        .then(function () {
            return entries;
        });
    });
}

function clearLinks(names, config, logger) {
    var promise;
    var dir = config.storage.links;

    // If no names are passed, grab all links
    if (!names) {
        promise = Q.nfcall(fs.readdir, dir)
        .fail(function (err) {
            if (err.code === 'ENOENT') {
                return [];
            }

            throw err;
        });
    // Otherwise use passed ones
    } else {
        promise = Q.resolve(names);
    }

    return promise
    .then(function (names) {
        var promises;
        var linksToRemove = [];

        // Decide which links to delete
        promises = names.map(function (name) {
            var link = path.join(config.storage.links, name);

            return Q.nfcall(fs.readlink, link)
            .then(function (linkTarget) {
                // Link exists, check if it points to a folder
                // that still exists
                return Q.nfcall(fs.stat, linkTarget)
                .then(function (stat) {
                    // Target is not a folder..
                    if (!stat.isDirectory()) {
                        linksToRemove.push(link);
                    }
                })
                // Error occurred reading the link
                .fail(function () {
                    linksToRemove.push(link);
                });
            // Ignore if link does not exist
            }, function (err) {
                if (err.code !== 'ENOENT') {
                    linksToRemove.push(link);
                }
            });
        });

        return Q.all(promises)
        .then(function () {
            var promises;

            // Remove each link that was declared as invalid
            promises = linksToRemove.map(function (link) {
                return Q.nfcall(rimraf, link)
                .then(function () {
                    logger.info('deleted', 'Invalid link: ' + link, {
                        file: link
                    });
                });
            });

            return Q.all(promises);
        });
    });
}

// -------------------

clean.readOptions = function (argv) {
    var cli = require('../../util/cli');
    var options = cli.readOptions(argv);
    var endpoints = options.argv.remain.slice(2);

    delete options.argv;

    return [endpoints, options];
};

module.exports = clean;