ksmithut/bower-files

View on GitHub
lib/component.js

Summary

Maintainability
B
6 hrs
Test Coverage
'use strict'

module.exports = Component

var assert = require('assert')
var debug = require('debug')('bower-files')
var path = require('path')
var util = require('util')
var arrify = require('arrify')
var globby = require('globby')
var isAbsolute = require('is-absolute')
var isSymlink = require('is-symlink-sync')
var assign = require('object-assign')
var readJson = require('read-json-sync')
var minimatch = require('minimatch')

function Component (options) {
  options = normalizeOptions(options)

  try {
    this.json = readJson(path.resolve(options.dir, options.json))
  } catch (err) {
    this.json = {}
  }

  var dirName = path.basename(options.dir)
  this.name = options.isRoot // TODO deprecate 'self' name
    ? 'self'
    : this.json.name || dirName
  options.overrides = assign({}, this.json.overrides, options.overrides)
  assign(this.json, options.overrides[dirName], options.overrides[this.name])

  this.files = function (filterOptions) {
    filterOptions = assign(
      {
        fileListProps: ['main'],
        ignoreListProps: [],
        firstFileListProp: true
      },
      filterOptions
    )
    var props = filterOptions.firstFileListProp
      ? find(
          filterOptions.fileListProps,
          function (prop) {
            return typeof this.json[prop] !== 'undefined'
          },
          this
        )
      : filterOptions.fileListProps
    var files = arrify(props).reduce(
      function (fileList, prop) {
        return fileList.concat(this.json[prop] || [])
      }.bind(this),
      []
    )
    var ignore = arrify(filterOptions.ignoreListProps).reduce(
      function (ignoreList, prop) {
        return ignoreList.concat(this.json[prop] || [])
      }.bind(this),
      []
    )
    return Component.mainFiles(options.dir, files, ignore)
  }.bind(this)

  this.dependencies = getDependencies(this.json.dependencies, options)
  if (options.isRoot) {
    this.devDependencies = getDependencies(this.json.devDependencies, options)
  }

  Object.defineProperties(this, {
    dir: {
      get: util.deprecate(function () {
        return options.dependencyDir
      }, 'component.dir: access to dir is deprecated')
    },
    path: {
      get: util.deprecate(function () {
        return options.dir
      }, 'component.path: access to path is deprecated')
    }
  })
}

Component.mainFiles = function mainFiles (dir, mainDef, ignore) {
  if (!mainDef) {
    return []
  }
  return globby
    .sync(arrify(mainDef), { cwd: dir })
    .filter(function (file) {
      return !ignore.some(function (ignorePattern) {
        return minimatch(file, ignorePattern)
      })
    })
    .map(function (file) {
      return path.resolve(dir, file)
    })
}

Component.prototype = {
  getDependencies: function (options) {
    options = assign(
      {
        self: false,
        dev: false,
        main: true
      },
      options
    )

    if (options.self && !this.json.main) {
      debug(
        this.json.name +
          " does not have a main property. You may need to override this to include this component's files"
      ) /* jshint ignore:line */
    }

    var components = []
    if (options.main) {
      components = components.concat(this.dependencies || [])
    }
    if (options.dev) {
      var devDependencies = this.devDependencies || []
      components = options.dev === 'after'
        ? components.concat(devDependencies)
        : devDependencies.concat(components)
    }

    var dependencies = components
      .reduce(function (deps, dep) {
        return deps.concat(dep.getDependencies({ self: true }))
      }, [])
      .concat(options.self ? this : [])
    var depNames = dependencies.map(function (dep) {
      return dep.name
    })
    return dependencies.filter(function (dep, i) {
      return depNames.indexOf(dep.name) === i
    })
  }
}

function getDependencies (dependencies, options) {
  dependencies = dependencies || {}
  return Object.keys(dependencies).map(function (key) {
    return new Component(
      assign({}, options, {
        dir: path.resolve(options.dependencyDir, key),
        isRoot: false
      })
    )
  })
}

function normalizeOptions (options) {
  options = assign(
    {
      dir: null,
      dependencyDir: null,
      json: 'bower.json',
      componentJson: '.bower.json',
      overrides: {},
      isRoot: false
    },
    options
  )

  assert(isAbsolute(options.dir || ''), 'options.dir must be absolute')

  assert(
    isAbsolute(options.dependencyDir || ''),
    'options.dependencyDir must be absolute'
  )

  options.json = options.isRoot || isSymlink(options.dir)
    ? options.json
    : options.componentJson

  return options
}

function find (arr, fn, context) {
  var found
  arr.some(function (val, i, orig) {
    var isFound = fn.call(context, val, i, orig)
    if (isFound) {
      found = val
      return true
    }
  })
  return found
}