aboutsummaryrefslogtreecommitdiff
path: root/node_modules/hullabaloo-config-manager/lib
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-05-28 00:38:50 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-05-28 00:40:43 +0200
commit7fff4499fd915bcea3fa93b1aa8b35f4fe7a6027 (patch)
tree6de9a1aebd150a23b7f8c273ec657a5d0a18fe3e /node_modules/hullabaloo-config-manager/lib
parent963b7a41feb29cc4be090a2446bdfe0c1f1bcd81 (diff)
add linting (and some initial fixes)
Diffstat (limited to 'node_modules/hullabaloo-config-manager/lib')
-rw-r--r--node_modules/hullabaloo-config-manager/lib/ResolvedConfig.js35
-rw-r--r--node_modules/hullabaloo-config-manager/lib/Verifier.js178
-rw-r--r--node_modules/hullabaloo-config-manager/lib/codegen.js64
-rw-r--r--node_modules/hullabaloo-config-manager/lib/collector.js332
-rw-r--r--node_modules/hullabaloo-config-manager/lib/currentEnv.js8
-rw-r--r--node_modules/hullabaloo-config-manager/lib/errors.js48
-rw-r--r--node_modules/hullabaloo-config-manager/lib/hashDependencies.js44
-rw-r--r--node_modules/hullabaloo-config-manager/lib/hashSources.js51
-rw-r--r--node_modules/hullabaloo-config-manager/lib/readSafe.js29
-rw-r--r--node_modules/hullabaloo-config-manager/lib/reduceChains.js158
-rw-r--r--node_modules/hullabaloo-config-manager/lib/resolvePluginsAndPresets.js133
11 files changed, 1080 insertions, 0 deletions
diff --git a/node_modules/hullabaloo-config-manager/lib/ResolvedConfig.js b/node_modules/hullabaloo-config-manager/lib/ResolvedConfig.js
new file mode 100644
index 000000000..07d5cd474
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/ResolvedConfig.js
@@ -0,0 +1,35 @@
+'use strict'
+
+const codegen = require('./codegen')
+const reduceChains = require('./reduceChains')
+const Verifier = require('./Verifier')
+
+class ResolvedConfig {
+ constructor (chains, cache) {
+ this.cache = cache
+ this.babelrcDir = chains.babelrcDir
+
+ const reduced = reduceChains(chains, cache)
+ this.dependencies = reduced.dependencies
+ this.envNames = reduced.envNames
+ this.fixedSourceHashes = reduced.fixedSourceHashes
+ this.sources = reduced.sources
+ this.unflattenedDefaultOptions = reduced.unflattenedDefaultOptions
+ this.unflattenedEnvOptions = reduced.unflattenedEnvOptions
+ }
+
+ createVerifier () {
+ return Verifier.hashAndCreate(
+ this.babelrcDir,
+ this.envNames,
+ this.dependencies,
+ this.sources,
+ this.fixedSourceHashes,
+ this.cache)
+ }
+
+ generateModule () {
+ return codegen(this)
+ }
+}
+module.exports = ResolvedConfig
diff --git a/node_modules/hullabaloo-config-manager/lib/Verifier.js b/node_modules/hullabaloo-config-manager/lib/Verifier.js
new file mode 100644
index 000000000..267fcb2bb
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/Verifier.js
@@ -0,0 +1,178 @@
+'use strict'
+
+const fs = require('fs')
+const path = require('path')
+
+const isEqual = require('lodash.isequal')
+const md5Hex = require('md5-hex')
+
+const currentEnv = require('./currentEnv')
+const hashDependencies = require('./hashDependencies')
+const hashSources = require('./hashSources')
+
+function ensureMissingBabelrcFile (file, cache) {
+ if (cache && cache.fileExistence && cache.fileExistence.has(file)) {
+ return cache.fileExistence.get(file)
+ }
+
+ const promise = new Promise((resolve, reject) => {
+ fs.access(file, err => {
+ if (err) {
+ if (err.code !== 'ENOENT') {
+ reject(err)
+ } else {
+ resolve(true)
+ }
+ } else {
+ resolve(false)
+ }
+ })
+ })
+
+ if (cache && cache.fileExistence) {
+ cache.fileExistence.set(file, promise)
+ }
+ return promise
+}
+
+class Verifier {
+ constructor (babelrcDir, envNames, dependencies, sources) {
+ Object.assign(this, { babelrcDir, envNames, dependencies, sources })
+ }
+
+ selectByEnv (arr, envName, mapFn) {
+ const selectDefault = !this.envNames.has(envName)
+ return arr
+ .filter(item => selectDefault ? item.default : item.envs.has(envName))
+ .map(mapFn || (item => item))
+ }
+
+ cacheKeysForCurrentEnv () {
+ const envName = currentEnv()
+ const getHash = item => item.hash
+
+ const dependencyHashes = this.selectByEnv(this.dependencies, envName, getHash)
+ const sourceHashes = this.selectByEnv(this.sources, envName, getHash)
+
+ return {
+ dependencies: md5Hex(dependencyHashes),
+ sources: md5Hex(sourceHashes)
+ }
+ }
+
+ verifyCurrentEnv (fixedHashes, cache) {
+ const envName = currentEnv()
+
+ const sourcesToHash = this.selectByEnv(this.sources, envName)
+ const expectedSourceHashes = sourcesToHash.map(item => item.hash)
+ const pendingSourceHashes = hashSources(sourcesToHash, fixedHashes && fixedHashes.sources, cache)
+
+ let checkedBabelrcFile = true
+ if (this.babelrcDir) {
+ const babelrcFile = path.join(this.babelrcDir, '.babelrc')
+ if (!sourcesToHash.some(item => item.source === babelrcFile)) {
+ checkedBabelrcFile = ensureMissingBabelrcFile(babelrcFile, cache)
+ }
+ }
+
+ const dependenciesToHash = this.selectByEnv(this.dependencies, envName)
+ const expectedDependencyHashes = dependenciesToHash.map(item => item.hash)
+ const pendingDependencyHashes = hashDependencies(dependenciesToHash, cache)
+
+ return Promise.all([
+ pendingSourceHashes,
+ checkedBabelrcFile
+ ])
+ .then(result => {
+ const sourceHashes = result[0]
+ const babelrcFileIsSame = result[1]
+
+ if (!babelrcFileIsSame || !isEqual(sourceHashes, expectedSourceHashes)) {
+ return { sourcesChanged: true }
+ }
+
+ return pendingDependencyHashes
+ .then(dependencyHashes => {
+ const dependenciesChanged = !isEqual(dependencyHashes, expectedDependencyHashes)
+
+ let verifier = this
+ if (dependenciesChanged) {
+ const dependencies = this.dependencies.map((item, index) => {
+ const hash = dependencyHashes[index]
+ return Object.assign({}, item, { hash })
+ })
+
+ verifier = new Verifier(this.babelrcDir, this.envNames, dependencies, this.sources)
+ }
+
+ return {
+ sourcesChanged: false,
+ dependenciesChanged,
+ cacheKeys: {
+ dependencies: md5Hex(dependencyHashes),
+ sources: md5Hex(sourceHashes)
+ },
+ verifier
+ }
+ })
+ })
+ .catch(err => {
+ if (err.name === 'NoSourceFileError') {
+ return {
+ missingSource: true
+ }
+ }
+
+ if (err.name === 'BadDependencyError') {
+ return {
+ badDependency: true
+ }
+ }
+
+ throw err
+ })
+ }
+
+ toBuffer () {
+ return Buffer.from(JSON.stringify({
+ babelrcDir: this.babelrcDir,
+ envNames: this.envNames,
+ dependencies: this.dependencies,
+ sources: this.sources
+ }, (key, value) => {
+ return key === 'envNames' || key === 'envs'
+ ? Array.from(value)
+ : value
+ }, 2))
+ }
+
+ static fromBuffer (buffer) {
+ const json = JSON.parse(buffer.toString('utf8'), (key, value) => {
+ return key === 'envNames' || key === 'envs'
+ ? new Set(value)
+ : value
+ })
+ return new this(json.babelrcDir, json.envNames, json.dependencies, json.sources)
+ }
+
+ static hashAndCreate (babelrcDir, envNames, dependencies, sources, fixedSourceHashes, cache) {
+ return Promise.all([
+ hashDependencies(dependencies, cache),
+ hashSources(sources, fixedSourceHashes, cache)
+ ])
+ .then(results => {
+ const dependencyHashes = results[0]
+ const sourceHashes = results[1]
+
+ dependencies.forEach((item, index) => {
+ item.hash = dependencyHashes[index]
+ })
+ sources.forEach((item, index) => {
+ item.hash = sourceHashes[index]
+ })
+
+ return new this(babelrcDir, envNames, dependencies, sources)
+ })
+ }
+}
+module.exports = Verifier
diff --git a/node_modules/hullabaloo-config-manager/lib/codegen.js b/node_modules/hullabaloo-config-manager/lib/codegen.js
new file mode 100644
index 000000000..0f95284d2
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/codegen.js
@@ -0,0 +1,64 @@
+'use strict'
+
+const indentString = require('indent-string')
+const stringifyJson5 = require('json5').stringify
+
+function stringify (json5, value) {
+ return json5
+ ? stringifyJson5(value, null, 2)
+ : JSON.stringify(value, null, 2)
+}
+
+function generateFactory (unflattened, envName) {
+ const code = [`${envName ? '()' : 'envName'} => {`]
+
+ if (envName) {
+ const flattenedOptions = unflattened.reduceRight((prev, options) => {
+ options.env = {
+ [envName]: prev
+ }
+ return options
+ })
+ code.push(indentString(`return ${stringify(unflattened.json5, flattenedOptions)}`, 2))
+ } else {
+ const optionsCode = unflattened.reduceRight((prev, options, index) => {
+ const str = stringify(unflattened.json5, options)
+ if (!prev) return str
+
+ // reduceOptions ensures no options object is ever empty.
+ const lines = str.split('\n')
+ lines[lines.length - 2] += ','
+ lines[lines.length - 1] = indentString(`env: {\n [envName]: ${indentString(prev, 2).trimLeft()}\n}`, 2)
+ return lines.join('\n') + '\n}'
+ }, null)
+
+ code.push(indentString(`return ${optionsCode.trimLeft()}`, 2))
+ }
+
+ code.push('}')
+ return code.join('\n')
+}
+
+function codegen (resolvedConfig) {
+ const code = [`"use strict"
+
+const process = require("process")\n`]
+ code.push(`const defaultOptions = ${generateFactory(resolvedConfig.unflattenedDefaultOptions)}\n`)
+
+ code.push(`const envOptions = Object.create(null)\n`)
+ for (const envName of resolvedConfig.envNames) {
+ const unflattened = resolvedConfig.unflattenedEnvOptions.get(envName)
+ code.push(`envOptions[${JSON.stringify(envName)}] = ${generateFactory(unflattened, envName)}\n`)
+ }
+
+ code.push(`exports.getOptions = () => {
+ const envName = process.env.BABEL_ENV || process.env.NODE_ENV || "development"
+ return envName in envOptions
+ ? envOptions[envName]()
+ : defaultOptions(envName)
+}\n`)
+
+ return code.join('\n')
+}
+
+module.exports = codegen
diff --git a/node_modules/hullabaloo-config-manager/lib/collector.js b/node_modules/hullabaloo-config-manager/lib/collector.js
new file mode 100644
index 000000000..1a7414a87
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/collector.js
@@ -0,0 +1,332 @@
+'use strict'
+
+const path = require('path')
+
+const parseJson5 = require('json5').parse
+
+const errors = require('./errors')
+const readSafe = require('./readSafe')
+
+function makeValid (source, options) {
+ // Arrays are never valid options.
+ if (Array.isArray(options)) throw new errors.InvalidFileError(source)
+
+ // Force options to be an object. Babel itself ignores falsy values when
+ // resolving config chains. Here such files still need to be included
+ // for cache busting purposes.
+ if (!options || typeof options !== 'object') return {}
+
+ return options
+}
+
+function parseFile (source, buffer) {
+ let options
+ try {
+ options = parseJson5(buffer.toString('utf8'))
+ } catch (err) {
+ throw new errors.ParseError(source, err)
+ }
+
+ return makeValid(source, options)
+}
+
+function parsePackage (source, buffer) {
+ let options
+ try {
+ const pkg = JSON.parse(buffer.toString('utf8'))
+ options = pkg && pkg.babel
+ } catch (err) {
+ throw new errors.ParseError(source, err)
+ }
+
+ return makeValid(source, options)
+}
+
+class Config {
+ constructor (dir, env, hash, json5, options, source) {
+ this.dir = dir
+ this.env = env
+ this.hash = hash
+ this.json5 = json5
+ this.options = options
+ this.source = source
+
+ this.babelrcPointer = null
+ this.envPointers = new Map()
+ this.extends = null
+ this.extendsPointer = null
+ }
+
+ copyWithEnv (env, options) {
+ return new this.constructor(this.dir, env, this.hash, this.json5, options, this.source)
+ }
+
+ extend (config) {
+ const clause = this.takeExtends()
+ if (clause) {
+ throw new TypeError(`Cannot extend config: there is an extends clause in the current options: ${clause}`)
+ }
+ if (this.extends) {
+ throw new Error('Cannot extend config: already extended')
+ }
+
+ this.extends = config
+ }
+
+ takeEnvs () {
+ const env = this.options.env
+ delete this.options.env
+
+ return env
+ ? new Map(
+ Object.keys(env)
+ .filter(Boolean)
+ .map(name => [name, env[name]]))
+ : new Map()
+ }
+
+ takeExtends () {
+ const clause = this.options.extends
+ delete this.options.extends
+ return clause
+ }
+}
+exports.Config = Config
+
+function resolveDirectory (dir, cache) {
+ const fileSource = path.join(dir, '.babelrc')
+ const packageSource = path.join(dir, 'package.json')
+
+ const fromFile = readSafe(fileSource, cache)
+ .then(contents => contents && {
+ json5: true,
+ parse () { return parseFile(fileSource, contents) },
+ source: fileSource
+ })
+
+ const fromPackage = readSafe(packageSource, cache)
+ .then(contents => contents && {
+ json5: false,
+ parse () { return parsePackage(packageSource, contents) },
+ source: packageSource
+ })
+
+ return fromFile
+ .then(fileResult => fileResult || fromPackage)
+ .then(result => {
+ // .babelrc or package.json files may not exist, and that's OK.
+ if (!result) return null
+
+ return new Config(dir, null, null, result.json5, result.parse(), result.source)
+ })
+}
+
+function resolveFile (source, cache) {
+ return readSafe(source, cache)
+ .then(contents => {
+ // The file *must* exist. Causes a proper error to be propagated to
+ // where "extends" directives are resolved.
+ if (!contents) throw new errors.NoSourceFileError(source)
+
+ return new Config(path.dirname(source), null, null, true, parseFile(source, contents), source)
+ })
+}
+
+class Chains {
+ constructor (babelrcDir, defaultChain, envChains) {
+ this.babelrcDir = babelrcDir
+ this.defaultChain = defaultChain
+ this.envChains = envChains
+ }
+
+ * [Symbol.iterator] () {
+ yield this.defaultChain
+ for (const chain of this.envChains.values()) {
+ yield chain
+ }
+ }
+}
+
+class Collector {
+ constructor (cache) {
+ this.cache = cache
+ this.configs = []
+ this.envNames = new Set()
+ this.pointers = new Map()
+ }
+
+ get initialConfig () {
+ return this.configs[0]
+ }
+
+ add (config) {
+ // Avoid adding duplicate configs. Note that configs that came from an
+ // "env" directive share their source with their parent config.
+ if (!config.env && this.pointers.has(config.source)) {
+ return Promise.resolve(this.pointers.get(config.source))
+ }
+
+ const pointer = this.configs.push(config) - 1
+ // Make sure not to override the pointer to an environmental
+ // config's parent.
+ if (!config.env) this.pointers.set(config.source, pointer)
+
+ const envs = config.takeEnvs()
+ const extendsClause = config.takeExtends()
+ const waitFor = []
+
+ if (config.extends) {
+ const promise = this.add(config.extends)
+ .then(extendsPointer => (config.extendsPointer = extendsPointer))
+ waitFor.push(promise)
+ } else if (extendsClause) {
+ const extendsSource = path.resolve(config.dir, extendsClause)
+
+ if (this.pointers.has(extendsSource)) {
+ // Point at existing config.
+ config.extendsPointer = this.pointers.get(extendsSource)
+ } else {
+ // Different configs may concurrently resolve the same extends source.
+ // While only one such resolution is added to the config list, this
+ // does lead to extra file I/O and parsing. Optimizing this is not
+ // currently considered worthwhile.
+ const promise = resolveFile(extendsSource, this.cache)
+ .then(parentConfig => this.add(parentConfig))
+ .then(extendsPointer => (config.extendsPointer = extendsPointer))
+ .catch(err => {
+ if (err.name === 'NoSourceFileError') {
+ throw new errors.ExtendsError(config.source, extendsClause, err)
+ }
+
+ throw err
+ })
+
+ waitFor.push(promise)
+ }
+ }
+
+ for (const pair of envs) {
+ const name = pair[0]
+ const options = pair[1]
+
+ this.envNames.add(name)
+ const promise = this.add(config.copyWithEnv(name, options))
+ .then(envPointer => config.envPointers.set(name, envPointer))
+ waitFor.push(promise)
+ }
+
+ return Promise.all(waitFor)
+ .then(() => pointer)
+ }
+
+ resolveChains (babelrcDir) {
+ if (this.configs.length === 0) return null
+
+ // Resolves a config chain, correctly ordering parent configs and recursing
+ // through environmental configs, while avoiding cycles and repetitions.
+ const resolveChain = (from, envName) => {
+ const chain = new Set()
+ const knownParents = new Set()
+
+ /* eslint-disable no-use-before-define */
+ const addWithEnv = config => {
+ // Avoid unnecessary work in case the `from` list contains configs that
+ // have already been added through an environmental config's parent.
+ if (chain.has(config)) return
+ chain.add(config)
+
+ if (config.envPointers.has(envName)) {
+ const pointer = config.envPointers.get(envName)
+ const envConfig = this.configs[pointer]
+ addAfterParents(envConfig)
+ }
+ }
+
+ const addAfterParents = config => {
+ // Avoid cycles by ignoring those parents that are already being added.
+ if (knownParents.has(config)) return
+ knownParents.add(config)
+
+ if (config.babelrcPointer !== null) {
+ const parent = this.configs[config.babelrcPointer]
+ addAfterParents(parent)
+ }
+ if (config.extendsPointer !== null) {
+ const parent = this.configs[config.extendsPointer]
+ addAfterParents(parent)
+ }
+
+ if (envName) {
+ addWithEnv(config)
+ } else {
+ chain.add(config)
+ }
+ }
+ /* eslint-enable no-use-before-define */
+
+ for (const config of from) {
+ if (envName) {
+ addWithEnv(config)
+ } else {
+ addAfterParents(config)
+ }
+ }
+
+ return chain
+ }
+
+ // Start with the first config. This is either the base config provided
+ // to fromConfig(), or the config derived from .babelrc / package.json
+ // found in fromDirectory().
+ const defaultChain = resolveChain([this.initialConfig])
+
+ // For each environment, augment the default chain with environmental
+ // configs.
+ const envChains = new Map(Array.from(this.envNames, name => {
+ return [name, resolveChain(defaultChain, name)]
+ }))
+
+ return new Chains(babelrcDir, defaultChain, envChains)
+ }
+}
+
+function fromConfig (baseConfig, cache) {
+ let babelrcConfig = null
+ for (let config = baseConfig; config; config = config.extends) {
+ if (config.options.babelrc === false) continue
+
+ if (babelrcConfig) {
+ throw new TypeError(`${config.source}: Cannot resolve babelrc option, already resolved by ${babelrcConfig.source}`)
+ }
+
+ babelrcConfig = config
+ }
+
+ const collector = new Collector(cache)
+ return Promise.all([
+ collector.add(baseConfig),
+ // Resolve the directory concurrently. Assumes that in the common case,
+ // the babelrcConfig doesn't extend from a .babelrc file while also leaving
+ // the babelrc option enabled. Worst case the resolved config is discarded
+ // as a duplicate.
+ babelrcConfig && resolveDirectory(babelrcConfig.dir, cache)
+ .then(parentConfig => {
+ if (!parentConfig) return
+
+ return collector.add(parentConfig)
+ .then(babelrcPointer => (babelrcConfig.babelrcPointer = babelrcPointer))
+ })
+ ])
+ .then(() => collector.resolveChains(babelrcConfig && babelrcConfig.dir))
+}
+exports.fromConfig = fromConfig
+
+function fromDirectory (dir, cache) {
+ dir = path.resolve(dir)
+
+ const collector = new Collector(cache)
+ return resolveDirectory(dir, cache)
+ .then(config => config && collector.add(config))
+ .then(() => collector.resolveChains(dir))
+}
+exports.fromDirectory = fromDirectory
diff --git a/node_modules/hullabaloo-config-manager/lib/currentEnv.js b/node_modules/hullabaloo-config-manager/lib/currentEnv.js
new file mode 100644
index 000000000..3717e3fca
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/currentEnv.js
@@ -0,0 +1,8 @@
+'use strict'
+
+const env = require('process').env
+
+function currentEnv () {
+ return env.BABEL_ENV || env.NODE_ENV || 'development'
+}
+module.exports = currentEnv
diff --git a/node_modules/hullabaloo-config-manager/lib/errors.js b/node_modules/hullabaloo-config-manager/lib/errors.js
new file mode 100644
index 000000000..6ecc2e283
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/errors.js
@@ -0,0 +1,48 @@
+'use strict'
+
+const ExtendableError = require('es6-error')
+
+class SourceError extends ExtendableError {
+ constructor (message, source, parent) {
+ super(`${source}: ${message}`)
+ this.source = source
+ this.parent = parent || null
+ }
+}
+exports.SourceError = SourceError
+
+class NoSourceFileError extends SourceError {
+ constructor (source) {
+ super('No such file', source)
+ }
+}
+exports.NoSourceFileError = NoSourceFileError
+
+class ParseError extends SourceError {
+ constructor (source, parent) {
+ super(`Error while parsing — ${parent.message}`, source, parent)
+ }
+}
+exports.ParseError = ParseError
+
+class InvalidFileError extends SourceError {
+ constructor (source) {
+ super('Not a proper configuration file', source)
+ }
+}
+exports.InvalidFileError = InvalidFileError
+
+class ExtendsError extends SourceError {
+ constructor (source, clause, parent) {
+ super(`Couldn't resolve extends clause: ${clause}`, source, parent)
+ this.clause = clause
+ }
+}
+exports.ExtendsError = ExtendsError
+
+class BadDependencyError extends SourceError {
+ constructor (source, parent) {
+ super("Couldn't resolve dependency", source, parent)
+ }
+}
+exports.BadDependencyError = BadDependencyError
diff --git a/node_modules/hullabaloo-config-manager/lib/hashDependencies.js b/node_modules/hullabaloo-config-manager/lib/hashDependencies.js
new file mode 100644
index 000000000..9587cee1e
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/hashDependencies.js
@@ -0,0 +1,44 @@
+'use strict'
+
+const packageHash = require('package-hash')
+const md5Hex = require('md5-hex')
+
+const errors = require('./errors')
+const readSafe = require('./readSafe')
+
+function hashFile (filename, cache) {
+ return readSafe(filename, cache)
+ .then(contents => {
+ if (!contents) throw new errors.BadDependencyError(filename)
+
+ return md5Hex(contents)
+ })
+}
+
+function hashPackage (filename, fromPackage) {
+ return packageHash(`${fromPackage}/package.json`)
+ .catch(err => {
+ throw new errors.BadDependencyError(filename, err)
+ })
+}
+
+function hashDependency (filename, fromPackage, cache) {
+ if (cache && cache.dependencyHashes && cache.dependencyHashes.has(filename)) {
+ return cache.dependencyHashes.get(filename)
+ }
+
+ const promise = fromPackage
+ ? hashPackage(filename, fromPackage)
+ : hashFile(filename, cache)
+
+ if (cache && cache.dependencyHashes) {
+ cache.dependencyHashes.set(filename, promise)
+ }
+ return promise
+}
+
+function hashDependencies (dependencies, cache) {
+ const promises = dependencies.map(item => hashDependency(item.filename, item.fromPackage, cache))
+ return Promise.all(promises)
+}
+module.exports = hashDependencies
diff --git a/node_modules/hullabaloo-config-manager/lib/hashSources.js b/node_modules/hullabaloo-config-manager/lib/hashSources.js
new file mode 100644
index 000000000..e48660ea9
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/hashSources.js
@@ -0,0 +1,51 @@
+'use strict'
+
+const path = require('path')
+
+const dotProp = require('dot-prop')
+const md5Hex = require('md5-hex')
+
+const errors = require('./errors')
+const readSafe = require('./readSafe')
+
+function hashSource (source, cache) {
+ if (cache && cache.sourceHashes && cache.sourceHashes.has(source)) {
+ return cache.sourceHashes.get(source)
+ }
+
+ const basename = path.basename(source)
+ const parts = basename.split('#')
+ const filename = parts[0]
+ const filepath = path.join(path.dirname(source), filename)
+
+ const pkgAccessor = filename === 'package.json'
+ ? parts[1] || 'babel'
+ : null
+
+ const promise = readSafe(filepath, cache)
+ .then(contents => {
+ if (!contents) throw new errors.NoSourceFileError(source)
+
+ if (!pkgAccessor) {
+ return md5Hex(contents)
+ }
+
+ const json = JSON.parse(contents.toString('utf8'))
+ const value = dotProp.get(json, pkgAccessor) || {}
+ return md5Hex(JSON.stringify(value))
+ })
+
+ if (cache && cache.sourceHashes) {
+ cache.sourceHashes.set(source, promise)
+ }
+ return promise
+}
+
+function hashSources (sources, fixedHashes, cache) {
+ const promises = sources.map(item => {
+ if (fixedHashes && fixedHashes.has(item.source)) return fixedHashes.get(item.source)
+ return hashSource(item.source, cache)
+ })
+ return Promise.all(promises)
+}
+module.exports = hashSources
diff --git a/node_modules/hullabaloo-config-manager/lib/readSafe.js b/node_modules/hullabaloo-config-manager/lib/readSafe.js
new file mode 100644
index 000000000..568258ac6
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/readSafe.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const gfs = require('graceful-fs')
+
+function readSafe (source, cache) {
+ if (cache && cache.files && cache.files.has(source)) {
+ return cache.files.get(source)
+ }
+
+ const promise = new Promise((resolve, reject) => {
+ gfs.readFile(source, (err, contents) => {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ resolve(null)
+ } else {
+ reject(err)
+ }
+ } else {
+ resolve(contents)
+ }
+ })
+ })
+
+ if (cache && cache.files) {
+ cache.files.set(source, promise)
+ }
+ return promise
+}
+module.exports = readSafe
diff --git a/node_modules/hullabaloo-config-manager/lib/reduceChains.js b/node_modules/hullabaloo-config-manager/lib/reduceChains.js
new file mode 100644
index 000000000..33d4de881
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/reduceChains.js
@@ -0,0 +1,158 @@
+'use strict'
+
+const cloneDeepWith = require('lodash.clonedeepwith')
+const merge = require('lodash.merge')
+
+const resolvePluginsAndPresets = require('./resolvePluginsAndPresets')
+
+function trackDependency (dependencies, filename, fromPackage, envName) {
+ if (dependencies.has(filename)) {
+ const existing = dependencies.get(filename)
+ if (envName) {
+ existing.envs.add(envName)
+ } else {
+ existing.default = true
+ }
+ return
+ }
+
+ const item = {
+ default: !envName,
+ envs: new Set(envName ? [envName] : []),
+ filename,
+ fromPackage
+ }
+ dependencies.set(filename, item)
+}
+
+function trackSource (sources, source, envName) {
+ if (sources.has(source)) {
+ const existing = sources.get(source)
+ if (envName) {
+ existing.envs.add(envName)
+ } else {
+ existing.default = true
+ }
+ return
+ }
+
+ const item = {
+ default: !envName,
+ envs: new Set(envName ? [envName] : []),
+ source
+ }
+ sources.set(source, item)
+}
+
+function createOptions (plugins, presets) {
+ const options = {}
+ if (plugins) options.plugins = plugins
+ // istanbul ignore else
+ if (presets) options.presets = presets
+ return options
+}
+
+function compressOptions (orderedOptions) {
+ const remaining = orderedOptions.slice(0, 1)
+ remaining[0].babelrc = false
+
+ for (let index = 1; index < orderedOptions.length; index++) {
+ const options = orderedOptions[index]
+ delete options.babelrc
+
+ const plugins = options.plugins
+ delete options.plugins
+
+ const presets = options.presets
+ delete options.presets
+
+ merge(remaining[0], options)
+
+ if (plugins || presets) {
+ remaining.push(createOptions(plugins, presets))
+ }
+ }
+
+ return remaining
+}
+
+function reduceOptions (chain, envName, pluginsAndPresets, dependencies, sources, fixedSourceHashes) {
+ let json5 = false
+
+ const orderedOptions = Array.from(chain, config => {
+ trackSource(sources, config.source, envName)
+ if (config.hash) {
+ fixedSourceHashes.set(config.source, config.hash)
+ }
+
+ if (config.json5) json5 = true
+
+ const lookup = pluginsAndPresets.get(config)
+ const mapPluginOrPreset = (getEntry, ref) => {
+ if (Array.isArray(ref)) {
+ return ref.length === 1
+ ? mapPluginOrPreset(getEntry, ref[0])
+ : [mapPluginOrPreset(getEntry, ref[0]), ref[1]]
+ }
+
+ const entry = getEntry(ref)
+ trackDependency(dependencies, entry.filename, entry.fromPackage, envName)
+ return entry.filename
+ }
+
+ return cloneDeepWith(config.options, (value, key, object) => {
+ if (object === config.options && (key === 'plugins' || key === 'presets')) {
+ const getEntry = ref => lookup[key].get(ref)
+ return Array.isArray(value)
+ ? value.map(ref => mapPluginOrPreset(getEntry, ref))
+ : []
+ }
+ })
+ })
+
+ const unflattenedOptions = compressOptions(orderedOptions)
+ unflattenedOptions.json5 = json5
+ return unflattenedOptions
+}
+
+function reduceChains (chains, cache) {
+ const pluginsAndPresets = resolvePluginsAndPresets(chains, cache)
+
+ const dependencies = new Map()
+ const envNames = new Set()
+ const fixedSourceHashes = new Map()
+ const sources = new Map()
+
+ const unflattenedDefaultOptions = reduceOptions(
+ chains.defaultChain, null, pluginsAndPresets, dependencies, sources, fixedSourceHashes
+ )
+
+ const unflattenedEnvOptions = new Map()
+ for (const pair of chains.envChains) {
+ const envName = pair[0]
+ const chain = pair[1]
+
+ envNames.add(envName)
+ unflattenedEnvOptions.set(
+ envName,
+ reduceOptions(chain, envName, pluginsAndPresets, dependencies, sources, fixedSourceHashes)
+ )
+ }
+
+ return {
+ dependencies:
+ Array.from(dependencies.keys())
+ .sort()
+ .map(filename => dependencies.get(filename)),
+ envNames,
+ fixedSourceHashes,
+ sources:
+ Array.from(sources.keys())
+ .sort()
+ .map(source => sources.get(source)),
+ unflattenedDefaultOptions,
+ unflattenedEnvOptions
+ }
+}
+
+module.exports = reduceChains
diff --git a/node_modules/hullabaloo-config-manager/lib/resolvePluginsAndPresets.js b/node_modules/hullabaloo-config-manager/lib/resolvePluginsAndPresets.js
new file mode 100644
index 000000000..b72ab4090
--- /dev/null
+++ b/node_modules/hullabaloo-config-manager/lib/resolvePluginsAndPresets.js
@@ -0,0 +1,133 @@
+'use strict'
+
+const path = require('path')
+
+const ExtendableError = require('es6-error')
+const pkgDir = require('pkg-dir')
+const resolveFrom = require('resolve-from')
+
+class ResolveError extends ExtendableError {
+ constructor (source, kind, ref) {
+ super(`${source}: Couldn't find ${kind} ${JSON.stringify(ref)} relative to directory`)
+ this.source = source
+ this.ref = ref
+ this.isPlugin = kind === 'plugin'
+ this.isPreset = kind === 'preset'
+ }
+}
+
+function normalize (arr) {
+ if (!Array.isArray(arr)) return []
+
+ return arr.map(item => Array.isArray(item) ? item[0] : item)
+}
+
+function isFilePath (ref) {
+ return path.isAbsolute(ref) || ref.startsWith('./') || ref.startsWith('../')
+}
+
+function resolveName (name, fromDir, cache) {
+ if (cache.has(name)) return cache.get(name)
+
+ const filename = resolveFrom(fromDir, name)
+ cache.set(name, filename)
+ return filename
+}
+
+function resolvePackage (filename, fromFile) {
+ if (fromFile) return null
+
+ return pkgDir.sync(filename)
+}
+
+function resolvePluginsAndPresets (chains, sharedCache) {
+ const dirCaches = (sharedCache && sharedCache.pluginsAndPresets) || new Map()
+ const getCache = dir => {
+ if (dirCaches.has(dir)) return dirCaches.get(dir)
+
+ const cache = new Map()
+ dirCaches.set(dir, cache)
+ return cache
+ }
+
+ const byConfig = new Map()
+ for (const chain of chains) {
+ for (const config of chain) {
+ if (byConfig.has(config)) continue
+
+ const plugins = new Map()
+ const presets = new Map()
+ byConfig.set(config, { plugins, presets })
+
+ const fromDir = config.dir
+ const cache = getCache(fromDir)
+ const resolve = (kind, ref) => {
+ const possibleNames = []
+ if (isFilePath(ref)) {
+ possibleNames.push({ fromFile: true, name: ref })
+ } else {
+ if (kind === 'plugin') {
+ // Expand possible plugin names, see
+ // https://github.com/babel/babel/blob/510e93b2bd434f05c816fe6639137b35bac267ed/packages/babel-core/src/helpers/get-possible-plugin-names.js
+
+ // Babel doesn't expand scoped plugin references. @ is only valid at
+ // the start of a package name, so disregard refs that would result
+ // in `babel-plugin-@scope/name`.
+ if (!ref.startsWith('@')) {
+ const name = `babel-plugin-${ref}`
+ possibleNames.push({ fromFile: false, name })
+ }
+ } else {
+ // Expand possible preset names, see
+ // https://github.com/babel/babel/blob/510e93b2bd434f05c816fe6639137b35bac267ed/packages/babel-core/src/helpers/get-possible-preset-names.js
+
+ if (ref.startsWith('@')) {
+ const matches = /^(@.+?)\/([^/]+)(.*)/.exec(ref)
+ const scope = matches[1]
+ const partialName = matches[2]
+ const remainder = matches[3]
+
+ const name = `${scope}/babel-preset-${partialName}${remainder}`
+ possibleNames.push({ fromFile: false, name })
+ } else {
+ const name = `babel-preset-${ref}`
+ possibleNames.push({ fromFile: false, name })
+ }
+ }
+
+ possibleNames.push({ fromFile: false, name: ref })
+ }
+
+ let entry = null
+ for (const possibility of possibleNames) {
+ const filename = resolveName(possibility.name, fromDir, cache)
+ if (filename) {
+ const fromPackage = resolvePackage(filename, possibility.fromFile)
+ entry = { filename, fromPackage }
+ break
+ }
+ }
+ if (!entry) {
+ throw new ResolveError(config.source, kind, ref)
+ }
+
+ if (kind === 'plugin') {
+ plugins.set(ref, entry)
+ } else {
+ presets.set(ref, entry)
+ }
+ }
+
+ for (const ref of normalize(config.options.plugins)) {
+ resolve('plugin', ref)
+ }
+ for (const ref of normalize(config.options.presets)) {
+ resolve('preset', ref)
+ }
+ }
+ }
+
+ return byConfig
+}
+
+module.exports = resolvePluginsAndPresets