123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- 'use strict';
- var $ = {
- _: require('lodash'),
- fs: require('fs'),
- lodash: require('lodash'),
- path: require('path'),
- glob: require('glob'),
- propprop: require('propprop')
- };
- /**
- * Detect dependencies of the components from `bower.json`.
- *
- * @param {object} config the global configuration object.
- * @return {object} config
- */
- function detectDependencies(config) {
- var allDependencies = {};
- if (config.get('dependencies')) {
- $._.assign(allDependencies, config.get('bower.json').dependencies);
- }
- if (config.get('dev-dependencies')) {
- $._.assign(allDependencies, config.get('bower.json').devDependencies);
- }
- if (config.get('include-self')) {
- allDependencies[config.get('bower.json').name] = config.get('bower.json').version;
- }
- $._.each(allDependencies, gatherInfo(config));
- config.set('global-dependencies-sorted', filterExcludedDependencies(
- config.get('detectable-file-types').
- reduce(function (acc, fileType) {
- if (!acc[fileType]) {
- acc[fileType] = prioritizeDependencies(config, '.' + fileType);
- }
- return acc;
- }, {}),
- config.get('exclude')
- ));
- return config;
- }
- /**
- * Find the component's JSON configuration file.
- *
- * @param {object} config the global configuration object
- * @param {string} component the name of the component to dig for
- * @return {object} the component's config file
- */
- function findComponentConfigFile(config, component) {
- var componentConfigFile;
- if (config.get('include-self') && component === config.get('bower.json').name) {
- return config.get('bower.json');
- }
- ['bower.json', '.bower.json', 'component.json', 'package.json'].
- forEach(function (configFile) {
- configFile = $.path.join(config.get('bower-directory'), component, configFile);
- if (!$._.isObject(componentConfigFile) && $.fs.existsSync(configFile)) {
- componentConfigFile = JSON.parse($.fs.readFileSync(configFile));
- }
- });
- return componentConfigFile;
- }
- /**
- * Find the main file the component refers to. It's not always `main` :(
- *
- * @param {object} config the global configuration object
- * @param {string} component the name of the component to dig for
- * @param {componentConfigFile} the component's config file
- * @return {array} the array of paths to the component's primary file(s)
- */
- function findMainFiles(config, component, componentConfigFile) {
- var filePaths = [];
- var file;
- var self = config.get('include-self') && component === config.get('bower.json').name;
- var cwd = self ? config.get('cwd') : $.path.join(config.get('bower-directory'), component);
- if ($._.isString(componentConfigFile.main)) {
- // start by looking for what every component should have: config.main
- filePaths = [componentConfigFile.main];
- } else if ($._.isArray(componentConfigFile.main)) {
- filePaths = componentConfigFile.main;
- } else if ($._.isArray(componentConfigFile.scripts)) {
- // still haven't found it. is it stored in config.scripts, then?
- filePaths = componentConfigFile.scripts;
- } else {
- file = $.path.join(config.get('bower-directory'), component, componentConfigFile.name + '.js');
- if ($.fs.existsSync(file)) {
- filePaths = [componentConfigFile.name + '.js'];
- }
- }
- return $._.unique(filePaths.reduce(function (acc, filePath) {
- acc = acc.concat(
- $.glob.sync(filePath, { cwd: cwd, root: '/' })
- .map(function (path) {
- return $.path.join(cwd, path);
- })
- );
- return acc;
- }, []));
- }
- /**
- * Store the information our prioritizer will need to determine rank.
- *
- * @param {object} config the global configuration object
- * @return {function} the iterator function, called on every component
- */
- function gatherInfo(config) {
- /**
- * The iterator function, which is called on each component.
- *
- * @param {string} version the version of the component
- * @param {string} component the name of the component
- * @return {undefined}
- */
- return function (version, component) {
- var dep = config.get('global-dependencies').get(component) || {
- main: '',
- type: '',
- name: '',
- dependencies: {}
- };
- var componentConfigFile = findComponentConfigFile(config, component);
- if (!componentConfigFile) {
- var error = new Error(component + ' is not installed. Try running `bower install`.');
- error.code = 'PKG_NOT_INSTALLED';
- config.get('on-error')(error);
- return;
- }
- var overrides = config.get('overrides');
- if (overrides && overrides[component]) {
- if (overrides[component].dependencies) {
- componentConfigFile.dependencies = overrides[component].dependencies;
- }
- if (overrides[component].main) {
- componentConfigFile.main = overrides[component].main;
- }
- }
- var mains = findMainFiles(config, component, componentConfigFile);
- var fileTypes = $._.chain(mains).map($.path.extname).unique().value();
- dep.main = mains;
- dep.type = fileTypes;
- dep.name = componentConfigFile.name;
- var depIsExcluded = $._.find(config.get('exclude'), function (pattern) {
- return $.path.join(config.get('bower-directory'), component).match(pattern);
- });
- if (dep.main.length === 0 && !depIsExcluded) {
- // can't find the main file. this config file is useless!
- config.get('on-main-not-found')(component);
- return;
- }
- if (componentConfigFile.dependencies) {
- dep.dependencies = componentConfigFile.dependencies;
- $._.each(componentConfigFile.dependencies, gatherInfo(config));
- }
- config.get('global-dependencies').set(component, dep);
- };
- }
- /**
- * Compare two dependencies to determine priority.
- *
- * @param {object} a dependency a
- * @param {object} b dependency b
- * @return {number} the priority of dependency a in comparison to dependency b
- */
- function dependencyComparator(a, b) {
- var aNeedsB = false;
- var bNeedsA = false;
- aNeedsB = Object.
- keys(a.dependencies).
- some(function (dependency) {
- return dependency === b.name;
- });
- if (aNeedsB) {
- return 1;
- }
- bNeedsA = Object.
- keys(b.dependencies).
- some(function (dependency) {
- return dependency === a.name;
- });
- if (bNeedsA) {
- return -1;
- }
- return 0;
- }
- /**
- * Take two arrays, sort based on their dependency relationship, then merge them
- * together.
- *
- * @param {array} left
- * @param {array} right
- * @return {array} the sorted, merged array
- */
- function merge(left, right) {
- var result = [];
- var leftIndex = 0;
- var rightIndex = 0;
- while (leftIndex < left.length && rightIndex < right.length) {
- if (dependencyComparator(left[leftIndex], right[rightIndex]) < 1) {
- result.push(left[leftIndex++]);
- } else {
- result.push(right[rightIndex++]);
- }
- }
- return result.
- concat(left.slice(leftIndex)).
- concat(right.slice(rightIndex));
- }
- /**
- * Take an array and slice it in halves, sorting each half along the way.
- *
- * @param {array} items
- * @return {array} the sorted array
- */
- function mergeSort(items) {
- if (items.length < 2) {
- return items;
- }
- var middle = Math.floor(items.length / 2);
- return merge(
- mergeSort(items.slice(0, middle)),
- mergeSort(items.slice(middle))
- );
- }
- /**
- * Some dependencies which we know should always come first.
- */
- var eliteDependencies = [
- 'es5-shim',
- 'jquery',
- 'zepto',
- 'modernizr'
- ];
- /**
- * Sort the dependencies in the order we can best determine they're needed.
- *
- * @param {object} config the global configuration object
- * @param {string} fileType the type of file to prioritize
- * @return {array} the sorted items of 'path/to/main/files.ext' sorted by type
- */
- function prioritizeDependencies(config, fileType) {
- var eliteDependenciesCaught = [];
- var dependencies = mergeSort(
- $._.toArray(config.get('global-dependencies').get()).
- filter(function (dependency) {
- return $._.contains(dependency.type, fileType);
- }).
- filter(function (dependency) {
- if ($._.contains(eliteDependencies, dependency.name)) {
- eliteDependenciesCaught.push(dependency.main);
- } else {
- return true;
- }
- })
- ).map($.propprop('main'));
- eliteDependenciesCaught.
- forEach(function (dependency) {
- dependencies.unshift(dependency);
- });
- return $._
- (dependencies).
- flatten().
- value().
- filter(function (main) {
- return $.path.extname(main) === fileType;
- });
- }
- /**
- * Excludes dependencies that match any of the patterns.
- *
- * @param {array} allDependencies array of dependencies to filter
- * @param {array} patterns array of patterns to match against
- * @return {array} items that don't match any of the patterns
- */
- function filterExcludedDependencies(allDependencies, patterns) {
- return $._.transform(allDependencies, function (result, dependencies, fileType) {
- result[fileType] = $._.reject(dependencies, function (dependency) {
- return $._.find(patterns, function (pattern) {
- return dependency.replace(/\\/g, '/').match(pattern);
- });
- });
- });
- }
- module.exports = detectDependencies;
|