broccoli-funnel
Advanced tools
Comparing version 2.0.2 to 3.0.0
# master | ||
# 3.0.0 | ||
- [Breaking] Upgrading to latest broccoli-plugin (Breaking only because of node version drops) | ||
- [Breaking] Modernize code: Class syntax, Async await etc. | ||
- [Breaking] Drop Node 8, as that is EOL end of the month | ||
- [Breaking] Drop Unsupported Node's (4, 6, 7) and add newly supported nodes (10, 12) | ||
# 2.0.2 | ||
@@ -4,0 +11,0 @@ |
697
index.js
'use strict'; | ||
const fs = require('fs'); | ||
const fs = require('fs-extra'); | ||
const path = require('path-posix'); | ||
const mkdirp = require('mkdirp'); | ||
const walkSync = require('walk-sync'); | ||
@@ -13,3 +12,2 @@ const Minimatch = require('minimatch').Minimatch; | ||
const FSTree = require('fs-tree-diff'); | ||
const rimraf = require('rimraf'); | ||
const BlankObject = require('blank-object'); | ||
@@ -52,455 +50,456 @@ const heimdall = require('heimdalljs'); | ||
Funnel.prototype = Object.create(Plugin.prototype); | ||
Funnel.prototype.constructor = Funnel; | ||
function Funnel(inputNode, _options) { | ||
if (!(this instanceof Funnel)) { return new Funnel(inputNode, _options); } | ||
class Funnel extends Plugin { | ||
constructor(inputNode, options = {}) { | ||
super([inputNode], { | ||
annotation: options.annotation, | ||
persistentOutput: true, | ||
needsCache: false, | ||
}); | ||
let options = _options || {}; | ||
Plugin.call(this, [inputNode], { | ||
annotation: options.annotation, | ||
persistentOutput: true, | ||
needsCache: false, | ||
}); | ||
this._includeFileCache = makeDictionary(); | ||
this._destinationPathCache = makeDictionary(); | ||
this._currentTree = new FSTree(); | ||
this._isRebuild = false; | ||
this._includeFileCache = makeDictionary(); | ||
this._destinationPathCache = makeDictionary(); | ||
this._currentTree = new FSTree(); | ||
this._isRebuild = false; | ||
let keys = Object.keys(options || {}); | ||
for (let i = 0, l = keys.length; i < l; i++) { | ||
let key = keys[i]; | ||
this[key] = options[key]; | ||
} | ||
let keys = Object.keys(options || {}); | ||
for (let i = 0, l = keys.length; i < l; i++) { | ||
let key = keys[i]; | ||
this[key] = options[key]; | ||
} | ||
this.destDir = this.destDir || '/'; | ||
this.count = 0; | ||
this.destDir = this.destDir || '/'; | ||
this.count = 0; | ||
if (this.files && typeof this.files === 'function') { | ||
// Save dynamic files func as a different variable and let the rest of the code | ||
// still assume that this.files is always an array. | ||
this._dynamicFilesFunc = this.files; | ||
delete this.files; | ||
} else if (this.files && !Array.isArray(this.files)) { | ||
throw new Error('Invalid files option, it must be an array or function (that returns an array).'); | ||
} | ||
if (this.files && typeof this.files === 'function') { | ||
// Save dynamic files func as a different variable and let the rest of the code | ||
// still assume that this.files is always an array. | ||
this._dynamicFilesFunc = this.files; | ||
delete this.files; | ||
} else if (this.files && !Array.isArray(this.files)) { | ||
throw new Error('Invalid files option, it must be an array or function (that returns an array).'); | ||
} | ||
if ((this.files || this._dynamicFilesFunc) && (this.include || this.exclude)) { | ||
throw new Error('Cannot pass files option (array or function) and a include/exlude filter. You can have one or the other'); | ||
} | ||
if ((this.files || this._dynamicFilesFunc) && (this.include || this.exclude)) { | ||
throw new Error('Cannot pass files option (array or function) and a include/exlude filter. You can have one or the other'); | ||
} | ||
if (this.files) { | ||
if (this.files.filter(isNotAPattern).length !== this.files.length) { | ||
console.warn('broccoli-funnel does not support `files:` option with globs, please use `include:` instead'); | ||
this.include = this.files; | ||
this.files = undefined; | ||
} | ||
} | ||
if (this.files) { | ||
if (this.files.filter(isNotAPattern).length !== this.files.length) { | ||
console.warn('broccoli-funnel does not support `files:` option with globs, please use `include:` instead'); | ||
this.include = this.files; | ||
this.files = undefined; | ||
} | ||
this._setupFilter('include'); | ||
this._setupFilter('exclude'); | ||
this._matchedWalk = this.canMatchWalk(); | ||
this._instantiatedStack = (new Error()).stack; | ||
this._buildStart = undefined; | ||
} | ||
this._setupFilter('include'); | ||
this._setupFilter('exclude'); | ||
canMatchWalk() { | ||
let include = this.include; | ||
let exclude = this.exclude; | ||
this._matchedWalk = this.canMatchWalk(); | ||
if (!include && !exclude) { return false; } | ||
this._instantiatedStack = (new Error()).stack; | ||
this._buildStart = undefined; | ||
} | ||
let includeIsOk = true; | ||
function isMinimatch(x) { | ||
return x instanceof Minimatch; | ||
} | ||
Funnel.prototype.canMatchWalk = function() { | ||
let include = this.include; | ||
let exclude = this.exclude; | ||
if (include) { | ||
includeIsOk = include.filter(isMinimatch).length === include.length; | ||
} | ||
if (!include && !exclude) { return false; } | ||
let excludeIsOk = true; | ||
let includeIsOk = true; | ||
if (exclude) { | ||
excludeIsOk = exclude.filter(isMinimatch).length === exclude.length; | ||
} | ||
if (include) { | ||
includeIsOk = include.filter(isMinimatch).length === include.length; | ||
return includeIsOk && excludeIsOk; | ||
} | ||
let excludeIsOk = true; | ||
_debugName() { | ||
return this.description || this._annotation || this.name || this.constructor.name; | ||
} | ||
if (exclude) { | ||
excludeIsOk = exclude.filter(isMinimatch).length === exclude.length; | ||
_debug() { | ||
debug(`broccoli-funnel:${this._debugName()}`).apply(null, arguments); | ||
} | ||
return includeIsOk && excludeIsOk; | ||
}; | ||
_setupFilter(type) { | ||
if (!this[type]) { | ||
return; | ||
} | ||
Funnel.prototype._debugName = function() { | ||
return this.description || this._annotation || this.name || this.constructor.name; | ||
}; | ||
if (!Array.isArray(this[type])) { | ||
throw new Error(`Invalid ${type} option, it must be an array. You specified \`${typeof this[type]}\`.`); | ||
} | ||
Funnel.prototype._debug = function() { | ||
debug(`broccoli-funnel:${this._debugName()}`).apply(null, arguments); | ||
}; | ||
// Clone the filter array so we are not mutating an external variable | ||
let filters = this[type] = this[type].slice(0); | ||
Funnel.prototype._setupFilter = function(type) { | ||
if (!this[type]) { | ||
return; | ||
for (let i = 0, l = filters.length; i < l; i++) { | ||
filters[i] = this._processPattern(filters[i]); | ||
} | ||
} | ||
if (!Array.isArray(this[type])) { | ||
throw new Error(`Invalid ${type} option, it must be an array. You specified \`${typeof this[type]}\`.`); | ||
} | ||
_processPattern(pattern) { | ||
if (pattern instanceof RegExp) { | ||
return pattern; | ||
} | ||
// Clone the filter array so we are not mutating an external variable | ||
let filters = this[type] = this[type].slice(0); | ||
let type = typeof pattern; | ||
for (let i = 0, l = filters.length; i < l; i++) { | ||
filters[i] = this._processPattern(filters[i]); | ||
} | ||
}; | ||
if (type === 'string') { | ||
return new Minimatch(pattern); | ||
} | ||
Funnel.prototype._processPattern = function(pattern) { | ||
if (pattern instanceof RegExp) { | ||
return pattern; | ||
} | ||
if (type === 'function') { | ||
return pattern; | ||
} | ||
let type = typeof pattern; | ||
if (type === 'string') { | ||
return new Minimatch(pattern); | ||
throw new Error(`include/exclude patterns can be a RegExp, glob string, or function. You supplied \`${typeof pattern}\`.`); | ||
} | ||
if (type === 'function') { | ||
return pattern; | ||
shouldLinkRoots() { | ||
return !this.files && !this.include && !this.exclude && !this.getDestinationPath; | ||
} | ||
throw new Error(`include/exclude patterns can be a RegExp, glob string, or function. You supplied \`${typeof pattern}\`.`); | ||
}; | ||
build() { | ||
this._buildStart = new Date(); | ||
this.destPath = path.join(this.outputPath, this.destDir); | ||
Funnel.prototype.shouldLinkRoots = function() { | ||
return !this.files && !this.include && !this.exclude && !this.getDestinationPath; | ||
}; | ||
if (this.destPath[this.destPath.length - 1] === '/') { | ||
this.destPath = this.destPath.slice(0, -1); | ||
} | ||
Funnel.prototype.build = function() { | ||
this._buildStart = new Date(); | ||
this.destPath = path.join(this.outputPath, this.destDir); | ||
let inputPath = this.inputPaths[0]; | ||
if (this.srcDir) { | ||
inputPath = path.join(inputPath, this.srcDir); | ||
} | ||
if (this.destPath[this.destPath.length - 1] === '/') { | ||
this.destPath = this.destPath.slice(0, -1); | ||
} | ||
if (this._dynamicFilesFunc) { | ||
this.lastFiles = this.files; | ||
this.files = this._dynamicFilesFunc() || []; | ||
let inputPath = this.inputPaths[0]; | ||
if (this.srcDir) { | ||
inputPath = path.join(inputPath, this.srcDir); | ||
} | ||
if (this._dynamicFilesFunc) { | ||
this.lastFiles = this.files; | ||
this.files = this._dynamicFilesFunc() || []; | ||
// Blow away the include cache if the list of files is new | ||
if (this.lastFiles !== undefined && !arrayEqual(this.lastFiles, this.files)) { | ||
this._includeFileCache = makeDictionary(); | ||
// Blow away the include cache if the list of files is new | ||
if (this.lastFiles !== undefined && !arrayEqual(this.lastFiles, this.files)) { | ||
this._includeFileCache = makeDictionary(); | ||
} | ||
} | ||
} | ||
let inputPathExists = fs.existsSync(inputPath); | ||
let inputPathExists = fs.existsSync(inputPath); | ||
let linkedRoots = false; | ||
if (this.shouldLinkRoots()) { | ||
linkedRoots = true; | ||
let linkedRoots = false; | ||
if (this.shouldLinkRoots()) { | ||
linkedRoots = true; | ||
/** | ||
* We want to link the roots of these directories, but there are a few | ||
* edge cases we must account for. | ||
* | ||
* 1. It's possible that the original input doesn't actually exist. | ||
* 2. It's possible that the output symlink has been broken. | ||
* 3. We need slightly different behavior on rebuilds. | ||
* | ||
* Behavior has been modified to always having an `else` clause so that | ||
* the code is forced to account for all scenarios. Not accounting for | ||
* all scenarios made it possible for initial builds to succeed without | ||
* specifying `this.allowEmpty`. | ||
*/ | ||
/** | ||
* We want to link the roots of these directories, but there are a few | ||
* edge cases we must account for. | ||
* | ||
* 1. It's possible that the original input doesn't actually exist. | ||
* 2. It's possible that the output symlink has been broken. | ||
* 3. We need slightly different behavior on rebuilds. | ||
* | ||
* Behavior has been modified to always having an `else` clause so that | ||
* the code is forced to account for all scenarios. Not accounting for | ||
* all scenarios made it possible for initial builds to succeed without | ||
* specifying `this.allowEmpty`. | ||
*/ | ||
// This is specifically looking for broken symlinks. | ||
let outputPathExists = fs.existsSync(this.outputPath); | ||
// This is specifically looking for broken symlinks. | ||
let outputPathExists = fs.existsSync(this.outputPath); | ||
// Doesn't count as a rebuild if there's not an existing outputPath. | ||
this._isRebuild = this._isRebuild && outputPathExists; | ||
// Doesn't count as a rebuild if there's not an existing outputPath. | ||
this._isRebuild = this._isRebuild && outputPathExists; | ||
/*eslint-disable no-lonely-if*/ | ||
if (this._isRebuild) { | ||
if (inputPathExists) { | ||
// Already works because of symlinks. Do nothing. | ||
} else if (!inputPathExists && this.allowEmpty) { | ||
// Make sure we're safely using a new outputPath since we were previously symlinked: | ||
rimraf.sync(this.outputPath); | ||
// Create a new empty folder: | ||
mkdirp.sync(this.destPath); | ||
} else { // this._isRebuild && !inputPathExists && !this.allowEmpty | ||
// Need to remove it on the rebuild. | ||
// Can blindly remove a symlink if path exists. | ||
rimraf.sync(this.outputPath); | ||
/*eslint-disable no-lonely-if*/ | ||
if (this._isRebuild) { | ||
if (inputPathExists) { | ||
// Already works because of symlinks. Do nothing. | ||
} else if (!inputPathExists && this.allowEmpty) { | ||
// Make sure we're safely using a new outputPath since we were previously symlinked: | ||
fs.removeSync(this.outputPath); | ||
// Create a new empty folder: | ||
fs.mkdirSync(this.destPath, { recursive: true }); | ||
} else { // this._isRebuild && !inputPathExists && !this.allowEmpty | ||
// Need to remove it on the rebuild. | ||
// Can blindly remove a symlink if path exists. | ||
fs.removeSync(this.outputPath); | ||
} | ||
} else { // Not a rebuild. | ||
if (inputPathExists) { | ||
// We don't want to use the generated-for-us folder. | ||
// Instead let's remove it: | ||
fs.removeSync(this.outputPath); | ||
// And then symlinkOrCopy over top of it: | ||
this._copy(inputPath, this.destPath); | ||
} else if (!inputPathExists && this.allowEmpty) { | ||
// Can't symlink nothing, so make an empty folder at `destPath`: | ||
fs.mkdirSync(this.destPath, { recursive: true }); | ||
} else { // !this._isRebuild && !inputPathExists && !this.allowEmpty | ||
throw new Error(`You specified a \`"srcDir": ${this.srcDir}\` which does not exist and did not specify \`"allowEmpty": true\`.`); | ||
} | ||
} | ||
} else { // Not a rebuild. | ||
if (inputPathExists) { | ||
// We don't want to use the generated-for-us folder. | ||
// Instead let's remove it: | ||
rimraf.sync(this.outputPath); | ||
// And then symlinkOrCopy over top of it: | ||
this._copy(inputPath, this.destPath); | ||
} else if (!inputPathExists && this.allowEmpty) { | ||
// Can't symlink nothing, so make an empty folder at `destPath`: | ||
mkdirp.sync(this.destPath); | ||
} else { // !this._isRebuild && !inputPathExists && !this.allowEmpty | ||
throw new Error(`You specified a \`"srcDir": ${this.srcDir}\` which does not exist and did not specify \`"allowEmpty": true\`.`); | ||
} | ||
/*eslint-enable no-lonely-if*/ | ||
this._isRebuild = true; | ||
} else if (inputPathExists) { | ||
this.processFilters(inputPath); | ||
} else if (!this.allowEmpty) { | ||
throw new Error(`You specified a \`"srcDir": ${this.srcDir}\` which does not exist and did not specify \`"allowEmpty": true\`.`); | ||
} else { // !inputPathExists && this.allowEmpty | ||
// Just make an empty folder so that any downstream consumers who don't know | ||
// to ignore this on `allowEmpty` don't get trolled. | ||
fs.mkdirSync(this.destPath, { recursive: true }); | ||
} | ||
/*eslint-enable no-lonely-if*/ | ||
this._isRebuild = true; | ||
} else if (inputPathExists) { | ||
this.processFilters(inputPath); | ||
} else if (!this.allowEmpty) { | ||
throw new Error(`You specified a \`"srcDir": ${this.srcDir}\` which does not exist and did not specify \`"allowEmpty": true\`.`); | ||
} else { // !inputPathExists && this.allowEmpty | ||
// Just make an empty folder so that any downstream consumers who don't know | ||
// to ignore this on `allowEmpty` don't get trolled. | ||
mkdirp(this.destPath); | ||
this._debug('build, %o', { | ||
in: `${new Date() - this._buildStart}ms`, | ||
linkedRoots, | ||
inputPath, | ||
destPath: this.destPath, | ||
}); | ||
} | ||
this._debug('build, %o', { | ||
in: `${new Date() - this._buildStart}ms`, | ||
linkedRoots, | ||
inputPath, | ||
destPath: this.destPath, | ||
}); | ||
}; | ||
_processEntries(entries) { | ||
return entries.filter(function(entry) { | ||
// support the second set of filters walk-sync does not support | ||
// * regexp | ||
// * excludes | ||
return this.includeFile(entry.relativePath); | ||
}, this).map(function(entry) { | ||
function ensureRelative(string) { | ||
if (string.charAt(0) === '/') { | ||
return string.substring(1); | ||
} | ||
return string; | ||
} | ||
let relativePath = entry.relativePath; | ||
Funnel.prototype._processEntries = function(entries) { | ||
return entries.filter(function(entry) { | ||
// support the second set of filters walk-sync does not support | ||
// * regexp | ||
// * excludes | ||
return this.includeFile(entry.relativePath); | ||
}, this).map(function(entry) { | ||
entry.relativePath = this.lookupDestinationPath(relativePath); | ||
let relativePath = entry.relativePath; | ||
this.outputToInputMappings[entry.relativePath] = relativePath; | ||
entry.relativePath = this.lookupDestinationPath(relativePath); | ||
return entry; | ||
}, this); | ||
} | ||
this.outputToInputMappings[entry.relativePath] = relativePath; | ||
_processPaths(paths) { | ||
return paths. | ||
slice(0). | ||
filter(this.includeFile, this). | ||
map(function(relativePath) { | ||
let output = this.lookupDestinationPath(relativePath); | ||
this.outputToInputMappings[output] = relativePath; | ||
return output; | ||
}, this); | ||
} | ||
return entry; | ||
}, this); | ||
}; | ||
processFilters(inputPath) { | ||
let nextTree; | ||
Funnel.prototype._processPaths = function(paths) { | ||
return paths. | ||
slice(0). | ||
filter(this.includeFile, this). | ||
map(function(relativePath) { | ||
let output = this.lookupDestinationPath(relativePath); | ||
this.outputToInputMappings[output] = relativePath; | ||
return output; | ||
}, this); | ||
}; | ||
let instrumentation = heimdall.start('derivePatches'); | ||
let entries; | ||
Funnel.prototype.processFilters = function(inputPath) { | ||
let nextTree; | ||
this.outputToInputMappings = {}; // we allow users to rename files | ||
let instrumentation = heimdall.start('derivePatches'); | ||
let entries; | ||
if (this.files && !this.exclude && !this.include) { | ||
entries = this._processPaths(this.files); | ||
// clone to be compatible with walkSync | ||
nextTree = FSTree.fromPaths(entries, { sortAndExpand: true }); | ||
} else { | ||
this.outputToInputMappings = {}; // we allow users to rename files | ||
if (this._matchedWalk) { | ||
entries = walkSync.entries(inputPath, { globs: this.include, ignore: this.exclude }); | ||
} else { | ||
entries = walkSync.entries(inputPath); | ||
} | ||
if (this.files && !this.exclude && !this.include) { | ||
entries = this._processPaths(this.files); | ||
// clone to be compatible with walkSync | ||
nextTree = FSTree.fromPaths(entries, { sortAndExpand: true }); | ||
} else { | ||
if (this._matchedWalk) { | ||
entries = walkSync.entries(inputPath, { globs: this.include, ignore: this.exclude }); | ||
} else { | ||
entries = walkSync.entries(inputPath); | ||
entries = this._processEntries(entries); | ||
nextTree = FSTree.fromEntries(entries, { sortAndExpand: true }); | ||
} | ||
entries = this._processEntries(entries); | ||
nextTree = FSTree.fromEntries(entries, { sortAndExpand: true }); | ||
} | ||
let patches = this._currentTree.calculatePatch(nextTree); | ||
let patches = this._currentTree.calculatePatch(nextTree); | ||
this._currentTree = nextTree; | ||
this._currentTree = nextTree; | ||
instrumentation.stats.patches = patches.length; | ||
instrumentation.stats.entries = entries.length; | ||
instrumentation.stats.patches = patches.length; | ||
instrumentation.stats.entries = entries.length; | ||
let outputPath = this.outputPath; | ||
let outputPath = this.outputPath; | ||
instrumentation.stop(); | ||
instrumentation.stop(); | ||
instrumentation = heimdall.start('applyPatch', ApplyPatchesSchema); | ||
instrumentation = heimdall.start('applyPatch', ApplyPatchesSchema); | ||
patches.forEach(function(entry) { | ||
this._applyPatch(entry, inputPath, outputPath, instrumentation.stats); | ||
}, this); | ||
patches.forEach(function(entry) { | ||
this._applyPatch(entry, inputPath, outputPath, instrumentation.stats); | ||
}, this); | ||
instrumentation.stop(); | ||
} | ||
instrumentation.stop(); | ||
}; | ||
_applyPatch(entry, inputPath, _outputPath, stats) { | ||
let outputToInput = this.outputToInputMappings; | ||
let operation = entry[0]; | ||
let outputRelative = entry[1]; | ||
Funnel.prototype._applyPatch = function applyPatch(entry, inputPath, _outputPath, stats) { | ||
let outputToInput = this.outputToInputMappings; | ||
let operation = entry[0]; | ||
let outputRelative = entry[1]; | ||
if (!outputRelative) { | ||
// broccoli itself maintains the roots, we can skip any operation on them | ||
return; | ||
} | ||
if (!outputRelative) { | ||
// broccoli itself maintains the roots, we can skip any operation on them | ||
return; | ||
} | ||
let outputPath = `${_outputPath}/${outputRelative}`; | ||
let outputPath = `${_outputPath}/${outputRelative}`; | ||
this._debug('%s %s', operation, outputPath); | ||
this._debug('%s %s', operation, outputPath); | ||
switch (operation) { | ||
case 'unlink' : | ||
stats.unlink++; | ||
switch (operation) { | ||
case 'unlink' : | ||
stats.unlink++; | ||
fs.unlinkSync(outputPath); | ||
break; | ||
case 'rmdir' : | ||
stats.rmdir++; | ||
fs.rmdirSync(outputPath); | ||
break; | ||
case 'mkdir' : | ||
stats.mkdir++; | ||
fs.mkdirSync(outputPath); | ||
break; | ||
case 'change': | ||
stats.change++; | ||
/* falls through */ | ||
case 'create': { | ||
if (operation === 'create') { | ||
stats.create++; | ||
} | ||
fs.unlinkSync(outputPath); | ||
break; | ||
case 'rmdir' : | ||
stats.rmdir++; | ||
fs.rmdirSync(outputPath); | ||
break; | ||
case 'mkdir' : | ||
stats.mkdir++; | ||
fs.mkdirSync(outputPath); | ||
break; | ||
case 'change': | ||
stats.change++; | ||
/* falls through */ | ||
case 'create': { | ||
if (operation === 'create') { | ||
stats.create++; | ||
let relativePath = outputToInput[outputRelative]; | ||
if (relativePath === undefined) { | ||
relativePath = outputToInput[`/${outputRelative}`]; | ||
} | ||
this.processFile(`${inputPath}/${relativePath}`, outputPath, relativePath); | ||
break; | ||
} | ||
let relativePath = outputToInput[outputRelative]; | ||
if (relativePath === undefined) { | ||
relativePath = outputToInput[`/${outputRelative}`]; | ||
} | ||
this.processFile(`${inputPath}/${relativePath}`, outputPath, relativePath); | ||
break; | ||
default: throw new Error(`Unknown operation: ${operation}`); | ||
} | ||
default: throw new Error(`Unknown operation: ${operation}`); | ||
} | ||
}; | ||
Funnel.prototype.lookupDestinationPath = function(relativePath) { | ||
if (this._destinationPathCache[relativePath] !== undefined) { | ||
return this._destinationPathCache[relativePath]; | ||
} | ||
lookupDestinationPath(relativePath) { | ||
if (this._destinationPathCache[relativePath] !== undefined) { | ||
return this._destinationPathCache[relativePath]; | ||
} | ||
// the destDir is absolute to prevent '..' above the output dir | ||
if (this.getDestinationPath) { | ||
return this._destinationPathCache[relativePath] = ensureRelative(path.join(this.destDir, this.getDestinationPath(relativePath))); | ||
// the destDir is absolute to prevent '..' above the output dir | ||
if (this.getDestinationPath) { | ||
return this._destinationPathCache[relativePath] = ensureRelative(path.join(this.destDir, this.getDestinationPath(relativePath))); | ||
} | ||
return this._destinationPathCache[relativePath] = ensureRelative(path.join(this.destDir, relativePath)); | ||
} | ||
return this._destinationPathCache[relativePath] = ensureRelative(path.join(this.destDir, relativePath)); | ||
}; | ||
includeFile(relativePath) { | ||
let includeFileCache = this._includeFileCache; | ||
Funnel.prototype.includeFile = function(relativePath) { | ||
let includeFileCache = this._includeFileCache; | ||
if (includeFileCache[relativePath] !== undefined) { | ||
return includeFileCache[relativePath]; | ||
} | ||
if (includeFileCache[relativePath] !== undefined) { | ||
return includeFileCache[relativePath]; | ||
} | ||
// do not include directories, only files | ||
if (relativePath[relativePath.length - 1] === '/') { | ||
return includeFileCache[relativePath] = false; | ||
} | ||
// do not include directories, only files | ||
if (relativePath[relativePath.length - 1] === '/') { | ||
return includeFileCache[relativePath] = false; | ||
} | ||
let i, l, pattern; | ||
let i, l, pattern; | ||
// Check for specific files listing | ||
if (this.files) { | ||
return includeFileCache[relativePath] = this.files.indexOf(relativePath) > -1; | ||
} | ||
// Check for specific files listing | ||
if (this.files) { | ||
return includeFileCache[relativePath] = this.files.indexOf(relativePath) > -1; | ||
} | ||
if (this._matchedWalk) { | ||
return true; | ||
} | ||
// Check exclude patterns | ||
if (this.exclude) { | ||
for (i = 0, l = this.exclude.length; i < l; i++) { | ||
// An exclude pattern that returns true should be ignored | ||
pattern = this.exclude[i]; | ||
if (this._matchedWalk) { | ||
return true; | ||
} | ||
// Check exclude patterns | ||
if (this.exclude) { | ||
for (i = 0, l = this.exclude.length; i < l; i++) { | ||
// An exclude pattern that returns true should be ignored | ||
pattern = this.exclude[i]; | ||
if (this._matchesPattern(pattern, relativePath)) { | ||
return includeFileCache[relativePath] = false; | ||
if (this._matchesPattern(pattern, relativePath)) { | ||
return includeFileCache[relativePath] = false; | ||
} | ||
} | ||
} | ||
} | ||
// Check include patterns | ||
if (this.include && this.include.length > 0) { | ||
for (i = 0, l = this.include.length; i < l; i++) { | ||
// An include pattern that returns true (and wasn't excluded at all) | ||
// should _not_ be ignored | ||
pattern = this.include[i]; | ||
// Check include patterns | ||
if (this.include && this.include.length > 0) { | ||
for (i = 0, l = this.include.length; i < l; i++) { | ||
// An include pattern that returns true (and wasn't excluded at all) | ||
// should _not_ be ignored | ||
pattern = this.include[i]; | ||
if (this._matchesPattern(pattern, relativePath)) { | ||
return includeFileCache[relativePath] = true; | ||
if (this._matchesPattern(pattern, relativePath)) { | ||
return includeFileCache[relativePath] = true; | ||
} | ||
} | ||
// If no include patterns were matched, ignore this file. | ||
return includeFileCache[relativePath] = false; | ||
} | ||
// If no include patterns were matched, ignore this file. | ||
return includeFileCache[relativePath] = false; | ||
// Otherwise, don't ignore this file | ||
return includeFileCache[relativePath] = true; | ||
} | ||
_matchesPattern(pattern, relativePath) { | ||
if (pattern instanceof RegExp) { | ||
return pattern.test(relativePath); | ||
} else if (pattern instanceof Minimatch) { | ||
return pattern.match(relativePath); | ||
} else if (typeof pattern === 'function') { | ||
return pattern(relativePath); | ||
} | ||
// Otherwise, don't ignore this file | ||
return includeFileCache[relativePath] = true; | ||
}; | ||
throw new Error(`Pattern \`${pattern}\` was not a RegExp, Glob, or Function.`); | ||
} | ||
Funnel.prototype._matchesPattern = function(pattern, relativePath) { | ||
if (pattern instanceof RegExp) { | ||
return pattern.test(relativePath); | ||
} else if (pattern instanceof Minimatch) { | ||
return pattern.match(relativePath); | ||
} else if (typeof pattern === 'function') { | ||
return pattern(relativePath); | ||
processFile(sourcePath, destPath /*, relativePath */) { | ||
this._copy(sourcePath, destPath); | ||
} | ||
throw new Error(`Pattern \`${pattern}\` was not a RegExp, Glob, or Function.`); | ||
}; | ||
_copy(sourcePath, destPath) { | ||
let destDir = path.dirname(destPath); | ||
Funnel.prototype.processFile = function(sourcePath, destPath /*, relativePath */) { | ||
this._copy(sourcePath, destPath); | ||
}; | ||
Funnel.prototype._copy = function(sourcePath, destPath) { | ||
let destDir = path.dirname(destPath); | ||
try { | ||
symlinkOrCopy.sync(sourcePath, destPath); | ||
} catch (e) { | ||
if (!fs.existsSync(destDir)) { | ||
mkdirp.sync(destDir); | ||
} | ||
try { | ||
fs.unlinkSync(destPath); | ||
symlinkOrCopy.sync(sourcePath, destPath); | ||
} catch (e) { | ||
// swallow the error | ||
if (!fs.existsSync(destDir)) { | ||
fs.mkdirSync(destDir, { recursive: true }); | ||
} | ||
try { | ||
fs.unlinkSync(destPath); | ||
} catch (e) { | ||
// swallow the error | ||
} | ||
symlinkOrCopy.sync(sourcePath, destPath); | ||
} | ||
symlinkOrCopy.sync(sourcePath, destPath); | ||
} | ||
} | ||
function isMinimatch(x) { | ||
return x instanceof Minimatch; | ||
} | ||
function ensureRelative(string) { | ||
if (string.charAt(0) === '/') { | ||
return string.substring(1); | ||
} | ||
return string; | ||
} | ||
module.exports = function funnel(...params) { | ||
return new Funnel(...params); | ||
}; | ||
module.exports = Funnel; | ||
module.exports.Funnel = Funnel; |
{ | ||
"name": "broccoli-funnel", | ||
"version": "2.0.2", | ||
"version": "3.0.0", | ||
"description": "Broccoli plugin that allows you to filter files selected from an input node down based on regular expressions.", | ||
@@ -16,7 +16,8 @@ "main": "index.js", | ||
"engines": { | ||
"node": "^4.5 || 6.* || >= 7.*" | ||
"node": "10.* || >= 12.*" | ||
}, | ||
"scripts": { | ||
"test": "mocha tests/", | ||
"test:debug": "mocha debug tests/" | ||
"test:debug": "mocha debug tests/", | ||
"test:debugger": "mocha --inspect-brk tests/" | ||
}, | ||
@@ -30,11 +31,10 @@ "keywords": [ | ||
"blank-object": "^1.0.1", | ||
"broccoli-plugin": "^1.3.0", | ||
"debug": "^2.2.0", | ||
"broccoli-plugin": "^3.0.0", | ||
"debug": "^4.1.1", | ||
"fast-ordered-set": "^1.0.0", | ||
"fs-tree-diff": "^0.5.3", | ||
"fs-tree-diff": "^2.0.1", | ||
"heimdalljs": "^0.2.0", | ||
"minimatch": "^3.0.0", | ||
"mkdirp": "^0.5.0", | ||
"path-posix": "^1.0.0", | ||
"rimraf": "^2.4.3", | ||
"rimraf": "^3.0.0", | ||
"symlink-or-copy": "^1.0.0", | ||
@@ -44,12 +44,10 @@ "walk-sync": "^0.3.1" | ||
"devDependencies": { | ||
"broccoli-builder": "^0.18.0", | ||
"chai": "^3.2.0", | ||
"eslint-plugin-mocha": "^4.11.0", | ||
"fixturify": "^0.3.0", | ||
"fs-extra": "^0.30.0", | ||
"mocha": "~3.0.2", | ||
"mocha-eslint": "^4.1.0", | ||
"rimraf": "^2.3.2", | ||
"rsvp": "^3.3.1" | ||
"broccoli-test-helper": "^2.0.0", | ||
"chai": "^4.2.0", | ||
"eslint-plugin-mocha": "^6.1.1", | ||
"fs-extra": "^8.1.0", | ||
"mocha": "~6.2.1", | ||
"mocha-eslint": "^5.0.0", | ||
"rimraf": "^3.0.0" | ||
} | ||
} |
@@ -13,3 +13,3 @@ # Broccoli Funnel | ||
### `new Funnel(inputNode, options)` | ||
### `funnel(inputNode, options)` | ||
@@ -42,4 +42,4 @@ `inputNode` *{Single node}* | ||
```javascript | ||
var Funnel = require('broccoli-funnel'); | ||
var cssFiles = new Funnel('src/css'); | ||
const funnel = require('broccoli-funnel'); | ||
const cssFiles = funnel('src/css'); | ||
@@ -86,7 +86,7 @@ /* | ||
```javascript | ||
var Funnel = require('broccoli-funnel'); | ||
var MergeTrees = require('broccoli-merge-trees'); | ||
const funnel = require('broccoli-funnel'); | ||
const merge = require('broccoli-merge-trees'); | ||
// root of our source files | ||
var projectFiles = 'src'; | ||
const projectFiles = 'src'; | ||
@@ -99,3 +99,3 @@ /* get a new node of only files in the 'src/css' directory | ||
*/ | ||
var cssFiles = new Funnel(projectFiles, { | ||
const cssFiles = funnel(projectFiles, { | ||
srcDir: 'css' | ||
@@ -110,3 +110,3 @@ }); | ||
*/ | ||
var imageFiles = new Funnel(projectFiles, { | ||
const imageFiles = funnel(projectFiles, { | ||
srcDir: 'icons' | ||
@@ -116,3 +116,3 @@ }); | ||
module.exports = new MergeTrees([cssFiles, imageFiles]); | ||
module.exports = merge([cssFiles, imageFiles]); | ||
``` | ||
@@ -148,5 +148,5 @@ | ||
```javascript | ||
var Funnel = require('broccoli-funnel'); | ||
const funnel = require('broccoli-funnel'); | ||
var cssFiles = new Funnel('src/css', { | ||
const cssFiles = funnel('src/css', { | ||
destDir: 'build' | ||
@@ -204,8 +204,8 @@ }); | ||
```javascript | ||
var Funnel = require('broccoli-funnel'); | ||
const funnel = require('broccoli-funnel'); | ||
// finds all files that match /todo/ and moves them | ||
// the destDir | ||
var todoRelatedFiles = new Funnel('src', { | ||
include: ['todo/**/*'] | ||
const todoRelatedFiles = funnel('src', { | ||
include: ['**/todo*'] | ||
}); | ||
@@ -258,7 +258,7 @@ | ||
```javascript | ||
var Funnel = require('broccoli-funnel'); | ||
const funnel = require('broccoli-funnel'); | ||
// finds all files in 'src' EXCEPT `todo.js` in any directory | ||
// or sub-directory and adds them to a node. | ||
var nobodyLikesTodosAnyway = new Funnel('src', { | ||
const nobodyLikesTodosAnyway = funnel('src', { | ||
exclude: ['**/todo.js'] | ||
@@ -313,6 +313,6 @@ }); | ||
```javascript | ||
var Funnel = require('broccoli-funnel'); | ||
const funnel = require('broccoli-funnel'); | ||
// finds these specific files and moves them to the destDir | ||
var someFiles = new Funnel('src', { | ||
const someFiles = funnel('src', { | ||
files: ['css/reset.css', 'icons/check-mark.png'] | ||
@@ -350,3 +350,3 @@ }); | ||
```javascript | ||
var node = new Funnel('packages/ember-metal/lib', { | ||
const node = funnel('packages/ember-metal/lib', { | ||
destDir: 'ember-metal', | ||
@@ -364,2 +364,11 @@ | ||
## Extending Funnel | ||
If you desire to extend funnel follow the below snippet | ||
```js | ||
const { Funnel } = require('broccoli-funnel'); | ||
class CustomFunnel extends Funnel { | ||
// cutstom implementation | ||
} | ||
``` | ||
## ZOMG!!! TESTS?!?!!? | ||
@@ -366,0 +375,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
27549
12
7
376
406
+ Added@types/minimatch@3.0.5(transitive)
+ Added@types/symlink-or-copy@1.2.2(transitive)
+ Addedbroccoli-node-api@1.7.0(transitive)
+ Addedbroccoli-node-info@2.2.0(transitive)
+ Addedbroccoli-output-wrapper@2.0.0(transitive)
+ Addedbroccoli-plugin@3.1.0(transitive)
+ Addeddebug@4.3.7(transitive)
+ Addedfs-extra@8.1.0(transitive)
+ Addedfs-merger@3.2.1(transitive)
+ Addedfs-tree-diff@2.0.1(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedjsonfile@4.0.0(transitive)
+ Addedmatcher-collection@2.0.1(transitive)
+ Addedms@2.1.3(transitive)
+ Addedrimraf@3.0.2(transitive)
+ Addeduniversalify@0.1.2(transitive)
+ Addedwalk-sync@2.2.0(transitive)
- Removedmkdirp@^0.5.0
- Removedbroccoli-plugin@1.3.1(transitive)
- Removedfs-tree-diff@0.5.9(transitive)
- Removedminimist@1.2.8(transitive)
- Removedmkdirp@0.5.6(transitive)
Updatedbroccoli-plugin@^3.0.0
Updateddebug@^4.1.1
Updatedfs-tree-diff@^2.0.1
Updatedrimraf@^3.0.0