diff options
Diffstat (limited to 'node_modules/hullabaloo-config-manager')
21 files changed, 1581 insertions, 0 deletions
diff --git a/node_modules/hullabaloo-config-manager/LICENSE b/node_modules/hullabaloo-config-manager/LICENSE new file mode 100644 index 000000000..bf1b86df3 --- /dev/null +++ b/node_modules/hullabaloo-config-manager/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 Mark Wubben <mark@novemberborn.net> (novemberborn.net) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/hullabaloo-config-manager/README.md b/node_modules/hullabaloo-config-manager/README.md new file mode 100644 index 000000000..ef2099bad --- /dev/null +++ b/node_modules/hullabaloo-config-manager/README.md @@ -0,0 +1,212 @@ +# hullabaloo-config-manager + +Manages complex [Babel] config chains, avoiding duplicated work and enabling +effective caching. + +> Hullabaloo: informal of "babel" (noun) +> +> A confused noise, typically that made by a number +of voices: *the babel of voices on the road.* + +Use this package to resolve [Babel] config chains. The resulting options result +in equivalent compilation behavior as if `babel-core` had resolved the config. + +A Node.js-compatible JavaScript module can be generated which exports a function +that provides the options object, applicable for the current environment. This +module can be written to disk and reused. + +Config sources and plugin and preset dependencies can be hashed and used as +cache keys. The cache keys and generated module can be verified to avoid having +to repeatedly resolve the config chains, and to be sure a previously +transformation result can be reused. + +This module is used by [AVA]. + +## Installation + +```console +$ npm install --save hullabaloo-config-manager +``` + +## Usage + +```js +const configManager = require('hullabaloo-config-manager') +``` + +## API + +### `currentEnv(): string` + +Returns the current environment value, just like `babel-core` would determine +it. + +### `fromDirectory(dir: string, options?: {cache: Cache}): Promise<null | ResolvedConfig>` + +Asynchronously resolves config chains from the `dir` directory. If no config can +be found the promise is resolved with `null`. Otherwise it is resolved with the +[resulting config object](#resolvedconfig). The promise is rejected if +[errors](#errors) occur. + +A `cache` object may be provided. + +### `createConfig(options: {options: BabelOptions, source: string, dir?: string, hash?: string, json5?: false}): Config` + +Creates and returns an in-memory [config object](#config). The first argument +must be provided, and it must have a valid [`options` object](#babeloptions) and +`source` value. + +If the `dir` value is not provided it's derived from the `source` value. +Dependencies are resolved relative to this `dir`. + +If the config source does not exist on disk the `hash` value should be provided, +otherwise hashes cannot be created for the config. + +The `json5` property can be set to `false` if the `options` object can be +serialized using `JSON.stringify()`. + +Note that the `options` object is cloned (deeply) before use. + +### `fromConfig(baseConfig: Config, options?: {cache: Cache}): Promise<ResolvedConfig>` + +Asynchronously resolves config chains, starting with the `baseConfig`. The +`baseConfig` must be created using the `createConfig()` method. The promise is +resolved with the [resulting config object](#resolvedconfig). The promise is +rejected if [errors](#errors) occur. + +A `cache` object may be provided. + +### `restoreVerifier(buffer: Buffer): Verifier` + +Deserializes a [`Verifier`](#verifier). The `buffer` should be created using +`Verifier#toBuffer()`. + +### `prepareCache(): Cache` + +Creates a cache object that can be passed to the above functions. This may +improve performance by avoiding repeatedly reading files from disk or computing +hashes. + +--- + +### `Config` + +Use `createConfig()` to create this object. + +#### `Config#extend(config: Config)` + +Extend the config with another config. Throws a `TypeError` if the config was +created with an `extends` clause in its `options`. It throws an `Error` if it +has already been extended. + +--- + +### `BabelOptions` + +See <https://babeljs.io/docs/usage/api/#options>. + +--- + +### `ResolvedConfig` + +Returned by `fromConfig()` and `fromDirectory()`. + +#### `ResolvedConfig#generateModule(): string` + +Generates a Node.js-compatible JavaScript module which exports a `getOptions()` +function. This function returns a unique options object, applicable for the +current environment, that can be passed to `babel-core` methods. + +This module needs to evaluated before the `getOptions()` method can be accessed. + +#### `ResolvedConfig#createVerifier(): Promise<Verifier>` + +Asynchronously hashes plugin and preset dependencies of the resolved config, as +well as config sources, and resolves the promise with a [`Verifier`](#verifier) +object. + +--- + +### `Verifier` + +Use `restoreVerifier()` or `ResolvedConfig#createVerifier()` to create this +object. + +#### `Verifier#cacheKeysForCurrentEnv(): {dependencies: string, sources: string}` + +Synchronously returns cache keys for the plugin and preset dependencies, and +config sources, that are applicable to the current environment. Use these values +to cache the result of `babel-core` transforms. + +#### `Verifier#verifyCurrentEnv(fixedHashes?: {sources: {[source: string]: string}}, cache?: Cache): Promise<{badDependency: true} | {missingSource: true} | {sourcesChanged: true} | {cacheKeys: {dependencies: string, sources: string}, dependenciesChanged: boolean, sourcesChanged: false, verifier: Verifier}>` + +Asynchronously verifies whether the config is still valid for the current +environment. + +Provide `fixedHashes` if the verifier was derived from a created config with a +fixed `hash` value. A `cache` object may also be provided. + +The promise is resolved with an object describing the verification result: + +* If the object has a `badDependency` property then a plugin or preset +dependency could not be hashed, presumably because it no longer exists. + +* If it has a `missingSource` property then a config source no longer exists. + +* If its `sourcesChanged` property is `true` then config sources have changed +and the config is no longer valid. + +* If its `dependenciesChanged` property is `true` then plugin or preset +dependencies have changed, but the config itself is still valid. The `verifier` +property holds a new `Verifier` object which takes the new dependencies into +account. The `cacheKeys` property contains the same result as calling +`Verifier#cacheKeysForCurrentEnv()` on the returned `verifier`. + +* If its `sourcesChanged` and `dependenciesChanged` properties are both `false` +then the config is valid and cache keys won't have changed. The `verifier` +property holds the same `Verifier` object. The `cacheKeys` properties contains +the same result as calling `Verifier#cacheKeysForCurrentEnv()`. + +#### `Verifier#toBuffer()` + +Serializes the verifier state into a `Buffer` object. Use `restoreVerifier()` +to deserialize. + +--- + +### Errors + +Error constructors are not publicly available, but errors can be identified by +their `name` property. + +#### `BadDependencyError` + +Used when a plugin or preset dependency couldn't be resolved. The corresponding +package or file name is available through the `source` property. There may be +another error with more details, available through the `parent` property. + +#### `ExtendsError` + +Used when an `extends` clause points at a non-existent file. The config file +that contains the clause is available through the `source` property. The clause +itself is available through the `clause` property. Has a `parent` property that +contains a `NoSourceFile` error. + +#### `InvalidFileError` + +Used when a config file is invalid. The file path is available through the +`source` property. + +#### `NoSourceFileError` + +Used when a file does not exist. The file path is available through the `source` +property. + +#### `ParseError` + +Used when a config file cannot be parsed (this is different from it being +invalid). The file path is available through the `source` property. The parsing +error is available through the `parent` property. + +[AVA]: https://ava.li/ +[Babel]: https://babeljs.io/ diff --git a/node_modules/hullabaloo-config-manager/index.js b/node_modules/hullabaloo-config-manager/index.js new file mode 100644 index 000000000..4e10228e7 --- /dev/null +++ b/node_modules/hullabaloo-config-manager/index.js @@ -0,0 +1,60 @@ +'use strict' + +const path = require('path') + +const cloneDeep = require('lodash.clonedeep') + +const collector = require('./lib/collector') +const currentEnv = require('./lib/currentEnv') +const ResolvedConfig = require('./lib/ResolvedConfig') +const Verifier = require('./lib/Verifier') + +function createConfig (options) { + if (!options || !options.options || !options.source) { + throw new TypeError("Expected 'options' and 'source' options") + } + if (typeof options.options !== 'object' || Array.isArray(options.options)) { + throw new TypeError("'options' must be an actual object") + } + + const source = options.source + const dir = options.dir || path.dirname(source) + const hash = options.hash || null + const json5 = options.json5 !== false + const babelOptions = cloneDeep(options.options) + + return new collector.Config(dir, null, hash, json5, babelOptions, source) +} +exports.createConfig = createConfig + +exports.currentEnv = currentEnv + +function fromConfig (baseConfig, options) { + options = options || {} + return collector.fromConfig(baseConfig, options.cache) + .then(chains => new ResolvedConfig(chains, options.cache)) +} +exports.fromConfig = fromConfig + +function fromDirectory (dir, options) { + options = options || {} + return collector.fromDirectory(dir, options.cache) + .then(chains => chains && new ResolvedConfig(chains, options.cache)) +} +exports.fromDirectory = fromDirectory + +function prepareCache () { + return { + dependencyHashes: new Map(), + fileExistence: new Map(), + files: new Map(), + pluginsAndPresets: new Map(), + sourceHashes: new Map() + } +} +exports.prepareCache = prepareCache + +function restoreVerifier (buffer) { + return Verifier.fromBuffer(buffer) +} +exports.restoreVerifier = restoreVerifier 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 diff --git a/node_modules/hullabaloo-config-manager/node_modules/.bin/json5 b/node_modules/hullabaloo-config-manager/node_modules/.bin/json5 new file mode 120000 index 000000000..519e49ec8 --- /dev/null +++ b/node_modules/hullabaloo-config-manager/node_modules/.bin/json5 @@ -0,0 +1 @@ +../../../json5/lib/cli.js
\ No newline at end of file diff --git a/node_modules/hullabaloo-config-manager/node_modules/md5-hex/browser.js b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/browser.js new file mode 100644 index 000000000..d6c2da0bf --- /dev/null +++ b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/browser.js @@ -0,0 +1,10 @@ +'use strict'; +const md5OMatic = require('md5-o-matic'); + +module.exports = input => { + if (Array.isArray(input)) { + input = input.join(''); + } + + return md5OMatic(input); +}; diff --git a/node_modules/hullabaloo-config-manager/node_modules/md5-hex/index.js b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/index.js new file mode 100644 index 000000000..82cfae306 --- /dev/null +++ b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/index.js @@ -0,0 +1,23 @@ +'use strict'; +const crypto = require('crypto'); + +module.exports = function (input) { + const hash = crypto.createHash('md5'); + + const update = buf => { + const inputEncoding = typeof buf === 'string' ? 'utf8' : undefined; + hash.update(buf, inputEncoding); + }; + + if (arguments.length > 1) { + throw new Error('Too many arguments. Try specifying an array.'); + } + + if (Array.isArray(input)) { + input.forEach(update); + } else { + update(input); + } + + return hash.digest('hex'); +}; diff --git a/node_modules/hullabaloo-config-manager/node_modules/md5-hex/license b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/license new file mode 100644 index 000000000..654d0bfe9 --- /dev/null +++ b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/hullabaloo-config-manager/node_modules/md5-hex/package.json b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/package.json new file mode 100644 index 000000000..a87ce154f --- /dev/null +++ b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/package.json @@ -0,0 +1,39 @@ +{ + "name": "md5-hex", + "version": "2.0.0", + "description": "Create a MD5 hash with hex encoding", + "license": "MIT", + "repository": "sindresorhus/md5-hex", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && ava" + }, + "files": [ + "index.js", + "browser.js" + ], + "keywords": [ + "hash", + "crypto", + "md5", + "hex", + "buffer", + "browser", + "browserify" + ], + "dependencies": { + "md5-o-matic": "^0.1.1" + }, + "devDependencies": { + "ava": "*", + "xo": "*" + }, + "browser": "browser.js" +} diff --git a/node_modules/hullabaloo-config-manager/node_modules/md5-hex/readme.md b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/readme.md new file mode 100644 index 000000000..630b31d0b --- /dev/null +++ b/node_modules/hullabaloo-config-manager/node_modules/md5-hex/readme.md @@ -0,0 +1,46 @@ +# md5-hex [](https://travis-ci.org/sindresorhus/md5-hex) + +> Create a MD5 hash with hex encoding + +*Please don't use MD5 hashes for anything sensitive!* + +Works in the browser too, when used with browserify/webpack. + +Checkout [`hasha`](https://github.com/sindresorhus/hasha) if you need something more flexible. + + +## Install + +``` +$ npm install --save md5-hex +``` + + +## Usage + +```js +const fs = require('fs'); +const md5Hex = require('md5-hex'); +const buffer = fs.readFileSync('unicorn.png'); + +md5Hex(buffer); +//=> '1abcb33beeb811dca15f0ac3e47b88d9' +``` + + +## API + +### md5Hex(input) + +#### input + +Type: `Buffer` `string` `Buffer[]` `string[]` + +Prefer buffers as they're faster to hash, but strings can be useful for small things. + +Pass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation. + + +## License + +MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/node_modules/hullabaloo-config-manager/package.json b/node_modules/hullabaloo-config-manager/package.json new file mode 100644 index 000000000..9fa461714 --- /dev/null +++ b/node_modules/hullabaloo-config-manager/package.json @@ -0,0 +1,67 @@ +{ + "name": "hullabaloo-config-manager", + "version": "1.0.1", + "description": "Manages complex Babel config chains, avoiding duplicated work and enabling effective caching", + "main": "index.js", + "files": [ + "index.js", + "lib" + ], + "engines": { + "node": ">=4.5" + }, + "scripts": { + "lint": "as-i-preach", + "test": "ava", + "posttest": "as-i-preach", + "coverage": "nyc npm test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/novemberborn/hullabaloo-config-manager.git" + }, + "keywords": [ + "babel" + ], + "author": "Mark Wubben (https://novemberborn.net/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/novemberborn/hullabaloo-config-manager/issues" + }, + "homepage": "https://github.com/novemberborn/hullabaloo-config-manager#readme", + "dependencies": { + "dot-prop": "^4.1.0", + "es6-error": "^4.0.2", + "graceful-fs": "^4.1.11", + "indent-string": "^3.1.0", + "json5": "^0.5.1", + "lodash.clonedeep": "^4.5.0", + "lodash.clonedeepwith": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.merge": "^4.6.0", + "md5-hex": "^2.0.0", + "package-hash": "^2.0.0", + "pkg-dir": "^1.0.0", + "resolve-from": "^2.0.0" + }, + "devDependencies": { + "@novemberborn/as-i-preach": "^9.0.0", + "ava": "^0.19.0", + "babel-core": "^6.22.1", + "coveralls": "^2.11.16", + "fs-extra": "^2.0.0", + "lodash.ismatch": "^4.4.0", + "nyc": "^10.1.2", + "proxyquire": "^1.7.11", + "testdouble": "^2.1.2", + "unique-temp-dir": "^1.0.0" + }, + "nyc": { + "reporter": [ + "html", + "lcov", + "text" + ] + }, + "standard-engine": "@novemberborn/as-i-preach" +} |