| /* |
| @license |
| Rollup.js v2.55.0 |
| Wed, 28 Jul 2021 05:11:06 GMT - commit e88c5c885790305d348c52faf30db835475323a5 |
| |
| |
| https://github.com/rollup/rollup |
| |
| Released under the MIT License. |
| */ |
| 'use strict'; |
| |
| var require$$0$2 = require('events'); |
| var fs$4 = require('fs'); |
| var path$3 = require('path'); |
| var require$$0$1 = require('util'); |
| var require$$1 = require('stream'); |
| var require$$2 = require('os'); |
| var rollup = require('./rollup.js'); |
| |
| function getAugmentedNamespace(n) { |
| if (n.__esModule) return n; |
| var a = Object.defineProperty({}, '__esModule', {value: true}); |
| Object.keys(n).forEach(function (k) { |
| var d = Object.getOwnPropertyDescriptor(n, k); |
| Object.defineProperty(a, k, d.get ? d : { |
| enumerable: true, |
| get: function () { |
| return n[k]; |
| } |
| }); |
| }); |
| return a; |
| } |
| |
| var utils$7 = {}; |
| |
| (function (exports) { |
| |
| exports.isInteger = num => { |
| if (typeof num === 'number') { |
| return Number.isInteger(num); |
| } |
| if (typeof num === 'string' && num.trim() !== '') { |
| return Number.isInteger(Number(num)); |
| } |
| return false; |
| }; |
| |
| /** |
| * Find a node of the given type |
| */ |
| |
| exports.find = (node, type) => node.nodes.find(node => node.type === type); |
| |
| /** |
| * Find a node of the given type |
| */ |
| |
| exports.exceedsLimit = (min, max, step = 1, limit) => { |
| if (limit === false) return false; |
| if (!exports.isInteger(min) || !exports.isInteger(max)) return false; |
| return ((Number(max) - Number(min)) / Number(step)) >= limit; |
| }; |
| |
| /** |
| * Escape the given node with '\\' before node.value |
| */ |
| |
| exports.escapeNode = (block, n = 0, type) => { |
| let node = block.nodes[n]; |
| if (!node) return; |
| |
| if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { |
| if (node.escaped !== true) { |
| node.value = '\\' + node.value; |
| node.escaped = true; |
| } |
| } |
| }; |
| |
| /** |
| * Returns true if the given brace node should be enclosed in literal braces |
| */ |
| |
| exports.encloseBrace = node => { |
| if (node.type !== 'brace') return false; |
| if ((node.commas >> 0 + node.ranges >> 0) === 0) { |
| node.invalid = true; |
| return true; |
| } |
| return false; |
| }; |
| |
| /** |
| * Returns true if a brace node is invalid. |
| */ |
| |
| exports.isInvalidBrace = block => { |
| if (block.type !== 'brace') return false; |
| if (block.invalid === true || block.dollar) return true; |
| if ((block.commas >> 0 + block.ranges >> 0) === 0) { |
| block.invalid = true; |
| return true; |
| } |
| if (block.open !== true || block.close !== true) { |
| block.invalid = true; |
| return true; |
| } |
| return false; |
| }; |
| |
| /** |
| * Returns true if a node is an open or close node |
| */ |
| |
| exports.isOpenOrClose = node => { |
| if (node.type === 'open' || node.type === 'close') { |
| return true; |
| } |
| return node.open === true || node.close === true; |
| }; |
| |
| /** |
| * Reduce an array of text nodes. |
| */ |
| |
| exports.reduce = nodes => nodes.reduce((acc, node) => { |
| if (node.type === 'text') acc.push(node.value); |
| if (node.type === 'range') node.type = 'text'; |
| return acc; |
| }, []); |
| |
| /** |
| * Flatten an array |
| */ |
| |
| exports.flatten = (...args) => { |
| const result = []; |
| const flat = arr => { |
| for (let i = 0; i < arr.length; i++) { |
| let ele = arr[i]; |
| Array.isArray(ele) ? flat(ele) : ele !== void 0 && result.push(ele); |
| } |
| return result; |
| }; |
| flat(args); |
| return result; |
| }; |
| }(utils$7)); |
| |
| const utils$6 = utils$7; |
| |
| var stringify$4 = (ast, options = {}) => { |
| let stringify = (node, parent = {}) => { |
| let invalidBlock = options.escapeInvalid && utils$6.isInvalidBrace(parent); |
| let invalidNode = node.invalid === true && options.escapeInvalid === true; |
| let output = ''; |
| |
| if (node.value) { |
| if ((invalidBlock || invalidNode) && utils$6.isOpenOrClose(node)) { |
| return '\\' + node.value; |
| } |
| return node.value; |
| } |
| |
| if (node.value) { |
| return node.value; |
| } |
| |
| if (node.nodes) { |
| for (let child of node.nodes) { |
| output += stringify(child); |
| } |
| } |
| return output; |
| }; |
| |
| return stringify(ast); |
| }; |
| |
| /*! |
| * is-number <https://github.com/jonschlinkert/is-number> |
| * |
| * Copyright (c) 2014-present, Jon Schlinkert. |
| * Released under the MIT License. |
| */ |
| |
| var isNumber$2 = function(num) { |
| if (typeof num === 'number') { |
| return num - num === 0; |
| } |
| if (typeof num === 'string' && num.trim() !== '') { |
| return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); |
| } |
| return false; |
| }; |
| |
| /*! |
| * to-regex-range <https://github.com/micromatch/to-regex-range> |
| * |
| * Copyright (c) 2015-present, Jon Schlinkert. |
| * Released under the MIT License. |
| */ |
| |
| const isNumber$1 = isNumber$2; |
| |
| const toRegexRange$1 = (min, max, options) => { |
| if (isNumber$1(min) === false) { |
| throw new TypeError('toRegexRange: expected the first argument to be a number'); |
| } |
| |
| if (max === void 0 || min === max) { |
| return String(min); |
| } |
| |
| if (isNumber$1(max) === false) { |
| throw new TypeError('toRegexRange: expected the second argument to be a number.'); |
| } |
| |
| let opts = { relaxZeros: true, ...options }; |
| if (typeof opts.strictZeros === 'boolean') { |
| opts.relaxZeros = opts.strictZeros === false; |
| } |
| |
| let relax = String(opts.relaxZeros); |
| let shorthand = String(opts.shorthand); |
| let capture = String(opts.capture); |
| let wrap = String(opts.wrap); |
| let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; |
| |
| if (toRegexRange$1.cache.hasOwnProperty(cacheKey)) { |
| return toRegexRange$1.cache[cacheKey].result; |
| } |
| |
| let a = Math.min(min, max); |
| let b = Math.max(min, max); |
| |
| if (Math.abs(a - b) === 1) { |
| let result = min + '|' + max; |
| if (opts.capture) { |
| return `(${result})`; |
| } |
| if (opts.wrap === false) { |
| return result; |
| } |
| return `(?:${result})`; |
| } |
| |
| let isPadded = hasPadding(min) || hasPadding(max); |
| let state = { min, max, a, b }; |
| let positives = []; |
| let negatives = []; |
| |
| if (isPadded) { |
| state.isPadded = isPadded; |
| state.maxLen = String(state.max).length; |
| } |
| |
| if (a < 0) { |
| let newMin = b < 0 ? Math.abs(b) : 1; |
| negatives = splitToPatterns(newMin, Math.abs(a), state, opts); |
| a = state.a = 0; |
| } |
| |
| if (b >= 0) { |
| positives = splitToPatterns(a, b, state, opts); |
| } |
| |
| state.negatives = negatives; |
| state.positives = positives; |
| state.result = collatePatterns(negatives, positives); |
| |
| if (opts.capture === true) { |
| state.result = `(${state.result})`; |
| } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { |
| state.result = `(?:${state.result})`; |
| } |
| |
| toRegexRange$1.cache[cacheKey] = state; |
| return state.result; |
| }; |
| |
| function collatePatterns(neg, pos, options) { |
| let onlyNegative = filterPatterns(neg, pos, '-', false) || []; |
| let onlyPositive = filterPatterns(pos, neg, '', false) || []; |
| let intersected = filterPatterns(neg, pos, '-?', true) || []; |
| let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); |
| return subpatterns.join('|'); |
| } |
| |
| function splitToRanges(min, max) { |
| let nines = 1; |
| let zeros = 1; |
| |
| let stop = countNines(min, nines); |
| let stops = new Set([max]); |
| |
| while (min <= stop && stop <= max) { |
| stops.add(stop); |
| nines += 1; |
| stop = countNines(min, nines); |
| } |
| |
| stop = countZeros(max + 1, zeros) - 1; |
| |
| while (min < stop && stop <= max) { |
| stops.add(stop); |
| zeros += 1; |
| stop = countZeros(max + 1, zeros) - 1; |
| } |
| |
| stops = [...stops]; |
| stops.sort(compare); |
| return stops; |
| } |
| |
| /** |
| * Convert a range to a regex pattern |
| * @param {Number} `start` |
| * @param {Number} `stop` |
| * @return {String} |
| */ |
| |
| function rangeToPattern(start, stop, options) { |
| if (start === stop) { |
| return { pattern: start, count: [], digits: 0 }; |
| } |
| |
| let zipped = zip(start, stop); |
| let digits = zipped.length; |
| let pattern = ''; |
| let count = 0; |
| |
| for (let i = 0; i < digits; i++) { |
| let [startDigit, stopDigit] = zipped[i]; |
| |
| if (startDigit === stopDigit) { |
| pattern += startDigit; |
| |
| } else if (startDigit !== '0' || stopDigit !== '9') { |
| pattern += toCharacterClass(startDigit, stopDigit); |
| |
| } else { |
| count++; |
| } |
| } |
| |
| if (count) { |
| pattern += options.shorthand === true ? '\\d' : '[0-9]'; |
| } |
| |
| return { pattern, count: [count], digits }; |
| } |
| |
| function splitToPatterns(min, max, tok, options) { |
| let ranges = splitToRanges(min, max); |
| let tokens = []; |
| let start = min; |
| let prev; |
| |
| for (let i = 0; i < ranges.length; i++) { |
| let max = ranges[i]; |
| let obj = rangeToPattern(String(start), String(max), options); |
| let zeros = ''; |
| |
| if (!tok.isPadded && prev && prev.pattern === obj.pattern) { |
| if (prev.count.length > 1) { |
| prev.count.pop(); |
| } |
| |
| prev.count.push(obj.count[0]); |
| prev.string = prev.pattern + toQuantifier(prev.count); |
| start = max + 1; |
| continue; |
| } |
| |
| if (tok.isPadded) { |
| zeros = padZeros(max, tok, options); |
| } |
| |
| obj.string = zeros + obj.pattern + toQuantifier(obj.count); |
| tokens.push(obj); |
| start = max + 1; |
| prev = obj; |
| } |
| |
| return tokens; |
| } |
| |
| function filterPatterns(arr, comparison, prefix, intersection, options) { |
| let result = []; |
| |
| for (let ele of arr) { |
| let { string } = ele; |
| |
| // only push if _both_ are negative... |
| if (!intersection && !contains(comparison, 'string', string)) { |
| result.push(prefix + string); |
| } |
| |
| // or _both_ are positive |
| if (intersection && contains(comparison, 'string', string)) { |
| result.push(prefix + string); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Zip strings |
| */ |
| |
| function zip(a, b) { |
| let arr = []; |
| for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); |
| return arr; |
| } |
| |
| function compare(a, b) { |
| return a > b ? 1 : b > a ? -1 : 0; |
| } |
| |
| function contains(arr, key, val) { |
| return arr.some(ele => ele[key] === val); |
| } |
| |
| function countNines(min, len) { |
| return Number(String(min).slice(0, -len) + '9'.repeat(len)); |
| } |
| |
| function countZeros(integer, zeros) { |
| return integer - (integer % Math.pow(10, zeros)); |
| } |
| |
| function toQuantifier(digits) { |
| let [start = 0, stop = ''] = digits; |
| if (stop || start > 1) { |
| return `{${start + (stop ? ',' + stop : '')}}`; |
| } |
| return ''; |
| } |
| |
| function toCharacterClass(a, b, options) { |
| return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; |
| } |
| |
| function hasPadding(str) { |
| return /^-?(0+)\d/.test(str); |
| } |
| |
| function padZeros(value, tok, options) { |
| if (!tok.isPadded) { |
| return value; |
| } |
| |
| let diff = Math.abs(tok.maxLen - String(value).length); |
| let relax = options.relaxZeros !== false; |
| |
| switch (diff) { |
| case 0: |
| return ''; |
| case 1: |
| return relax ? '0?' : '0'; |
| case 2: |
| return relax ? '0{0,2}' : '00'; |
| default: { |
| return relax ? `0{0,${diff}}` : `0{${diff}}`; |
| } |
| } |
| } |
| |
| /** |
| * Cache |
| */ |
| |
| toRegexRange$1.cache = {}; |
| toRegexRange$1.clearCache = () => (toRegexRange$1.cache = {}); |
| |
| /** |
| * Expose `toRegexRange` |
| */ |
| |
| var toRegexRange_1 = toRegexRange$1; |
| |
| /*! |
| * fill-range <https://github.com/jonschlinkert/fill-range> |
| * |
| * Copyright (c) 2014-present, Jon Schlinkert. |
| * Licensed under the MIT License. |
| */ |
| |
| const util = require$$0$1; |
| const toRegexRange = toRegexRange_1; |
| |
| const isObject$1 = val => val !== null && typeof val === 'object' && !Array.isArray(val); |
| |
| const transform = toNumber => { |
| return value => toNumber === true ? Number(value) : String(value); |
| }; |
| |
| const isValidValue = value => { |
| return typeof value === 'number' || (typeof value === 'string' && value !== ''); |
| }; |
| |
| const isNumber = num => Number.isInteger(+num); |
| |
| const zeros = input => { |
| let value = `${input}`; |
| let index = -1; |
| if (value[0] === '-') value = value.slice(1); |
| if (value === '0') return false; |
| while (value[++index] === '0'); |
| return index > 0; |
| }; |
| |
| const stringify$3 = (start, end, options) => { |
| if (typeof start === 'string' || typeof end === 'string') { |
| return true; |
| } |
| return options.stringify === true; |
| }; |
| |
| const pad = (input, maxLength, toNumber) => { |
| if (maxLength > 0) { |
| let dash = input[0] === '-' ? '-' : ''; |
| if (dash) input = input.slice(1); |
| input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); |
| } |
| if (toNumber === false) { |
| return String(input); |
| } |
| return input; |
| }; |
| |
| const toMaxLen = (input, maxLength) => { |
| let negative = input[0] === '-' ? '-' : ''; |
| if (negative) { |
| input = input.slice(1); |
| maxLength--; |
| } |
| while (input.length < maxLength) input = '0' + input; |
| return negative ? ('-' + input) : input; |
| }; |
| |
| const toSequence = (parts, options) => { |
| parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); |
| parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); |
| |
| let prefix = options.capture ? '' : '?:'; |
| let positives = ''; |
| let negatives = ''; |
| let result; |
| |
| if (parts.positives.length) { |
| positives = parts.positives.join('|'); |
| } |
| |
| if (parts.negatives.length) { |
| negatives = `-(${prefix}${parts.negatives.join('|')})`; |
| } |
| |
| if (positives && negatives) { |
| result = `${positives}|${negatives}`; |
| } else { |
| result = positives || negatives; |
| } |
| |
| if (options.wrap) { |
| return `(${prefix}${result})`; |
| } |
| |
| return result; |
| }; |
| |
| const toRange = (a, b, isNumbers, options) => { |
| if (isNumbers) { |
| return toRegexRange(a, b, { wrap: false, ...options }); |
| } |
| |
| let start = String.fromCharCode(a); |
| if (a === b) return start; |
| |
| let stop = String.fromCharCode(b); |
| return `[${start}-${stop}]`; |
| }; |
| |
| const toRegex = (start, end, options) => { |
| if (Array.isArray(start)) { |
| let wrap = options.wrap === true; |
| let prefix = options.capture ? '' : '?:'; |
| return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); |
| } |
| return toRegexRange(start, end, options); |
| }; |
| |
| const rangeError = (...args) => { |
| return new RangeError('Invalid range arguments: ' + util.inspect(...args)); |
| }; |
| |
| const invalidRange = (start, end, options) => { |
| if (options.strictRanges === true) throw rangeError([start, end]); |
| return []; |
| }; |
| |
| const invalidStep = (step, options) => { |
| if (options.strictRanges === true) { |
| throw new TypeError(`Expected step "${step}" to be a number`); |
| } |
| return []; |
| }; |
| |
| const fillNumbers = (start, end, step = 1, options = {}) => { |
| let a = Number(start); |
| let b = Number(end); |
| |
| if (!Number.isInteger(a) || !Number.isInteger(b)) { |
| if (options.strictRanges === true) throw rangeError([start, end]); |
| return []; |
| } |
| |
| // fix negative zero |
| if (a === 0) a = 0; |
| if (b === 0) b = 0; |
| |
| let descending = a > b; |
| let startString = String(start); |
| let endString = String(end); |
| let stepString = String(step); |
| step = Math.max(Math.abs(step), 1); |
| |
| let padded = zeros(startString) || zeros(endString) || zeros(stepString); |
| let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; |
| let toNumber = padded === false && stringify$3(start, end, options) === false; |
| let format = options.transform || transform(toNumber); |
| |
| if (options.toRegex && step === 1) { |
| return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); |
| } |
| |
| let parts = { negatives: [], positives: [] }; |
| let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); |
| let range = []; |
| let index = 0; |
| |
| while (descending ? a >= b : a <= b) { |
| if (options.toRegex === true && step > 1) { |
| push(a); |
| } else { |
| range.push(pad(format(a, index), maxLen, toNumber)); |
| } |
| a = descending ? a - step : a + step; |
| index++; |
| } |
| |
| if (options.toRegex === true) { |
| return step > 1 |
| ? toSequence(parts, options) |
| : toRegex(range, null, { wrap: false, ...options }); |
| } |
| |
| return range; |
| }; |
| |
| const fillLetters = (start, end, step = 1, options = {}) => { |
| if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { |
| return invalidRange(start, end, options); |
| } |
| |
| |
| let format = options.transform || (val => String.fromCharCode(val)); |
| let a = `${start}`.charCodeAt(0); |
| let b = `${end}`.charCodeAt(0); |
| |
| let descending = a > b; |
| let min = Math.min(a, b); |
| let max = Math.max(a, b); |
| |
| if (options.toRegex && step === 1) { |
| return toRange(min, max, false, options); |
| } |
| |
| let range = []; |
| let index = 0; |
| |
| while (descending ? a >= b : a <= b) { |
| range.push(format(a, index)); |
| a = descending ? a - step : a + step; |
| index++; |
| } |
| |
| if (options.toRegex === true) { |
| return toRegex(range, null, { wrap: false, options }); |
| } |
| |
| return range; |
| }; |
| |
| const fill$2 = (start, end, step, options = {}) => { |
| if (end == null && isValidValue(start)) { |
| return [start]; |
| } |
| |
| if (!isValidValue(start) || !isValidValue(end)) { |
| return invalidRange(start, end, options); |
| } |
| |
| if (typeof step === 'function') { |
| return fill$2(start, end, 1, { transform: step }); |
| } |
| |
| if (isObject$1(step)) { |
| return fill$2(start, end, 0, step); |
| } |
| |
| let opts = { ...options }; |
| if (opts.capture === true) opts.wrap = true; |
| step = step || opts.step || 1; |
| |
| if (!isNumber(step)) { |
| if (step != null && !isObject$1(step)) return invalidStep(step, opts); |
| return fill$2(start, end, 1, step); |
| } |
| |
| if (isNumber(start) && isNumber(end)) { |
| return fillNumbers(start, end, step, opts); |
| } |
| |
| return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); |
| }; |
| |
| var fillRange = fill$2; |
| |
| const fill$1 = fillRange; |
| const utils$5 = utils$7; |
| |
| const compile$1 = (ast, options = {}) => { |
| let walk = (node, parent = {}) => { |
| let invalidBlock = utils$5.isInvalidBrace(parent); |
| let invalidNode = node.invalid === true && options.escapeInvalid === true; |
| let invalid = invalidBlock === true || invalidNode === true; |
| let prefix = options.escapeInvalid === true ? '\\' : ''; |
| let output = ''; |
| |
| if (node.isOpen === true) { |
| return prefix + node.value; |
| } |
| if (node.isClose === true) { |
| return prefix + node.value; |
| } |
| |
| if (node.type === 'open') { |
| return invalid ? (prefix + node.value) : '('; |
| } |
| |
| if (node.type === 'close') { |
| return invalid ? (prefix + node.value) : ')'; |
| } |
| |
| if (node.type === 'comma') { |
| return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); |
| } |
| |
| if (node.value) { |
| return node.value; |
| } |
| |
| if (node.nodes && node.ranges > 0) { |
| let args = utils$5.reduce(node.nodes); |
| let range = fill$1(...args, { ...options, wrap: false, toRegex: true }); |
| |
| if (range.length !== 0) { |
| return args.length > 1 && range.length > 1 ? `(${range})` : range; |
| } |
| } |
| |
| if (node.nodes) { |
| for (let child of node.nodes) { |
| output += walk(child, node); |
| } |
| } |
| return output; |
| }; |
| |
| return walk(ast); |
| }; |
| |
| var compile_1 = compile$1; |
| |
| const fill = fillRange; |
| const stringify$2 = stringify$4; |
| const utils$4 = utils$7; |
| |
| const append = (queue = '', stash = '', enclose = false) => { |
| let result = []; |
| |
| queue = [].concat(queue); |
| stash = [].concat(stash); |
| |
| if (!stash.length) return queue; |
| if (!queue.length) { |
| return enclose ? utils$4.flatten(stash).map(ele => `{${ele}}`) : stash; |
| } |
| |
| for (let item of queue) { |
| if (Array.isArray(item)) { |
| for (let value of item) { |
| result.push(append(value, stash, enclose)); |
| } |
| } else { |
| for (let ele of stash) { |
| if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; |
| result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); |
| } |
| } |
| } |
| return utils$4.flatten(result); |
| }; |
| |
| const expand$1 = (ast, options = {}) => { |
| let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; |
| |
| let walk = (node, parent = {}) => { |
| node.queue = []; |
| |
| let p = parent; |
| let q = parent.queue; |
| |
| while (p.type !== 'brace' && p.type !== 'root' && p.parent) { |
| p = p.parent; |
| q = p.queue; |
| } |
| |
| if (node.invalid || node.dollar) { |
| q.push(append(q.pop(), stringify$2(node, options))); |
| return; |
| } |
| |
| if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { |
| q.push(append(q.pop(), ['{}'])); |
| return; |
| } |
| |
| if (node.nodes && node.ranges > 0) { |
| let args = utils$4.reduce(node.nodes); |
| |
| if (utils$4.exceedsLimit(...args, options.step, rangeLimit)) { |
| throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); |
| } |
| |
| let range = fill(...args, options); |
| if (range.length === 0) { |
| range = stringify$2(node, options); |
| } |
| |
| q.push(append(q.pop(), range)); |
| node.nodes = []; |
| return; |
| } |
| |
| let enclose = utils$4.encloseBrace(node); |
| let queue = node.queue; |
| let block = node; |
| |
| while (block.type !== 'brace' && block.type !== 'root' && block.parent) { |
| block = block.parent; |
| queue = block.queue; |
| } |
| |
| for (let i = 0; i < node.nodes.length; i++) { |
| let child = node.nodes[i]; |
| |
| if (child.type === 'comma' && node.type === 'brace') { |
| if (i === 1) queue.push(''); |
| queue.push(''); |
| continue; |
| } |
| |
| if (child.type === 'close') { |
| q.push(append(q.pop(), queue, enclose)); |
| continue; |
| } |
| |
| if (child.value && child.type !== 'open') { |
| queue.push(append(queue.pop(), child.value)); |
| continue; |
| } |
| |
| if (child.nodes) { |
| walk(child, node); |
| } |
| } |
| |
| return queue; |
| }; |
| |
| return utils$4.flatten(walk(ast)); |
| }; |
| |
| var expand_1 = expand$1; |
| |
| var constants$4 = { |
| MAX_LENGTH: 1024 * 64, |
| |
| // Digits |
| CHAR_0: '0', /* 0 */ |
| CHAR_9: '9', /* 9 */ |
| |
| // Alphabet chars. |
| CHAR_UPPERCASE_A: 'A', /* A */ |
| CHAR_LOWERCASE_A: 'a', /* a */ |
| CHAR_UPPERCASE_Z: 'Z', /* Z */ |
| CHAR_LOWERCASE_Z: 'z', /* z */ |
| |
| CHAR_LEFT_PARENTHESES: '(', /* ( */ |
| CHAR_RIGHT_PARENTHESES: ')', /* ) */ |
| |
| CHAR_ASTERISK: '*', /* * */ |
| |
| // Non-alphabetic chars. |
| CHAR_AMPERSAND: '&', /* & */ |
| CHAR_AT: '@', /* @ */ |
| CHAR_BACKSLASH: '\\', /* \ */ |
| CHAR_BACKTICK: '`', /* ` */ |
| CHAR_CARRIAGE_RETURN: '\r', /* \r */ |
| CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ |
| CHAR_COLON: ':', /* : */ |
| CHAR_COMMA: ',', /* , */ |
| CHAR_DOLLAR: '$', /* . */ |
| CHAR_DOT: '.', /* . */ |
| CHAR_DOUBLE_QUOTE: '"', /* " */ |
| CHAR_EQUAL: '=', /* = */ |
| CHAR_EXCLAMATION_MARK: '!', /* ! */ |
| CHAR_FORM_FEED: '\f', /* \f */ |
| CHAR_FORWARD_SLASH: '/', /* / */ |
| CHAR_HASH: '#', /* # */ |
| CHAR_HYPHEN_MINUS: '-', /* - */ |
| CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ |
| CHAR_LEFT_CURLY_BRACE: '{', /* { */ |
| CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ |
| CHAR_LINE_FEED: '\n', /* \n */ |
| CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ |
| CHAR_PERCENT: '%', /* % */ |
| CHAR_PLUS: '+', /* + */ |
| CHAR_QUESTION_MARK: '?', /* ? */ |
| CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ |
| CHAR_RIGHT_CURLY_BRACE: '}', /* } */ |
| CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ |
| CHAR_SEMICOLON: ';', /* ; */ |
| CHAR_SINGLE_QUOTE: '\'', /* ' */ |
| CHAR_SPACE: ' ', /* */ |
| CHAR_TAB: '\t', /* \t */ |
| CHAR_UNDERSCORE: '_', /* _ */ |
| CHAR_VERTICAL_LINE: '|', /* | */ |
| CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ |
| }; |
| |
| const stringify$1 = stringify$4; |
| |
| /** |
| * Constants |
| */ |
| |
| const { |
| MAX_LENGTH: MAX_LENGTH$1, |
| CHAR_BACKSLASH, /* \ */ |
| CHAR_BACKTICK, /* ` */ |
| CHAR_COMMA: CHAR_COMMA$1, /* , */ |
| CHAR_DOT: CHAR_DOT$1, /* . */ |
| CHAR_LEFT_PARENTHESES: CHAR_LEFT_PARENTHESES$1, /* ( */ |
| CHAR_RIGHT_PARENTHESES: CHAR_RIGHT_PARENTHESES$1, /* ) */ |
| CHAR_LEFT_CURLY_BRACE: CHAR_LEFT_CURLY_BRACE$1, /* { */ |
| CHAR_RIGHT_CURLY_BRACE: CHAR_RIGHT_CURLY_BRACE$1, /* } */ |
| CHAR_LEFT_SQUARE_BRACKET: CHAR_LEFT_SQUARE_BRACKET$1, /* [ */ |
| CHAR_RIGHT_SQUARE_BRACKET: CHAR_RIGHT_SQUARE_BRACKET$1, /* ] */ |
| CHAR_DOUBLE_QUOTE, /* " */ |
| CHAR_SINGLE_QUOTE, /* ' */ |
| CHAR_NO_BREAK_SPACE, |
| CHAR_ZERO_WIDTH_NOBREAK_SPACE |
| } = constants$4; |
| |
| /** |
| * parse |
| */ |
| |
| const parse$3 = (input, options = {}) => { |
| if (typeof input !== 'string') { |
| throw new TypeError('Expected a string'); |
| } |
| |
| let opts = options || {}; |
| let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH$1, opts.maxLength) : MAX_LENGTH$1; |
| if (input.length > max) { |
| throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); |
| } |
| |
| let ast = { type: 'root', input, nodes: [] }; |
| let stack = [ast]; |
| let block = ast; |
| let prev = ast; |
| let brackets = 0; |
| let length = input.length; |
| let index = 0; |
| let depth = 0; |
| let value; |
| |
| /** |
| * Helpers |
| */ |
| |
| const advance = () => input[index++]; |
| const push = node => { |
| if (node.type === 'text' && prev.type === 'dot') { |
| prev.type = 'text'; |
| } |
| |
| if (prev && prev.type === 'text' && node.type === 'text') { |
| prev.value += node.value; |
| return; |
| } |
| |
| block.nodes.push(node); |
| node.parent = block; |
| node.prev = prev; |
| prev = node; |
| return node; |
| }; |
| |
| push({ type: 'bos' }); |
| |
| while (index < length) { |
| block = stack[stack.length - 1]; |
| value = advance(); |
| |
| /** |
| * Invalid chars |
| */ |
| |
| if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { |
| continue; |
| } |
| |
| /** |
| * Escaped chars |
| */ |
| |
| if (value === CHAR_BACKSLASH) { |
| push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); |
| continue; |
| } |
| |
| /** |
| * Right square bracket (literal): ']' |
| */ |
| |
| if (value === CHAR_RIGHT_SQUARE_BRACKET$1) { |
| push({ type: 'text', value: '\\' + value }); |
| continue; |
| } |
| |
| /** |
| * Left square bracket: '[' |
| */ |
| |
| if (value === CHAR_LEFT_SQUARE_BRACKET$1) { |
| brackets++; |
| let next; |
| |
| while (index < length && (next = advance())) { |
| value += next; |
| |
| if (next === CHAR_LEFT_SQUARE_BRACKET$1) { |
| brackets++; |
| continue; |
| } |
| |
| if (next === CHAR_BACKSLASH) { |
| value += advance(); |
| continue; |
| } |
| |
| if (next === CHAR_RIGHT_SQUARE_BRACKET$1) { |
| brackets--; |
| |
| if (brackets === 0) { |
| break; |
| } |
| } |
| } |
| |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Parentheses |
| */ |
| |
| if (value === CHAR_LEFT_PARENTHESES$1) { |
| block = push({ type: 'paren', nodes: [] }); |
| stack.push(block); |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| if (value === CHAR_RIGHT_PARENTHESES$1) { |
| if (block.type !== 'paren') { |
| push({ type: 'text', value }); |
| continue; |
| } |
| block = stack.pop(); |
| push({ type: 'text', value }); |
| block = stack[stack.length - 1]; |
| continue; |
| } |
| |
| /** |
| * Quotes: '|"|` |
| */ |
| |
| if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { |
| let open = value; |
| let next; |
| |
| if (options.keepQuotes !== true) { |
| value = ''; |
| } |
| |
| while (index < length && (next = advance())) { |
| if (next === CHAR_BACKSLASH) { |
| value += next + advance(); |
| continue; |
| } |
| |
| if (next === open) { |
| if (options.keepQuotes === true) value += next; |
| break; |
| } |
| |
| value += next; |
| } |
| |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Left curly brace: '{' |
| */ |
| |
| if (value === CHAR_LEFT_CURLY_BRACE$1) { |
| depth++; |
| |
| let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; |
| let brace = { |
| type: 'brace', |
| open: true, |
| close: false, |
| dollar, |
| depth, |
| commas: 0, |
| ranges: 0, |
| nodes: [] |
| }; |
| |
| block = push(brace); |
| stack.push(block); |
| push({ type: 'open', value }); |
| continue; |
| } |
| |
| /** |
| * Right curly brace: '}' |
| */ |
| |
| if (value === CHAR_RIGHT_CURLY_BRACE$1) { |
| if (block.type !== 'brace') { |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| let type = 'close'; |
| block = stack.pop(); |
| block.close = true; |
| |
| push({ type, value }); |
| depth--; |
| |
| block = stack[stack.length - 1]; |
| continue; |
| } |
| |
| /** |
| * Comma: ',' |
| */ |
| |
| if (value === CHAR_COMMA$1 && depth > 0) { |
| if (block.ranges > 0) { |
| block.ranges = 0; |
| let open = block.nodes.shift(); |
| block.nodes = [open, { type: 'text', value: stringify$1(block) }]; |
| } |
| |
| push({ type: 'comma', value }); |
| block.commas++; |
| continue; |
| } |
| |
| /** |
| * Dot: '.' |
| */ |
| |
| if (value === CHAR_DOT$1 && depth > 0 && block.commas === 0) { |
| let siblings = block.nodes; |
| |
| if (depth === 0 || siblings.length === 0) { |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| if (prev.type === 'dot') { |
| block.range = []; |
| prev.value += value; |
| prev.type = 'range'; |
| |
| if (block.nodes.length !== 3 && block.nodes.length !== 5) { |
| block.invalid = true; |
| block.ranges = 0; |
| prev.type = 'text'; |
| continue; |
| } |
| |
| block.ranges++; |
| block.args = []; |
| continue; |
| } |
| |
| if (prev.type === 'range') { |
| siblings.pop(); |
| |
| let before = siblings[siblings.length - 1]; |
| before.value += prev.value + value; |
| prev = before; |
| block.ranges--; |
| continue; |
| } |
| |
| push({ type: 'dot', value }); |
| continue; |
| } |
| |
| /** |
| * Text |
| */ |
| |
| push({ type: 'text', value }); |
| } |
| |
| // Mark imbalanced braces and brackets as invalid |
| do { |
| block = stack.pop(); |
| |
| if (block.type !== 'root') { |
| block.nodes.forEach(node => { |
| if (!node.nodes) { |
| if (node.type === 'open') node.isOpen = true; |
| if (node.type === 'close') node.isClose = true; |
| if (!node.nodes) node.type = 'text'; |
| node.invalid = true; |
| } |
| }); |
| |
| // get the location of the block on parent.nodes (block's siblings) |
| let parent = stack[stack.length - 1]; |
| let index = parent.nodes.indexOf(block); |
| // replace the (invalid) block with it's nodes |
| parent.nodes.splice(index, 1, ...block.nodes); |
| } |
| } while (stack.length > 0); |
| |
| push({ type: 'eos' }); |
| return ast; |
| }; |
| |
| var parse_1$1 = parse$3; |
| |
| const stringify = stringify$4; |
| const compile = compile_1; |
| const expand = expand_1; |
| const parse$2 = parse_1$1; |
| |
| /** |
| * Expand the given pattern or create a regex-compatible string. |
| * |
| * ```js |
| * const braces = require('braces'); |
| * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] |
| * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] |
| * ``` |
| * @param {String} `str` |
| * @param {Object} `options` |
| * @return {String} |
| * @api public |
| */ |
| |
| const braces$1 = (input, options = {}) => { |
| let output = []; |
| |
| if (Array.isArray(input)) { |
| for (let pattern of input) { |
| let result = braces$1.create(pattern, options); |
| if (Array.isArray(result)) { |
| output.push(...result); |
| } else { |
| output.push(result); |
| } |
| } |
| } else { |
| output = [].concat(braces$1.create(input, options)); |
| } |
| |
| if (options && options.expand === true && options.nodupes === true) { |
| output = [...new Set(output)]; |
| } |
| return output; |
| }; |
| |
| /** |
| * Parse the given `str` with the given `options`. |
| * |
| * ```js |
| * // braces.parse(pattern, [, options]); |
| * const ast = braces.parse('a/{b,c}/d'); |
| * console.log(ast); |
| * ``` |
| * @param {String} pattern Brace pattern to parse |
| * @param {Object} options |
| * @return {Object} Returns an AST |
| * @api public |
| */ |
| |
| braces$1.parse = (input, options = {}) => parse$2(input, options); |
| |
| /** |
| * Creates a braces string from an AST, or an AST node. |
| * |
| * ```js |
| * const braces = require('braces'); |
| * let ast = braces.parse('foo/{a,b}/bar'); |
| * console.log(stringify(ast.nodes[2])); //=> '{a,b}' |
| * ``` |
| * @param {String} `input` Brace pattern or AST. |
| * @param {Object} `options` |
| * @return {Array} Returns an array of expanded values. |
| * @api public |
| */ |
| |
| braces$1.stringify = (input, options = {}) => { |
| if (typeof input === 'string') { |
| return stringify(braces$1.parse(input, options), options); |
| } |
| return stringify(input, options); |
| }; |
| |
| /** |
| * Compiles a brace pattern into a regex-compatible, optimized string. |
| * This method is called by the main [braces](#braces) function by default. |
| * |
| * ```js |
| * const braces = require('braces'); |
| * console.log(braces.compile('a/{b,c}/d')); |
| * //=> ['a/(b|c)/d'] |
| * ``` |
| * @param {String} `input` Brace pattern or AST. |
| * @param {Object} `options` |
| * @return {Array} Returns an array of expanded values. |
| * @api public |
| */ |
| |
| braces$1.compile = (input, options = {}) => { |
| if (typeof input === 'string') { |
| input = braces$1.parse(input, options); |
| } |
| return compile(input, options); |
| }; |
| |
| /** |
| * Expands a brace pattern into an array. This method is called by the |
| * main [braces](#braces) function when `options.expand` is true. Before |
| * using this method it's recommended that you read the [performance notes](#performance)) |
| * and advantages of using [.compile](#compile) instead. |
| * |
| * ```js |
| * const braces = require('braces'); |
| * console.log(braces.expand('a/{b,c}/d')); |
| * //=> ['a/b/d', 'a/c/d']; |
| * ``` |
| * @param {String} `pattern` Brace pattern |
| * @param {Object} `options` |
| * @return {Array} Returns an array of expanded values. |
| * @api public |
| */ |
| |
| braces$1.expand = (input, options = {}) => { |
| if (typeof input === 'string') { |
| input = braces$1.parse(input, options); |
| } |
| |
| let result = expand(input, options); |
| |
| // filter out empty strings if specified |
| if (options.noempty === true) { |
| result = result.filter(Boolean); |
| } |
| |
| // filter out duplicates if specified |
| if (options.nodupes === true) { |
| result = [...new Set(result)]; |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * Processes a brace pattern and returns either an expanded array |
| * (if `options.expand` is true), a highly optimized regex-compatible string. |
| * This method is called by the main [braces](#braces) function. |
| * |
| * ```js |
| * const braces = require('braces'); |
| * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) |
| * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' |
| * ``` |
| * @param {String} `pattern` Brace pattern |
| * @param {Object} `options` |
| * @return {Array} Returns an array of expanded values. |
| * @api public |
| */ |
| |
| braces$1.create = (input, options = {}) => { |
| if (input === '' || input.length < 3) { |
| return [input]; |
| } |
| |
| return options.expand !== true |
| ? braces$1.compile(input, options) |
| : braces$1.expand(input, options); |
| }; |
| |
| /** |
| * Expose "braces" |
| */ |
| |
| var braces_1 = braces$1; |
| |
| var utils$3 = {}; |
| |
| const path$2 = path$3; |
| const WIN_SLASH = '\\\\/'; |
| const WIN_NO_SLASH = `[^${WIN_SLASH}]`; |
| |
| /** |
| * Posix glob regex |
| */ |
| |
| const DOT_LITERAL = '\\.'; |
| const PLUS_LITERAL = '\\+'; |
| const QMARK_LITERAL = '\\?'; |
| const SLASH_LITERAL = '\\/'; |
| const ONE_CHAR = '(?=.)'; |
| const QMARK = '[^/]'; |
| const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; |
| const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; |
| const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; |
| const NO_DOT = `(?!${DOT_LITERAL})`; |
| const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; |
| const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; |
| const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; |
| const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; |
| const STAR$1 = `${QMARK}*?`; |
| |
| const POSIX_CHARS = { |
| DOT_LITERAL, |
| PLUS_LITERAL, |
| QMARK_LITERAL, |
| SLASH_LITERAL, |
| ONE_CHAR, |
| QMARK, |
| END_ANCHOR, |
| DOTS_SLASH, |
| NO_DOT, |
| NO_DOTS, |
| NO_DOT_SLASH, |
| NO_DOTS_SLASH, |
| QMARK_NO_DOT, |
| STAR: STAR$1, |
| START_ANCHOR |
| }; |
| |
| /** |
| * Windows glob regex |
| */ |
| |
| const WINDOWS_CHARS = { |
| ...POSIX_CHARS, |
| |
| SLASH_LITERAL: `[${WIN_SLASH}]`, |
| QMARK: WIN_NO_SLASH, |
| STAR: `${WIN_NO_SLASH}*?`, |
| DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, |
| NO_DOT: `(?!${DOT_LITERAL})`, |
| NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, |
| NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, |
| NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, |
| QMARK_NO_DOT: `[^.${WIN_SLASH}]`, |
| START_ANCHOR: `(?:^|[${WIN_SLASH}])`, |
| END_ANCHOR: `(?:[${WIN_SLASH}]|$)` |
| }; |
| |
| /** |
| * POSIX Bracket Regex |
| */ |
| |
| const POSIX_REGEX_SOURCE$1 = { |
| alnum: 'a-zA-Z0-9', |
| alpha: 'a-zA-Z', |
| ascii: '\\x00-\\x7F', |
| blank: ' \\t', |
| cntrl: '\\x00-\\x1F\\x7F', |
| digit: '0-9', |
| graph: '\\x21-\\x7E', |
| lower: 'a-z', |
| print: '\\x20-\\x7E ', |
| punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', |
| space: ' \\t\\r\\n\\v\\f', |
| upper: 'A-Z', |
| word: 'A-Za-z0-9_', |
| xdigit: 'A-Fa-f0-9' |
| }; |
| |
| var constants$3 = { |
| MAX_LENGTH: 1024 * 64, |
| POSIX_REGEX_SOURCE: POSIX_REGEX_SOURCE$1, |
| |
| // regular expressions |
| REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, |
| REGEX_NON_SPECIAL_CHARS: /^[^@![\].,$*+?^{}()|\\/]+/, |
| REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, |
| REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, |
| REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, |
| REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, |
| |
| // Replace globs with equivalent patterns to reduce parsing time. |
| REPLACEMENTS: { |
| '***': '*', |
| '**/**': '**', |
| '**/**/**': '**' |
| }, |
| |
| // Digits |
| CHAR_0: 48, /* 0 */ |
| CHAR_9: 57, /* 9 */ |
| |
| // Alphabet chars. |
| CHAR_UPPERCASE_A: 65, /* A */ |
| CHAR_LOWERCASE_A: 97, /* a */ |
| CHAR_UPPERCASE_Z: 90, /* Z */ |
| CHAR_LOWERCASE_Z: 122, /* z */ |
| |
| CHAR_LEFT_PARENTHESES: 40, /* ( */ |
| CHAR_RIGHT_PARENTHESES: 41, /* ) */ |
| |
| CHAR_ASTERISK: 42, /* * */ |
| |
| // Non-alphabetic chars. |
| CHAR_AMPERSAND: 38, /* & */ |
| CHAR_AT: 64, /* @ */ |
| CHAR_BACKWARD_SLASH: 92, /* \ */ |
| CHAR_CARRIAGE_RETURN: 13, /* \r */ |
| CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ |
| CHAR_COLON: 58, /* : */ |
| CHAR_COMMA: 44, /* , */ |
| CHAR_DOT: 46, /* . */ |
| CHAR_DOUBLE_QUOTE: 34, /* " */ |
| CHAR_EQUAL: 61, /* = */ |
| CHAR_EXCLAMATION_MARK: 33, /* ! */ |
| CHAR_FORM_FEED: 12, /* \f */ |
| CHAR_FORWARD_SLASH: 47, /* / */ |
| CHAR_GRAVE_ACCENT: 96, /* ` */ |
| CHAR_HASH: 35, /* # */ |
| CHAR_HYPHEN_MINUS: 45, /* - */ |
| CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ |
| CHAR_LEFT_CURLY_BRACE: 123, /* { */ |
| CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ |
| CHAR_LINE_FEED: 10, /* \n */ |
| CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ |
| CHAR_PERCENT: 37, /* % */ |
| CHAR_PLUS: 43, /* + */ |
| CHAR_QUESTION_MARK: 63, /* ? */ |
| CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ |
| CHAR_RIGHT_CURLY_BRACE: 125, /* } */ |
| CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ |
| CHAR_SEMICOLON: 59, /* ; */ |
| CHAR_SINGLE_QUOTE: 39, /* ' */ |
| CHAR_SPACE: 32, /* */ |
| CHAR_TAB: 9, /* \t */ |
| CHAR_UNDERSCORE: 95, /* _ */ |
| CHAR_VERTICAL_LINE: 124, /* | */ |
| CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ |
| |
| SEP: path$2.sep, |
| |
| /** |
| * Create EXTGLOB_CHARS |
| */ |
| |
| extglobChars(chars) { |
| return { |
| '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, |
| '?': { type: 'qmark', open: '(?:', close: ')?' }, |
| '+': { type: 'plus', open: '(?:', close: ')+' }, |
| '*': { type: 'star', open: '(?:', close: ')*' }, |
| '@': { type: 'at', open: '(?:', close: ')' } |
| }; |
| }, |
| |
| /** |
| * Create GLOB_CHARS |
| */ |
| |
| globChars(win32) { |
| return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; |
| } |
| }; |
| |
| (function (exports) { |
| |
| const path = path$3; |
| const win32 = process.platform === 'win32'; |
| const { |
| REGEX_BACKSLASH, |
| REGEX_REMOVE_BACKSLASH, |
| REGEX_SPECIAL_CHARS, |
| REGEX_SPECIAL_CHARS_GLOBAL |
| } = constants$3; |
| |
| exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); |
| exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); |
| exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); |
| exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); |
| exports.toPosixSlashes = str => str.replace(REGEX_BACKSLASH, '/'); |
| |
| exports.removeBackslashes = str => { |
| return str.replace(REGEX_REMOVE_BACKSLASH, match => { |
| return match === '\\' ? '' : match; |
| }); |
| }; |
| |
| exports.supportsLookbehinds = () => { |
| const segs = process.version.slice(1).split('.').map(Number); |
| if (segs.length === 3 && segs[0] >= 9 || (segs[0] === 8 && segs[1] >= 10)) { |
| return true; |
| } |
| return false; |
| }; |
| |
| exports.isWindows = options => { |
| if (options && typeof options.windows === 'boolean') { |
| return options.windows; |
| } |
| return win32 === true || path.sep === '\\'; |
| }; |
| |
| exports.escapeLast = (input, char, lastIdx) => { |
| const idx = input.lastIndexOf(char, lastIdx); |
| if (idx === -1) return input; |
| if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); |
| return `${input.slice(0, idx)}\\${input.slice(idx)}`; |
| }; |
| |
| exports.removePrefix = (input, state = {}) => { |
| let output = input; |
| if (output.startsWith('./')) { |
| output = output.slice(2); |
| state.prefix = './'; |
| } |
| return output; |
| }; |
| |
| exports.wrapOutput = (input, state = {}, options = {}) => { |
| const prepend = options.contains ? '' : '^'; |
| const append = options.contains ? '' : '$'; |
| |
| let output = `${prepend}(?:${input})${append}`; |
| if (state.negated === true) { |
| output = `(?:^(?!${output}).*$)`; |
| } |
| return output; |
| }; |
| }(utils$3)); |
| |
| const utils$2 = utils$3; |
| const { |
| CHAR_ASTERISK, /* * */ |
| CHAR_AT, /* @ */ |
| CHAR_BACKWARD_SLASH, /* \ */ |
| CHAR_COMMA, /* , */ |
| CHAR_DOT, /* . */ |
| CHAR_EXCLAMATION_MARK, /* ! */ |
| CHAR_FORWARD_SLASH, /* / */ |
| CHAR_LEFT_CURLY_BRACE, /* { */ |
| CHAR_LEFT_PARENTHESES, /* ( */ |
| CHAR_LEFT_SQUARE_BRACKET, /* [ */ |
| CHAR_PLUS, /* + */ |
| CHAR_QUESTION_MARK, /* ? */ |
| CHAR_RIGHT_CURLY_BRACE, /* } */ |
| CHAR_RIGHT_PARENTHESES, /* ) */ |
| CHAR_RIGHT_SQUARE_BRACKET /* ] */ |
| } = constants$3; |
| |
| const isPathSeparator = code => { |
| return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; |
| }; |
| |
| const depth = token => { |
| if (token.isPrefix !== true) { |
| token.depth = token.isGlobstar ? Infinity : 1; |
| } |
| }; |
| |
| /** |
| * Quickly scans a glob pattern and returns an object with a handful of |
| * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), |
| * `glob` (the actual pattern), `negated` (true if the path starts with `!` but not |
| * with `!(`) and `negatedExtglob` (true if the path starts with `!(`). |
| * |
| * ```js |
| * const pm = require('picomatch'); |
| * console.log(pm.scan('foo/bar/*.js')); |
| * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } |
| * ``` |
| * @param {String} `str` |
| * @param {Object} `options` |
| * @return {Object} Returns an object with tokens and regex source string. |
| * @api public |
| */ |
| |
| const scan$1 = (input, options) => { |
| const opts = options || {}; |
| |
| const length = input.length - 1; |
| const scanToEnd = opts.parts === true || opts.scanToEnd === true; |
| const slashes = []; |
| const tokens = []; |
| const parts = []; |
| |
| let str = input; |
| let index = -1; |
| let start = 0; |
| let lastIndex = 0; |
| let isBrace = false; |
| let isBracket = false; |
| let isGlob = false; |
| let isExtglob = false; |
| let isGlobstar = false; |
| let braceEscaped = false; |
| let backslashes = false; |
| let negated = false; |
| let negatedExtglob = false; |
| let finished = false; |
| let braces = 0; |
| let prev; |
| let code; |
| let token = { value: '', depth: 0, isGlob: false }; |
| |
| const eos = () => index >= length; |
| const peek = () => str.charCodeAt(index + 1); |
| const advance = () => { |
| prev = code; |
| return str.charCodeAt(++index); |
| }; |
| |
| while (index < length) { |
| code = advance(); |
| let next; |
| |
| if (code === CHAR_BACKWARD_SLASH) { |
| backslashes = token.backslashes = true; |
| code = advance(); |
| |
| if (code === CHAR_LEFT_CURLY_BRACE) { |
| braceEscaped = true; |
| } |
| continue; |
| } |
| |
| if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { |
| braces++; |
| |
| while (eos() !== true && (code = advance())) { |
| if (code === CHAR_BACKWARD_SLASH) { |
| backslashes = token.backslashes = true; |
| advance(); |
| continue; |
| } |
| |
| if (code === CHAR_LEFT_CURLY_BRACE) { |
| braces++; |
| continue; |
| } |
| |
| if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) { |
| isBrace = token.isBrace = true; |
| isGlob = token.isGlob = true; |
| finished = true; |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (braceEscaped !== true && code === CHAR_COMMA) { |
| isBrace = token.isBrace = true; |
| isGlob = token.isGlob = true; |
| finished = true; |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (code === CHAR_RIGHT_CURLY_BRACE) { |
| braces--; |
| |
| if (braces === 0) { |
| braceEscaped = false; |
| isBrace = token.isBrace = true; |
| finished = true; |
| break; |
| } |
| } |
| } |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (code === CHAR_FORWARD_SLASH) { |
| slashes.push(index); |
| tokens.push(token); |
| token = { value: '', depth: 0, isGlob: false }; |
| |
| if (finished === true) continue; |
| if (prev === CHAR_DOT && index === (start + 1)) { |
| start += 2; |
| continue; |
| } |
| |
| lastIndex = index + 1; |
| continue; |
| } |
| |
| if (opts.noext !== true) { |
| const isExtglobChar = code === CHAR_PLUS |
| || code === CHAR_AT |
| || code === CHAR_ASTERISK |
| || code === CHAR_QUESTION_MARK |
| || code === CHAR_EXCLAMATION_MARK; |
| |
| if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) { |
| isGlob = token.isGlob = true; |
| isExtglob = token.isExtglob = true; |
| finished = true; |
| if (code === CHAR_EXCLAMATION_MARK && index === start) { |
| negatedExtglob = true; |
| } |
| |
| if (scanToEnd === true) { |
| while (eos() !== true && (code = advance())) { |
| if (code === CHAR_BACKWARD_SLASH) { |
| backslashes = token.backslashes = true; |
| code = advance(); |
| continue; |
| } |
| |
| if (code === CHAR_RIGHT_PARENTHESES) { |
| isGlob = token.isGlob = true; |
| finished = true; |
| break; |
| } |
| } |
| continue; |
| } |
| break; |
| } |
| } |
| |
| if (code === CHAR_ASTERISK) { |
| if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true; |
| isGlob = token.isGlob = true; |
| finished = true; |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| break; |
| } |
| |
| if (code === CHAR_QUESTION_MARK) { |
| isGlob = token.isGlob = true; |
| finished = true; |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| break; |
| } |
| |
| if (code === CHAR_LEFT_SQUARE_BRACKET) { |
| while (eos() !== true && (next = advance())) { |
| if (next === CHAR_BACKWARD_SLASH) { |
| backslashes = token.backslashes = true; |
| advance(); |
| continue; |
| } |
| |
| if (next === CHAR_RIGHT_SQUARE_BRACKET) { |
| isBracket = token.isBracket = true; |
| isGlob = token.isGlob = true; |
| finished = true; |
| break; |
| } |
| } |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) { |
| negated = token.negated = true; |
| start++; |
| continue; |
| } |
| |
| if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { |
| isGlob = token.isGlob = true; |
| |
| if (scanToEnd === true) { |
| while (eos() !== true && (code = advance())) { |
| if (code === CHAR_LEFT_PARENTHESES) { |
| backslashes = token.backslashes = true; |
| code = advance(); |
| continue; |
| } |
| |
| if (code === CHAR_RIGHT_PARENTHESES) { |
| finished = true; |
| break; |
| } |
| } |
| continue; |
| } |
| break; |
| } |
| |
| if (isGlob === true) { |
| finished = true; |
| |
| if (scanToEnd === true) { |
| continue; |
| } |
| |
| break; |
| } |
| } |
| |
| if (opts.noext === true) { |
| isExtglob = false; |
| isGlob = false; |
| } |
| |
| let base = str; |
| let prefix = ''; |
| let glob = ''; |
| |
| if (start > 0) { |
| prefix = str.slice(0, start); |
| str = str.slice(start); |
| lastIndex -= start; |
| } |
| |
| if (base && isGlob === true && lastIndex > 0) { |
| base = str.slice(0, lastIndex); |
| glob = str.slice(lastIndex); |
| } else if (isGlob === true) { |
| base = ''; |
| glob = str; |
| } else { |
| base = str; |
| } |
| |
| if (base && base !== '' && base !== '/' && base !== str) { |
| if (isPathSeparator(base.charCodeAt(base.length - 1))) { |
| base = base.slice(0, -1); |
| } |
| } |
| |
| if (opts.unescape === true) { |
| if (glob) glob = utils$2.removeBackslashes(glob); |
| |
| if (base && backslashes === true) { |
| base = utils$2.removeBackslashes(base); |
| } |
| } |
| |
| const state = { |
| prefix, |
| input, |
| start, |
| base, |
| glob, |
| isBrace, |
| isBracket, |
| isGlob, |
| isExtglob, |
| isGlobstar, |
| negated, |
| negatedExtglob |
| }; |
| |
| if (opts.tokens === true) { |
| state.maxDepth = 0; |
| if (!isPathSeparator(code)) { |
| tokens.push(token); |
| } |
| state.tokens = tokens; |
| } |
| |
| if (opts.parts === true || opts.tokens === true) { |
| let prevIndex; |
| |
| for (let idx = 0; idx < slashes.length; idx++) { |
| const n = prevIndex ? prevIndex + 1 : start; |
| const i = slashes[idx]; |
| const value = input.slice(n, i); |
| if (opts.tokens) { |
| if (idx === 0 && start !== 0) { |
| tokens[idx].isPrefix = true; |
| tokens[idx].value = prefix; |
| } else { |
| tokens[idx].value = value; |
| } |
| depth(tokens[idx]); |
| state.maxDepth += tokens[idx].depth; |
| } |
| if (idx !== 0 || value !== '') { |
| parts.push(value); |
| } |
| prevIndex = i; |
| } |
| |
| if (prevIndex && prevIndex + 1 < input.length) { |
| const value = input.slice(prevIndex + 1); |
| parts.push(value); |
| |
| if (opts.tokens) { |
| tokens[tokens.length - 1].value = value; |
| depth(tokens[tokens.length - 1]); |
| state.maxDepth += tokens[tokens.length - 1].depth; |
| } |
| } |
| |
| state.slashes = slashes; |
| state.parts = parts; |
| } |
| |
| return state; |
| }; |
| |
| var scan_1 = scan$1; |
| |
| const constants$2 = constants$3; |
| const utils$1 = utils$3; |
| |
| /** |
| * Constants |
| */ |
| |
| const { |
| MAX_LENGTH, |
| POSIX_REGEX_SOURCE, |
| REGEX_NON_SPECIAL_CHARS, |
| REGEX_SPECIAL_CHARS_BACKREF, |
| REPLACEMENTS |
| } = constants$2; |
| |
| /** |
| * Helpers |
| */ |
| |
| const expandRange = (args, options) => { |
| if (typeof options.expandRange === 'function') { |
| return options.expandRange(...args, options); |
| } |
| |
| args.sort(); |
| const value = `[${args.join('-')}]`; |
| |
| return value; |
| }; |
| |
| /** |
| * Create the message for a syntax error |
| */ |
| |
| const syntaxError = (type, char) => { |
| return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; |
| }; |
| |
| /** |
| * Parse the given input string. |
| * @param {String} input |
| * @param {Object} options |
| * @return {Object} |
| */ |
| |
| const parse$1 = (input, options) => { |
| if (typeof input !== 'string') { |
| throw new TypeError('Expected a string'); |
| } |
| |
| input = REPLACEMENTS[input] || input; |
| |
| const opts = { ...options }; |
| const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; |
| |
| let len = input.length; |
| if (len > max) { |
| throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); |
| } |
| |
| const bos = { type: 'bos', value: '', output: opts.prepend || '' }; |
| const tokens = [bos]; |
| |
| const capture = opts.capture ? '' : '?:'; |
| const win32 = utils$1.isWindows(options); |
| |
| // create constants based on platform, for windows or posix |
| const PLATFORM_CHARS = constants$2.globChars(win32); |
| const EXTGLOB_CHARS = constants$2.extglobChars(PLATFORM_CHARS); |
| |
| const { |
| DOT_LITERAL, |
| PLUS_LITERAL, |
| SLASH_LITERAL, |
| ONE_CHAR, |
| DOTS_SLASH, |
| NO_DOT, |
| NO_DOT_SLASH, |
| NO_DOTS_SLASH, |
| QMARK, |
| QMARK_NO_DOT, |
| STAR, |
| START_ANCHOR |
| } = PLATFORM_CHARS; |
| |
| const globstar = opts => { |
| return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; |
| }; |
| |
| const nodot = opts.dot ? '' : NO_DOT; |
| const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; |
| let star = opts.bash === true ? globstar(opts) : STAR; |
| |
| if (opts.capture) { |
| star = `(${star})`; |
| } |
| |
| // minimatch options support |
| if (typeof opts.noext === 'boolean') { |
| opts.noextglob = opts.noext; |
| } |
| |
| const state = { |
| input, |
| index: -1, |
| start: 0, |
| dot: opts.dot === true, |
| consumed: '', |
| output: '', |
| prefix: '', |
| backtrack: false, |
| negated: false, |
| brackets: 0, |
| braces: 0, |
| parens: 0, |
| quotes: 0, |
| globstar: false, |
| tokens |
| }; |
| |
| input = utils$1.removePrefix(input, state); |
| len = input.length; |
| |
| const extglobs = []; |
| const braces = []; |
| const stack = []; |
| let prev = bos; |
| let value; |
| |
| /** |
| * Tokenizing helpers |
| */ |
| |
| const eos = () => state.index === len - 1; |
| const peek = state.peek = (n = 1) => input[state.index + n]; |
| const advance = state.advance = () => input[++state.index] || ''; |
| const remaining = () => input.slice(state.index + 1); |
| const consume = (value = '', num = 0) => { |
| state.consumed += value; |
| state.index += num; |
| }; |
| |
| const append = token => { |
| state.output += token.output != null ? token.output : token.value; |
| consume(token.value); |
| }; |
| |
| const negate = () => { |
| let count = 1; |
| |
| while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) { |
| advance(); |
| state.start++; |
| count++; |
| } |
| |
| if (count % 2 === 0) { |
| return false; |
| } |
| |
| state.negated = true; |
| state.start++; |
| return true; |
| }; |
| |
| const increment = type => { |
| state[type]++; |
| stack.push(type); |
| }; |
| |
| const decrement = type => { |
| state[type]--; |
| stack.pop(); |
| }; |
| |
| /** |
| * Push tokens onto the tokens array. This helper speeds up |
| * tokenizing by 1) helping us avoid backtracking as much as possible, |
| * and 2) helping us avoid creating extra tokens when consecutive |
| * characters are plain text. This improves performance and simplifies |
| * lookbehinds. |
| */ |
| |
| const push = tok => { |
| if (prev.type === 'globstar') { |
| const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); |
| const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren')); |
| |
| if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { |
| state.output = state.output.slice(0, -prev.output.length); |
| prev.type = 'star'; |
| prev.value = '*'; |
| prev.output = star; |
| state.output += prev.output; |
| } |
| } |
| |
| if (extglobs.length && tok.type !== 'paren') { |
| extglobs[extglobs.length - 1].inner += tok.value; |
| } |
| |
| if (tok.value || tok.output) append(tok); |
| if (prev && prev.type === 'text' && tok.type === 'text') { |
| prev.value += tok.value; |
| prev.output = (prev.output || '') + tok.value; |
| return; |
| } |
| |
| tok.prev = prev; |
| tokens.push(tok); |
| prev = tok; |
| }; |
| |
| const extglobOpen = (type, value) => { |
| const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; |
| |
| token.prev = prev; |
| token.parens = state.parens; |
| token.output = state.output; |
| const output = (opts.capture ? '(' : '') + token.open; |
| |
| increment('parens'); |
| push({ type, value, output: state.output ? '' : ONE_CHAR }); |
| push({ type: 'paren', extglob: true, value: advance(), output }); |
| extglobs.push(token); |
| }; |
| |
| const extglobClose = token => { |
| let output = token.close + (opts.capture ? ')' : ''); |
| let rest; |
| |
| if (token.type === 'negate') { |
| let extglobStar = star; |
| |
| if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { |
| extglobStar = globstar(opts); |
| } |
| |
| if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) { |
| output = token.close = `)$))${extglobStar}`; |
| } |
| |
| if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) { |
| output = token.close = `)${rest})${extglobStar})`; |
| } |
| |
| if (token.prev.type === 'bos') { |
| state.negatedExtglob = true; |
| } |
| } |
| |
| push({ type: 'paren', extglob: true, value, output }); |
| decrement('parens'); |
| }; |
| |
| /** |
| * Fast paths |
| */ |
| |
| if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) { |
| let backslashes = false; |
| |
| let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { |
| if (first === '\\') { |
| backslashes = true; |
| return m; |
| } |
| |
| if (first === '?') { |
| if (esc) { |
| return esc + first + (rest ? QMARK.repeat(rest.length) : ''); |
| } |
| if (index === 0) { |
| return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); |
| } |
| return QMARK.repeat(chars.length); |
| } |
| |
| if (first === '.') { |
| return DOT_LITERAL.repeat(chars.length); |
| } |
| |
| if (first === '*') { |
| if (esc) { |
| return esc + first + (rest ? star : ''); |
| } |
| return star; |
| } |
| return esc ? m : `\\${m}`; |
| }); |
| |
| if (backslashes === true) { |
| if (opts.unescape === true) { |
| output = output.replace(/\\/g, ''); |
| } else { |
| output = output.replace(/\\+/g, m => { |
| return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); |
| }); |
| } |
| } |
| |
| if (output === input && opts.contains === true) { |
| state.output = input; |
| return state; |
| } |
| |
| state.output = utils$1.wrapOutput(output, state, options); |
| return state; |
| } |
| |
| /** |
| * Tokenize input until we reach end-of-string |
| */ |
| |
| while (!eos()) { |
| value = advance(); |
| |
| if (value === '\u0000') { |
| continue; |
| } |
| |
| /** |
| * Escaped characters |
| */ |
| |
| if (value === '\\') { |
| const next = peek(); |
| |
| if (next === '/' && opts.bash !== true) { |
| continue; |
| } |
| |
| if (next === '.' || next === ';') { |
| continue; |
| } |
| |
| if (!next) { |
| value += '\\'; |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| // collapse slashes to reduce potential for exploits |
| const match = /^\\+/.exec(remaining()); |
| let slashes = 0; |
| |
| if (match && match[0].length > 2) { |
| slashes = match[0].length; |
| state.index += slashes; |
| if (slashes % 2 !== 0) { |
| value += '\\'; |
| } |
| } |
| |
| if (opts.unescape === true) { |
| value = advance(); |
| } else { |
| value += advance(); |
| } |
| |
| if (state.brackets === 0) { |
| push({ type: 'text', value }); |
| continue; |
| } |
| } |
| |
| /** |
| * If we're inside a regex character class, continue |
| * until we reach the closing bracket. |
| */ |
| |
| if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { |
| if (opts.posix !== false && value === ':') { |
| const inner = prev.value.slice(1); |
| if (inner.includes('[')) { |
| prev.posix = true; |
| |
| if (inner.includes(':')) { |
| const idx = prev.value.lastIndexOf('['); |
| const pre = prev.value.slice(0, idx); |
| const rest = prev.value.slice(idx + 2); |
| const posix = POSIX_REGEX_SOURCE[rest]; |
| if (posix) { |
| prev.value = pre + posix; |
| state.backtrack = true; |
| advance(); |
| |
| if (!bos.output && tokens.indexOf(prev) === 1) { |
| bos.output = ONE_CHAR; |
| } |
| continue; |
| } |
| } |
| } |
| } |
| |
| if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { |
| value = `\\${value}`; |
| } |
| |
| if (value === ']' && (prev.value === '[' || prev.value === '[^')) { |
| value = `\\${value}`; |
| } |
| |
| if (opts.posix === true && value === '!' && prev.value === '[') { |
| value = '^'; |
| } |
| |
| prev.value += value; |
| append({ value }); |
| continue; |
| } |
| |
| /** |
| * If we're inside a quoted string, continue |
| * until we reach the closing double quote. |
| */ |
| |
| if (state.quotes === 1 && value !== '"') { |
| value = utils$1.escapeRegex(value); |
| prev.value += value; |
| append({ value }); |
| continue; |
| } |
| |
| /** |
| * Double quotes |
| */ |
| |
| if (value === '"') { |
| state.quotes = state.quotes === 1 ? 0 : 1; |
| if (opts.keepQuotes === true) { |
| push({ type: 'text', value }); |
| } |
| continue; |
| } |
| |
| /** |
| * Parentheses |
| */ |
| |
| if (value === '(') { |
| increment('parens'); |
| push({ type: 'paren', value }); |
| continue; |
| } |
| |
| if (value === ')') { |
| if (state.parens === 0 && opts.strictBrackets === true) { |
| throw new SyntaxError(syntaxError('opening', '(')); |
| } |
| |
| const extglob = extglobs[extglobs.length - 1]; |
| if (extglob && state.parens === extglob.parens + 1) { |
| extglobClose(extglobs.pop()); |
| continue; |
| } |
| |
| push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); |
| decrement('parens'); |
| continue; |
| } |
| |
| /** |
| * Square brackets |
| */ |
| |
| if (value === '[') { |
| if (opts.nobracket === true || !remaining().includes(']')) { |
| if (opts.nobracket !== true && opts.strictBrackets === true) { |
| throw new SyntaxError(syntaxError('closing', ']')); |
| } |
| |
| value = `\\${value}`; |
| } else { |
| increment('brackets'); |
| } |
| |
| push({ type: 'bracket', value }); |
| continue; |
| } |
| |
| if (value === ']') { |
| if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { |
| push({ type: 'text', value, output: `\\${value}` }); |
| continue; |
| } |
| |
| if (state.brackets === 0) { |
| if (opts.strictBrackets === true) { |
| throw new SyntaxError(syntaxError('opening', '[')); |
| } |
| |
| push({ type: 'text', value, output: `\\${value}` }); |
| continue; |
| } |
| |
| decrement('brackets'); |
| |
| const prevValue = prev.value.slice(1); |
| if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { |
| value = `/${value}`; |
| } |
| |
| prev.value += value; |
| append({ value }); |
| |
| // when literal brackets are explicitly disabled |
| // assume we should match with a regex character class |
| if (opts.literalBrackets === false || utils$1.hasRegexChars(prevValue)) { |
| continue; |
| } |
| |
| const escaped = utils$1.escapeRegex(prev.value); |
| state.output = state.output.slice(0, -prev.value.length); |
| |
| // when literal brackets are explicitly enabled |
| // assume we should escape the brackets to match literal characters |
| if (opts.literalBrackets === true) { |
| state.output += escaped; |
| prev.value = escaped; |
| continue; |
| } |
| |
| // when the user specifies nothing, try to match both |
| prev.value = `(${capture}${escaped}|${prev.value})`; |
| state.output += prev.value; |
| continue; |
| } |
| |
| /** |
| * Braces |
| */ |
| |
| if (value === '{' && opts.nobrace !== true) { |
| increment('braces'); |
| |
| const open = { |
| type: 'brace', |
| value, |
| output: '(', |
| outputIndex: state.output.length, |
| tokensIndex: state.tokens.length |
| }; |
| |
| braces.push(open); |
| push(open); |
| continue; |
| } |
| |
| if (value === '}') { |
| const brace = braces[braces.length - 1]; |
| |
| if (opts.nobrace === true || !brace) { |
| push({ type: 'text', value, output: value }); |
| continue; |
| } |
| |
| let output = ')'; |
| |
| if (brace.dots === true) { |
| const arr = tokens.slice(); |
| const range = []; |
| |
| for (let i = arr.length - 1; i >= 0; i--) { |
| tokens.pop(); |
| if (arr[i].type === 'brace') { |
| break; |
| } |
| if (arr[i].type !== 'dots') { |
| range.unshift(arr[i].value); |
| } |
| } |
| |
| output = expandRange(range, opts); |
| state.backtrack = true; |
| } |
| |
| if (brace.comma !== true && brace.dots !== true) { |
| const out = state.output.slice(0, brace.outputIndex); |
| const toks = state.tokens.slice(brace.tokensIndex); |
| brace.value = brace.output = '\\{'; |
| value = output = '\\}'; |
| state.output = out; |
| for (const t of toks) { |
| state.output += (t.output || t.value); |
| } |
| } |
| |
| push({ type: 'brace', value, output }); |
| decrement('braces'); |
| braces.pop(); |
| continue; |
| } |
| |
| /** |
| * Pipes |
| */ |
| |
| if (value === '|') { |
| if (extglobs.length > 0) { |
| extglobs[extglobs.length - 1].conditions++; |
| } |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Commas |
| */ |
| |
| if (value === ',') { |
| let output = value; |
| |
| const brace = braces[braces.length - 1]; |
| if (brace && stack[stack.length - 1] === 'braces') { |
| brace.comma = true; |
| output = '|'; |
| } |
| |
| push({ type: 'comma', value, output }); |
| continue; |
| } |
| |
| /** |
| * Slashes |
| */ |
| |
| if (value === '/') { |
| // if the beginning of the glob is "./", advance the start |
| // to the current index, and don't add the "./" characters |
| // to the state. This greatly simplifies lookbehinds when |
| // checking for BOS characters like "!" and "." (not "./") |
| if (prev.type === 'dot' && state.index === state.start + 1) { |
| state.start = state.index + 1; |
| state.consumed = ''; |
| state.output = ''; |
| tokens.pop(); |
| prev = bos; // reset "prev" to the first token |
| continue; |
| } |
| |
| push({ type: 'slash', value, output: SLASH_LITERAL }); |
| continue; |
| } |
| |
| /** |
| * Dots |
| */ |
| |
| if (value === '.') { |
| if (state.braces > 0 && prev.type === 'dot') { |
| if (prev.value === '.') prev.output = DOT_LITERAL; |
| const brace = braces[braces.length - 1]; |
| prev.type = 'dots'; |
| prev.output += value; |
| prev.value += value; |
| brace.dots = true; |
| continue; |
| } |
| |
| if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') { |
| push({ type: 'text', value, output: DOT_LITERAL }); |
| continue; |
| } |
| |
| push({ type: 'dot', value, output: DOT_LITERAL }); |
| continue; |
| } |
| |
| /** |
| * Question marks |
| */ |
| |
| if (value === '?') { |
| const isGroup = prev && prev.value === '('; |
| if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| extglobOpen('qmark', value); |
| continue; |
| } |
| |
| if (prev && prev.type === 'paren') { |
| const next = peek(); |
| let output = value; |
| |
| if (next === '<' && !utils$1.supportsLookbehinds()) { |
| throw new Error('Node.js v10 or higher is required for regex lookbehinds'); |
| } |
| |
| if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) { |
| output = `\\${value}`; |
| } |
| |
| push({ type: 'text', value, output }); |
| continue; |
| } |
| |
| if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { |
| push({ type: 'qmark', value, output: QMARK_NO_DOT }); |
| continue; |
| } |
| |
| push({ type: 'qmark', value, output: QMARK }); |
| continue; |
| } |
| |
| /** |
| * Exclamation |
| */ |
| |
| if (value === '!') { |
| if (opts.noextglob !== true && peek() === '(') { |
| if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { |
| extglobOpen('negate', value); |
| continue; |
| } |
| } |
| |
| if (opts.nonegate !== true && state.index === 0) { |
| negate(); |
| continue; |
| } |
| } |
| |
| /** |
| * Plus |
| */ |
| |
| if (value === '+') { |
| if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| extglobOpen('plus', value); |
| continue; |
| } |
| |
| if ((prev && prev.value === '(') || opts.regex === false) { |
| push({ type: 'plus', value, output: PLUS_LITERAL }); |
| continue; |
| } |
| |
| if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) { |
| push({ type: 'plus', value }); |
| continue; |
| } |
| |
| push({ type: 'plus', value: PLUS_LITERAL }); |
| continue; |
| } |
| |
| /** |
| * Plain text |
| */ |
| |
| if (value === '@') { |
| if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| push({ type: 'at', extglob: true, value, output: '' }); |
| continue; |
| } |
| |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Plain text |
| */ |
| |
| if (value !== '*') { |
| if (value === '$' || value === '^') { |
| value = `\\${value}`; |
| } |
| |
| const match = REGEX_NON_SPECIAL_CHARS.exec(remaining()); |
| if (match) { |
| value += match[0]; |
| state.index += match[0].length; |
| } |
| |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Stars |
| */ |
| |
| if (prev && (prev.type === 'globstar' || prev.star === true)) { |
| prev.type = 'star'; |
| prev.star = true; |
| prev.value += value; |
| prev.output = star; |
| state.backtrack = true; |
| state.globstar = true; |
| consume(value); |
| continue; |
| } |
| |
| let rest = remaining(); |
| if (opts.noextglob !== true && /^\([^?]/.test(rest)) { |
| extglobOpen('star', value); |
| continue; |
| } |
| |
| if (prev.type === 'star') { |
| if (opts.noglobstar === true) { |
| consume(value); |
| continue; |
| } |
| |
| const prior = prev.prev; |
| const before = prior.prev; |
| const isStart = prior.type === 'slash' || prior.type === 'bos'; |
| const afterStar = before && (before.type === 'star' || before.type === 'globstar'); |
| |
| if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) { |
| push({ type: 'star', value, output: '' }); |
| continue; |
| } |
| |
| const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); |
| const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); |
| if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { |
| push({ type: 'star', value, output: '' }); |
| continue; |
| } |
| |
| // strip consecutive `/**/` |
| while (rest.slice(0, 3) === '/**') { |
| const after = input[state.index + 4]; |
| if (after && after !== '/') { |
| break; |
| } |
| rest = rest.slice(3); |
| consume('/**', 3); |
| } |
| |
| if (prior.type === 'bos' && eos()) { |
| prev.type = 'globstar'; |
| prev.value += value; |
| prev.output = globstar(opts); |
| state.output = prev.output; |
| state.globstar = true; |
| consume(value); |
| continue; |
| } |
| |
| if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { |
| state.output = state.output.slice(0, -(prior.output + prev.output).length); |
| prior.output = `(?:${prior.output}`; |
| |
| prev.type = 'globstar'; |
| prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)'); |
| prev.value += value; |
| state.globstar = true; |
| state.output += prior.output + prev.output; |
| consume(value); |
| continue; |
| } |
| |
| if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') { |
| const end = rest[1] !== void 0 ? '|$' : ''; |
| |
| state.output = state.output.slice(0, -(prior.output + prev.output).length); |
| prior.output = `(?:${prior.output}`; |
| |
| prev.type = 'globstar'; |
| prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; |
| prev.value += value; |
| |
| state.output += prior.output + prev.output; |
| state.globstar = true; |
| |
| consume(value + advance()); |
| |
| push({ type: 'slash', value: '/', output: '' }); |
| continue; |
| } |
| |
| if (prior.type === 'bos' && rest[0] === '/') { |
| prev.type = 'globstar'; |
| prev.value += value; |
| prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; |
| state.output = prev.output; |
| state.globstar = true; |
| consume(value + advance()); |
| push({ type: 'slash', value: '/', output: '' }); |
| continue; |
| } |
| |
| // remove single star from output |
| state.output = state.output.slice(0, -prev.output.length); |
| |
| // reset previous token to globstar |
| prev.type = 'globstar'; |
| prev.output = globstar(opts); |
| prev.value += value; |
| |
| // reset output with globstar |
| state.output += prev.output; |
| state.globstar = true; |
| consume(value); |
| continue; |
| } |
| |
| const token = { type: 'star', value, output: star }; |
| |
| if (opts.bash === true) { |
| token.output = '.*?'; |
| if (prev.type === 'bos' || prev.type === 'slash') { |
| token.output = nodot + token.output; |
| } |
| push(token); |
| continue; |
| } |
| |
| if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { |
| token.output = value; |
| push(token); |
| continue; |
| } |
| |
| if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { |
| if (prev.type === 'dot') { |
| state.output += NO_DOT_SLASH; |
| prev.output += NO_DOT_SLASH; |
| |
| } else if (opts.dot === true) { |
| state.output += NO_DOTS_SLASH; |
| prev.output += NO_DOTS_SLASH; |
| |
| } else { |
| state.output += nodot; |
| prev.output += nodot; |
| } |
| |
| if (peek() !== '*') { |
| state.output += ONE_CHAR; |
| prev.output += ONE_CHAR; |
| } |
| } |
| |
| push(token); |
| } |
| |
| while (state.brackets > 0) { |
| if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); |
| state.output = utils$1.escapeLast(state.output, '['); |
| decrement('brackets'); |
| } |
| |
| while (state.parens > 0) { |
| if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); |
| state.output = utils$1.escapeLast(state.output, '('); |
| decrement('parens'); |
| } |
| |
| while (state.braces > 0) { |
| if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); |
| state.output = utils$1.escapeLast(state.output, '{'); |
| decrement('braces'); |
| } |
| |
| if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { |
| push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); |
| } |
| |
| // rebuild the output if we had to backtrack at any point |
| if (state.backtrack === true) { |
| state.output = ''; |
| |
| for (const token of state.tokens) { |
| state.output += token.output != null ? token.output : token.value; |
| |
| if (token.suffix) { |
| state.output += token.suffix; |
| } |
| } |
| } |
| |
| return state; |
| }; |
| |
| /** |
| * Fast paths for creating regular expressions for common glob patterns. |
| * This can significantly speed up processing and has very little downside |
| * impact when none of the fast paths match. |
| */ |
| |
| parse$1.fastpaths = (input, options) => { |
| const opts = { ...options }; |
| const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; |
| const len = input.length; |
| if (len > max) { |
| throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); |
| } |
| |
| input = REPLACEMENTS[input] || input; |
| const win32 = utils$1.isWindows(options); |
| |
| // create constants based on platform, for windows or posix |
| const { |
| DOT_LITERAL, |
| SLASH_LITERAL, |
| ONE_CHAR, |
| DOTS_SLASH, |
| NO_DOT, |
| NO_DOTS, |
| NO_DOTS_SLASH, |
| STAR, |
| START_ANCHOR |
| } = constants$2.globChars(win32); |
| |
| const nodot = opts.dot ? NO_DOTS : NO_DOT; |
| const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; |
| const capture = opts.capture ? '' : '?:'; |
| const state = { negated: false, prefix: '' }; |
| let star = opts.bash === true ? '.*?' : STAR; |
| |
| if (opts.capture) { |
| star = `(${star})`; |
| } |
| |
| const globstar = opts => { |
| if (opts.noglobstar === true) return star; |
| return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; |
| }; |
| |
| const create = str => { |
| switch (str) { |
| case '*': |
| return `${nodot}${ONE_CHAR}${star}`; |
| |
| case '.*': |
| return `${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| case '*.*': |
| return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| case '*/*': |
| return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; |
| |
| case '**': |
| return nodot + globstar(opts); |
| |
| case '**/*': |
| return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; |
| |
| case '**/*.*': |
| return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| case '**/.*': |
| return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| default: { |
| const match = /^(.*?)\.(\w+)$/.exec(str); |
| if (!match) return; |
| |
| const source = create(match[1]); |
| if (!source) return; |
| |
| return source + DOT_LITERAL + match[2]; |
| } |
| } |
| }; |
| |
| const output = utils$1.removePrefix(input, state); |
| let source = create(output); |
| |
| if (source && opts.strictSlashes !== true) { |
| source += `${SLASH_LITERAL}?`; |
| } |
| |
| return source; |
| }; |
| |
| var parse_1 = parse$1; |
| |
| const path$1 = path$3; |
| const scan = scan_1; |
| const parse = parse_1; |
| const utils = utils$3; |
| const constants$1 = constants$3; |
| const isObject = val => val && typeof val === 'object' && !Array.isArray(val); |
| |
| /** |
| * Creates a matcher function from one or more glob patterns. The |
| * returned function takes a string to match as its first argument, |
| * and returns true if the string is a match. The returned matcher |
| * function also takes a boolean as the second argument that, when true, |
| * returns an object with additional information. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * // picomatch(glob[, options]); |
| * |
| * const isMatch = picomatch('*.!(*a)'); |
| * console.log(isMatch('a.a')); //=> false |
| * console.log(isMatch('a.b')); //=> true |
| * ``` |
| * @name picomatch |
| * @param {String|Array} `globs` One or more glob patterns. |
| * @param {Object=} `options` |
| * @return {Function=} Returns a matcher function. |
| * @api public |
| */ |
| |
| const picomatch$3 = (glob, options, returnState = false) => { |
| if (Array.isArray(glob)) { |
| const fns = glob.map(input => picomatch$3(input, options, returnState)); |
| const arrayMatcher = str => { |
| for (const isMatch of fns) { |
| const state = isMatch(str); |
| if (state) return state; |
| } |
| return false; |
| }; |
| return arrayMatcher; |
| } |
| |
| const isState = isObject(glob) && glob.tokens && glob.input; |
| |
| if (glob === '' || (typeof glob !== 'string' && !isState)) { |
| throw new TypeError('Expected pattern to be a non-empty string'); |
| } |
| |
| const opts = options || {}; |
| const posix = utils.isWindows(options); |
| const regex = isState |
| ? picomatch$3.compileRe(glob, options) |
| : picomatch$3.makeRe(glob, options, false, true); |
| |
| const state = regex.state; |
| delete regex.state; |
| |
| let isIgnored = () => false; |
| if (opts.ignore) { |
| const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; |
| isIgnored = picomatch$3(opts.ignore, ignoreOpts, returnState); |
| } |
| |
| const matcher = (input, returnObject = false) => { |
| const { isMatch, match, output } = picomatch$3.test(input, regex, options, { glob, posix }); |
| const result = { glob, state, regex, posix, input, output, match, isMatch }; |
| |
| if (typeof opts.onResult === 'function') { |
| opts.onResult(result); |
| } |
| |
| if (isMatch === false) { |
| result.isMatch = false; |
| return returnObject ? result : false; |
| } |
| |
| if (isIgnored(input)) { |
| if (typeof opts.onIgnore === 'function') { |
| opts.onIgnore(result); |
| } |
| result.isMatch = false; |
| return returnObject ? result : false; |
| } |
| |
| if (typeof opts.onMatch === 'function') { |
| opts.onMatch(result); |
| } |
| return returnObject ? result : true; |
| }; |
| |
| if (returnState) { |
| matcher.state = state; |
| } |
| |
| return matcher; |
| }; |
| |
| /** |
| * Test `input` with the given `regex`. This is used by the main |
| * `picomatch()` function to test the input string. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * // picomatch.test(input, regex[, options]); |
| * |
| * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); |
| * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } |
| * ``` |
| * @param {String} `input` String to test. |
| * @param {RegExp} `regex` |
| * @return {Object} Returns an object with matching info. |
| * @api public |
| */ |
| |
| picomatch$3.test = (input, regex, options, { glob, posix } = {}) => { |
| if (typeof input !== 'string') { |
| throw new TypeError('Expected input to be a string'); |
| } |
| |
| if (input === '') { |
| return { isMatch: false, output: '' }; |
| } |
| |
| const opts = options || {}; |
| const format = opts.format || (posix ? utils.toPosixSlashes : null); |
| let match = input === glob; |
| let output = (match && format) ? format(input) : input; |
| |
| if (match === false) { |
| output = format ? format(input) : input; |
| match = output === glob; |
| } |
| |
| if (match === false || opts.capture === true) { |
| if (opts.matchBase === true || opts.basename === true) { |
| match = picomatch$3.matchBase(input, regex, options, posix); |
| } else { |
| match = regex.exec(output); |
| } |
| } |
| |
| return { isMatch: Boolean(match), match, output }; |
| }; |
| |
| /** |
| * Match the basename of a filepath. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * // picomatch.matchBase(input, glob[, options]); |
| * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true |
| * ``` |
| * @param {String} `input` String to test. |
| * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). |
| * @return {Boolean} |
| * @api public |
| */ |
| |
| picomatch$3.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { |
| const regex = glob instanceof RegExp ? glob : picomatch$3.makeRe(glob, options); |
| return regex.test(path$1.basename(input)); |
| }; |
| |
| /** |
| * Returns true if **any** of the given glob `patterns` match the specified `string`. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * // picomatch.isMatch(string, patterns[, options]); |
| * |
| * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true |
| * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false |
| * ``` |
| * @param {String|Array} str The string to test. |
| * @param {String|Array} patterns One or more glob patterns to use for matching. |
| * @param {Object} [options] See available [options](#options). |
| * @return {Boolean} Returns true if any patterns match `str` |
| * @api public |
| */ |
| |
| picomatch$3.isMatch = (str, patterns, options) => picomatch$3(patterns, options)(str); |
| |
| /** |
| * Parse a glob pattern to create the source string for a regular |
| * expression. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * const result = picomatch.parse(pattern[, options]); |
| * ``` |
| * @param {String} `pattern` |
| * @param {Object} `options` |
| * @return {Object} Returns an object with useful properties and output to be used as a regex source string. |
| * @api public |
| */ |
| |
| picomatch$3.parse = (pattern, options) => { |
| if (Array.isArray(pattern)) return pattern.map(p => picomatch$3.parse(p, options)); |
| return parse(pattern, { ...options, fastpaths: false }); |
| }; |
| |
| /** |
| * Scan a glob pattern to separate the pattern into segments. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * // picomatch.scan(input[, options]); |
| * |
| * const result = picomatch.scan('!./foo/*.js'); |
| * console.log(result); |
| * { prefix: '!./', |
| * input: '!./foo/*.js', |
| * start: 3, |
| * base: 'foo', |
| * glob: '*.js', |
| * isBrace: false, |
| * isBracket: false, |
| * isGlob: true, |
| * isExtglob: false, |
| * isGlobstar: false, |
| * negated: true } |
| * ``` |
| * @param {String} `input` Glob pattern to scan. |
| * @param {Object} `options` |
| * @return {Object} Returns an object with |
| * @api public |
| */ |
| |
| picomatch$3.scan = (input, options) => scan(input, options); |
| |
| /** |
| * Compile a regular expression from the `state` object returned by the |
| * [parse()](#parse) method. |
| * |
| * @param {Object} `state` |
| * @param {Object} `options` |
| * @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser. |
| * @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging. |
| * @return {RegExp} |
| * @api public |
| */ |
| |
| picomatch$3.compileRe = (state, options, returnOutput = false, returnState = false) => { |
| if (returnOutput === true) { |
| return state.output; |
| } |
| |
| const opts = options || {}; |
| const prepend = opts.contains ? '' : '^'; |
| const append = opts.contains ? '' : '$'; |
| |
| let source = `${prepend}(?:${state.output})${append}`; |
| if (state && state.negated === true) { |
| source = `^(?!${source}).*$`; |
| } |
| |
| const regex = picomatch$3.toRegex(source, options); |
| if (returnState === true) { |
| regex.state = state; |
| } |
| |
| return regex; |
| }; |
| |
| /** |
| * Create a regular expression from a parsed glob pattern. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * const state = picomatch.parse('*.js'); |
| * // picomatch.compileRe(state[, options]); |
| * |
| * console.log(picomatch.compileRe(state)); |
| * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ |
| * ``` |
| * @param {String} `state` The object returned from the `.parse` method. |
| * @param {Object} `options` |
| * @param {Boolean} `returnOutput` Implementors may use this argument to return the compiled output, instead of a regular expression. This is not exposed on the options to prevent end-users from mutating the result. |
| * @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression. |
| * @return {RegExp} Returns a regex created from the given pattern. |
| * @api public |
| */ |
| |
| picomatch$3.makeRe = (input, options = {}, returnOutput = false, returnState = false) => { |
| if (!input || typeof input !== 'string') { |
| throw new TypeError('Expected a non-empty string'); |
| } |
| |
| let parsed = { negated: false, fastpaths: true }; |
| |
| if (options.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { |
| parsed.output = parse.fastpaths(input, options); |
| } |
| |
| if (!parsed.output) { |
| parsed = parse(input, options); |
| } |
| |
| return picomatch$3.compileRe(parsed, options, returnOutput, returnState); |
| }; |
| |
| /** |
| * Create a regular expression from the given regex source string. |
| * |
| * ```js |
| * const picomatch = require('picomatch'); |
| * // picomatch.toRegex(source[, options]); |
| * |
| * const { output } = picomatch.parse('*.js'); |
| * console.log(picomatch.toRegex(output)); |
| * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ |
| * ``` |
| * @param {String} `source` Regular expression source string. |
| * @param {Object} `options` |
| * @return {RegExp} |
| * @api public |
| */ |
| |
| picomatch$3.toRegex = (source, options) => { |
| try { |
| const opts = options || {}; |
| return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); |
| } catch (err) { |
| if (options && options.debug === true) throw err; |
| return /$^/; |
| } |
| }; |
| |
| /** |
| * Picomatch constants. |
| * @return {Object} |
| */ |
| |
| picomatch$3.constants = constants$1; |
| |
| /** |
| * Expose "picomatch" |
| */ |
| |
| var picomatch_1 = picomatch$3; |
| |
| var picomatch$2 = picomatch_1; |
| |
| var chokidar$1 = {}; |
| |
| const fs$3 = fs$4; |
| const { Readable } = require$$1; |
| const sysPath$3 = path$3; |
| const { promisify: promisify$3 } = require$$0$1; |
| const picomatch$1 = picomatch$2; |
| |
| const readdir$1 = promisify$3(fs$3.readdir); |
| const stat$3 = promisify$3(fs$3.stat); |
| const lstat$2 = promisify$3(fs$3.lstat); |
| const realpath$1 = promisify$3(fs$3.realpath); |
| |
| /** |
| * @typedef {Object} EntryInfo |
| * @property {String} path |
| * @property {String} fullPath |
| * @property {fs.Stats=} stats |
| * @property {fs.Dirent=} dirent |
| * @property {String} basename |
| */ |
| |
| const BANG$2 = '!'; |
| const RECURSIVE_ERROR_CODE = 'READDIRP_RECURSIVE_ERROR'; |
| const NORMAL_FLOW_ERRORS = new Set(['ENOENT', 'EPERM', 'EACCES', 'ELOOP', RECURSIVE_ERROR_CODE]); |
| const FILE_TYPE = 'files'; |
| const DIR_TYPE = 'directories'; |
| const FILE_DIR_TYPE = 'files_directories'; |
| const EVERYTHING_TYPE = 'all'; |
| const ALL_TYPES = [FILE_TYPE, DIR_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE]; |
| |
| const isNormalFlowError = error => NORMAL_FLOW_ERRORS.has(error.code); |
| const [maj, min] = process.versions.node.split('.').slice(0, 2).map(n => Number.parseInt(n, 10)); |
| const wantBigintFsStats = process.platform === 'win32' && (maj > 10 || (maj === 10 && min >= 5)); |
| |
| const normalizeFilter = filter => { |
| if (filter === undefined) return; |
| if (typeof filter === 'function') return filter; |
| |
| if (typeof filter === 'string') { |
| const glob = picomatch$1(filter.trim()); |
| return entry => glob(entry.basename); |
| } |
| |
| if (Array.isArray(filter)) { |
| const positive = []; |
| const negative = []; |
| for (const item of filter) { |
| const trimmed = item.trim(); |
| if (trimmed.charAt(0) === BANG$2) { |
| negative.push(picomatch$1(trimmed.slice(1))); |
| } else { |
| positive.push(picomatch$1(trimmed)); |
| } |
| } |
| |
| if (negative.length > 0) { |
| if (positive.length > 0) { |
| return entry => |
| positive.some(f => f(entry.basename)) && !negative.some(f => f(entry.basename)); |
| } |
| return entry => !negative.some(f => f(entry.basename)); |
| } |
| return entry => positive.some(f => f(entry.basename)); |
| } |
| }; |
| |
| class ReaddirpStream extends Readable { |
| static get defaultOptions() { |
| return { |
| root: '.', |
| /* eslint-disable no-unused-vars */ |
| fileFilter: (path) => true, |
| directoryFilter: (path) => true, |
| /* eslint-enable no-unused-vars */ |
| type: FILE_TYPE, |
| lstat: false, |
| depth: 2147483648, |
| alwaysStat: false |
| }; |
| } |
| |
| constructor(options = {}) { |
| super({ |
| objectMode: true, |
| autoDestroy: true, |
| highWaterMark: options.highWaterMark || 4096 |
| }); |
| const opts = { ...ReaddirpStream.defaultOptions, ...options }; |
| const { root, type } = opts; |
| |
| this._fileFilter = normalizeFilter(opts.fileFilter); |
| this._directoryFilter = normalizeFilter(opts.directoryFilter); |
| |
| const statMethod = opts.lstat ? lstat$2 : stat$3; |
| // Use bigint stats if it's windows and stat() supports options (node 10+). |
| if (wantBigintFsStats) { |
| this._stat = path => statMethod(path, { bigint: true }); |
| } else { |
| this._stat = statMethod; |
| } |
| |
| this._maxDepth = opts.depth; |
| this._wantsDir = [DIR_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE].includes(type); |
| this._wantsFile = [FILE_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE].includes(type); |
| this._wantsEverything = type === EVERYTHING_TYPE; |
| this._root = sysPath$3.resolve(root); |
| this._isDirent = ('Dirent' in fs$3) && !opts.alwaysStat; |
| this._statsProp = this._isDirent ? 'dirent' : 'stats'; |
| this._rdOptions = { encoding: 'utf8', withFileTypes: this._isDirent }; |
| |
| // Launch stream with one parent, the root dir. |
| this.parents = [this._exploreDir(root, 1)]; |
| this.reading = false; |
| this.parent = undefined; |
| } |
| |
| async _read(batch) { |
| if (this.reading) return; |
| this.reading = true; |
| |
| try { |
| while (!this.destroyed && batch > 0) { |
| const { path, depth, files = [] } = this.parent || {}; |
| |
| if (files.length > 0) { |
| const slice = files.splice(0, batch).map(dirent => this._formatEntry(dirent, path)); |
| for (const entry of await Promise.all(slice)) { |
| if (this.destroyed) return; |
| |
| const entryType = await this._getEntryType(entry); |
| if (entryType === 'directory' && this._directoryFilter(entry)) { |
| if (depth <= this._maxDepth) { |
| this.parents.push(this._exploreDir(entry.fullPath, depth + 1)); |
| } |
| |
| if (this._wantsDir) { |
| this.push(entry); |
| batch--; |
| } |
| } else if ((entryType === 'file' || this._includeAsFile(entry)) && this._fileFilter(entry)) { |
| if (this._wantsFile) { |
| this.push(entry); |
| batch--; |
| } |
| } |
| } |
| } else { |
| const parent = this.parents.pop(); |
| if (!parent) { |
| this.push(null); |
| break; |
| } |
| this.parent = await parent; |
| if (this.destroyed) return; |
| } |
| } |
| } catch (error) { |
| this.destroy(error); |
| } finally { |
| this.reading = false; |
| } |
| } |
| |
| async _exploreDir(path, depth) { |
| let files; |
| try { |
| files = await readdir$1(path, this._rdOptions); |
| } catch (error) { |
| this._onError(error); |
| } |
| return { files, depth, path }; |
| } |
| |
| async _formatEntry(dirent, path) { |
| let entry; |
| try { |
| const basename = this._isDirent ? dirent.name : dirent; |
| const fullPath = sysPath$3.resolve(sysPath$3.join(path, basename)); |
| entry = { path: sysPath$3.relative(this._root, fullPath), fullPath, basename }; |
| entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath); |
| } catch (err) { |
| this._onError(err); |
| } |
| return entry; |
| } |
| |
| _onError(err) { |
| if (isNormalFlowError(err) && !this.destroyed) { |
| this.emit('warn', err); |
| } else { |
| this.destroy(err); |
| } |
| } |
| |
| async _getEntryType(entry) { |
| // entry may be undefined, because a warning or an error were emitted |
| // and the statsProp is undefined |
| const stats = entry && entry[this._statsProp]; |
| if (!stats) { |
| return; |
| } |
| if (stats.isFile()) { |
| return 'file'; |
| } |
| if (stats.isDirectory()) { |
| return 'directory'; |
| } |
| if (stats && stats.isSymbolicLink()) { |
| const full = entry.fullPath; |
| try { |
| const entryRealPath = await realpath$1(full); |
| const entryRealPathStats = await lstat$2(entryRealPath); |
| if (entryRealPathStats.isFile()) { |
| return 'file'; |
| } |
| if (entryRealPathStats.isDirectory()) { |
| const len = entryRealPath.length; |
| if (full.startsWith(entryRealPath) && full.substr(len, 1) === sysPath$3.sep) { |
| const recursiveError = new Error( |
| `Circular symlink detected: "${full}" points to "${entryRealPath}"` |
| ); |
| recursiveError.code = RECURSIVE_ERROR_CODE; |
| return this._onError(recursiveError); |
| } |
| return 'directory'; |
| } |
| } catch (error) { |
| this._onError(error); |
| } |
| } |
| } |
| |
| _includeAsFile(entry) { |
| const stats = entry && entry[this._statsProp]; |
| |
| return stats && this._wantsEverything && !stats.isDirectory(); |
| } |
| } |
| |
| /** |
| * @typedef {Object} ReaddirpArguments |
| * @property {Function=} fileFilter |
| * @property {Function=} directoryFilter |
| * @property {String=} type |
| * @property {Number=} depth |
| * @property {String=} root |
| * @property {Boolean=} lstat |
| * @property {Boolean=} bigint |
| */ |
| |
| /** |
| * Main function which ends up calling readdirRec and reads all files and directories in given root recursively. |
| * @param {String} root Root directory |
| * @param {ReaddirpArguments=} options Options to specify root (start directory), filters and recursion depth |
| */ |
| const readdirp$1 = (root, options = {}) => { |
| let type = options.entryType || options.type; |
| if (type === 'both') type = FILE_DIR_TYPE; // backwards-compatibility |
| if (type) options.type = type; |
| if (!root) { |
| throw new Error('readdirp: root argument is required. Usage: readdirp(root, options)'); |
| } else if (typeof root !== 'string') { |
| throw new TypeError('readdirp: root argument must be a string. Usage: readdirp(root, options)'); |
| } else if (type && !ALL_TYPES.includes(type)) { |
| throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(', ')}`); |
| } |
| |
| options.root = root; |
| return new ReaddirpStream(options); |
| }; |
| |
| const readdirpPromise = (root, options = {}) => { |
| return new Promise((resolve, reject) => { |
| const files = []; |
| readdirp$1(root, options) |
| .on('data', entry => files.push(entry)) |
| .on('end', () => resolve(files)) |
| .on('error', error => reject(error)); |
| }); |
| }; |
| |
| readdirp$1.promise = readdirpPromise; |
| readdirp$1.ReaddirpStream = ReaddirpStream; |
| readdirp$1.default = readdirp$1; |
| |
| var readdirp_1 = readdirp$1; |
| |
| var anymatch$2 = {exports: {}}; |
| |
| /*! |
| * normalize-path <https://github.com/jonschlinkert/normalize-path> |
| * |
| * Copyright (c) 2014-2018, Jon Schlinkert. |
| * Released under the MIT License. |
| */ |
| |
| var normalizePath$2 = function(path, stripTrailing) { |
| if (typeof path !== 'string') { |
| throw new TypeError('expected path to be a string'); |
| } |
| |
| if (path === '\\' || path === '/') return '/'; |
| |
| var len = path.length; |
| if (len <= 1) return path; |
| |
| // ensure that win32 namespaces has two leading slashes, so that the path is |
| // handled properly by the win32 version of path.parse() after being normalized |
| // https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces |
| var prefix = ''; |
| if (len > 4 && path[3] === '\\') { |
| var ch = path[2]; |
| if ((ch === '?' || ch === '.') && path.slice(0, 2) === '\\\\') { |
| path = path.slice(2); |
| prefix = '//'; |
| } |
| } |
| |
| var segs = path.split(/[/\\]+/); |
| if (stripTrailing !== false && segs[segs.length - 1] === '') { |
| segs.pop(); |
| } |
| return prefix + segs.join('/'); |
| }; |
| |
| Object.defineProperty(anymatch$2.exports, "__esModule", { value: true }); |
| |
| const picomatch = picomatch$2; |
| const normalizePath$1 = normalizePath$2; |
| |
| /** |
| * @typedef {(testString: string) => boolean} AnymatchFn |
| * @typedef {string|RegExp|AnymatchFn} AnymatchPattern |
| * @typedef {AnymatchPattern|AnymatchPattern[]} AnymatchMatcher |
| */ |
| const BANG$1 = '!'; |
| const DEFAULT_OPTIONS = {returnIndex: false}; |
| const arrify$1 = (item) => Array.isArray(item) ? item : [item]; |
| |
| /** |
| * @param {AnymatchPattern} matcher |
| * @param {object} options |
| * @returns {AnymatchFn} |
| */ |
| const createPattern = (matcher, options) => { |
| if (typeof matcher === 'function') { |
| return matcher; |
| } |
| if (typeof matcher === 'string') { |
| const glob = picomatch(matcher, options); |
| return (string) => matcher === string || glob(string); |
| } |
| if (matcher instanceof RegExp) { |
| return (string) => matcher.test(string); |
| } |
| return (string) => false; |
| }; |
| |
| /** |
| * @param {Array<Function>} patterns |
| * @param {Array<Function>} negPatterns |
| * @param {String|Array} args |
| * @param {Boolean} returnIndex |
| * @returns {boolean|number} |
| */ |
| const matchPatterns = (patterns, negPatterns, args, returnIndex) => { |
| const isList = Array.isArray(args); |
| const _path = isList ? args[0] : args; |
| if (!isList && typeof _path !== 'string') { |
| throw new TypeError('anymatch: second argument must be a string: got ' + |
| Object.prototype.toString.call(_path)) |
| } |
| const path = normalizePath$1(_path); |
| |
| for (let index = 0; index < negPatterns.length; index++) { |
| const nglob = negPatterns[index]; |
| if (nglob(path)) { |
| return returnIndex ? -1 : false; |
| } |
| } |
| |
| const applied = isList && [path].concat(args.slice(1)); |
| for (let index = 0; index < patterns.length; index++) { |
| const pattern = patterns[index]; |
| if (isList ? pattern(...applied) : pattern(path)) { |
| return returnIndex ? index : true; |
| } |
| } |
| |
| return returnIndex ? -1 : false; |
| }; |
| |
| /** |
| * @param {AnymatchMatcher} matchers |
| * @param {Array|string} testString |
| * @param {object} options |
| * @returns {boolean|number|Function} |
| */ |
| const anymatch$1 = (matchers, testString, options = DEFAULT_OPTIONS) => { |
| if (matchers == null) { |
| throw new TypeError('anymatch: specify first argument'); |
| } |
| const opts = typeof options === 'boolean' ? {returnIndex: options} : options; |
| const returnIndex = opts.returnIndex || false; |
| |
| // Early cache for matchers. |
| const mtchers = arrify$1(matchers); |
| const negatedGlobs = mtchers |
| .filter(item => typeof item === 'string' && item.charAt(0) === BANG$1) |
| .map(item => item.slice(1)) |
| .map(item => picomatch(item, opts)); |
| const patterns = mtchers |
| .filter(item => typeof item !== 'string' || (typeof item === 'string' && item.charAt(0) !== BANG$1)) |
| .map(matcher => createPattern(matcher, opts)); |
| |
| if (testString == null) { |
| return (testString, ri = false) => { |
| const returnIndex = typeof ri === 'boolean' ? ri : false; |
| return matchPatterns(patterns, negatedGlobs, testString, returnIndex); |
| } |
| } |
| |
| return matchPatterns(patterns, negatedGlobs, testString, returnIndex); |
| }; |
| |
| anymatch$1.default = anymatch$1; |
| anymatch$2.exports = anymatch$1; |
| |
| /*! |
| * is-extglob <https://github.com/jonschlinkert/is-extglob> |
| * |
| * Copyright (c) 2014-2016, Jon Schlinkert. |
| * Licensed under the MIT License. |
| */ |
| |
| var isExtglob$1 = function isExtglob(str) { |
| if (typeof str !== 'string' || str === '') { |
| return false; |
| } |
| |
| var match; |
| while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { |
| if (match[2]) return true; |
| str = str.slice(match.index + match[0].length); |
| } |
| |
| return false; |
| }; |
| |
| /*! |
| * is-glob <https://github.com/jonschlinkert/is-glob> |
| * |
| * Copyright (c) 2014-2017, Jon Schlinkert. |
| * Released under the MIT License. |
| */ |
| |
| var isExtglob = isExtglob$1; |
| var chars = { '{': '}', '(': ')', '[': ']'}; |
| var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; |
| var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; |
| |
| var isGlob$2 = function isGlob(str, options) { |
| if (typeof str !== 'string' || str === '') { |
| return false; |
| } |
| |
| if (isExtglob(str)) { |
| return true; |
| } |
| |
| var regex = strictRegex; |
| var match; |
| |
| // optionally relax regex |
| if (options && options.strict === false) { |
| regex = relaxedRegex; |
| } |
| |
| while ((match = regex.exec(str))) { |
| if (match[2]) return true; |
| var idx = match.index + match[0].length; |
| |
| // if an open bracket/brace/paren is escaped, |
| // set the index to the next closing character |
| var open = match[1]; |
| var close = open ? chars[open] : null; |
| if (open && close) { |
| var n = str.indexOf(close, idx); |
| if (n !== -1) { |
| idx = n + 1; |
| } |
| } |
| |
| str = str.slice(idx); |
| } |
| return false; |
| }; |
| |
| var isGlob$1 = isGlob$2; |
| var pathPosixDirname = path$3.posix.dirname; |
| var isWin32 = require$$2.platform() === 'win32'; |
| |
| var slash = '/'; |
| var backslash = /\\/g; |
| var enclosure = /[\{\[].*[\}\]]$/; |
| var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; |
| var escaped = /\\([\!\*\?\|\[\]\(\)\{\}])/g; |
| |
| /** |
| * @param {string} str |
| * @param {Object} opts |
| * @param {boolean} [opts.flipBackslashes=true] |
| * @returns {string} |
| */ |
| var globParent$1 = function globParent(str, opts) { |
| var options = Object.assign({ flipBackslashes: true }, opts); |
| |
| // flip windows path separators |
| if (options.flipBackslashes && isWin32 && str.indexOf(slash) < 0) { |
| str = str.replace(backslash, slash); |
| } |
| |
| // special case for strings ending in enclosure containing path separator |
| if (enclosure.test(str)) { |
| str += slash; |
| } |
| |
| // preserves full path in case of trailing path separator |
| str += 'a'; |
| |
| // remove path parts that are globby |
| do { |
| str = pathPosixDirname(str); |
| } while (isGlob$1(str) || globby.test(str)); |
| |
| // remove escape chars and return result |
| return str.replace(escaped, '$1'); |
| }; |
| |
| var require$$0 = [ |
| "3dm", |
| "3ds", |
| "3g2", |
| "3gp", |
| "7z", |
| "a", |
| "aac", |
| "adp", |
| "ai", |
| "aif", |
| "aiff", |
| "alz", |
| "ape", |
| "apk", |
| "appimage", |
| "ar", |
| "arj", |
| "asf", |
| "au", |
| "avi", |
| "bak", |
| "baml", |
| "bh", |
| "bin", |
| "bk", |
| "bmp", |
| "btif", |
| "bz2", |
| "bzip2", |
| "cab", |
| "caf", |
| "cgm", |
| "class", |
| "cmx", |
| "cpio", |
| "cr2", |
| "cur", |
| "dat", |
| "dcm", |
| "deb", |
| "dex", |
| "djvu", |
| "dll", |
| "dmg", |
| "dng", |
| "doc", |
| "docm", |
| "docx", |
| "dot", |
| "dotm", |
| "dra", |
| "DS_Store", |
| "dsk", |
| "dts", |
| "dtshd", |
| "dvb", |
| "dwg", |
| "dxf", |
| "ecelp4800", |
| "ecelp7470", |
| "ecelp9600", |
| "egg", |
| "eol", |
| "eot", |
| "epub", |
| "exe", |
| "f4v", |
| "fbs", |
| "fh", |
| "fla", |
| "flac", |
| "flatpak", |
| "fli", |
| "flv", |
| "fpx", |
| "fst", |
| "fvt", |
| "g3", |
| "gh", |
| "gif", |
| "graffle", |
| "gz", |
| "gzip", |
| "h261", |
| "h263", |
| "h264", |
| "icns", |
| "ico", |
| "ief", |
| "img", |
| "ipa", |
| "iso", |
| "jar", |
| "jpeg", |
| "jpg", |
| "jpgv", |
| "jpm", |
| "jxr", |
| "key", |
| "ktx", |
| "lha", |
| "lib", |
| "lvp", |
| "lz", |
| "lzh", |
| "lzma", |
| "lzo", |
| "m3u", |
| "m4a", |
| "m4v", |
| "mar", |
| "mdi", |
| "mht", |
| "mid", |
| "midi", |
| "mj2", |
| "mka", |
| "mkv", |
| "mmr", |
| "mng", |
| "mobi", |
| "mov", |
| "movie", |
| "mp3", |
| "mp4", |
| "mp4a", |
| "mpeg", |
| "mpg", |
| "mpga", |
| "mxu", |
| "nef", |
| "npx", |
| "numbers", |
| "nupkg", |
| "o", |
| "odp", |
| "ods", |
| "odt", |
| "oga", |
| "ogg", |
| "ogv", |
| "otf", |
| "ott", |
| "pages", |
| "pbm", |
| "pcx", |
| "pdb", |
| "pdf", |
| "pea", |
| "pgm", |
| "pic", |
| "png", |
| "pnm", |
| "pot", |
| "potm", |
| "potx", |
| "ppa", |
| "ppam", |
| "ppm", |
| "pps", |
| "ppsm", |
| "ppsx", |
| "ppt", |
| "pptm", |
| "pptx", |
| "psd", |
| "pya", |
| "pyc", |
| "pyo", |
| "pyv", |
| "qt", |
| "rar", |
| "ras", |
| "raw", |
| "resources", |
| "rgb", |
| "rip", |
| "rlc", |
| "rmf", |
| "rmvb", |
| "rpm", |
| "rtf", |
| "rz", |
| "s3m", |
| "s7z", |
| "scpt", |
| "sgi", |
| "shar", |
| "snap", |
| "sil", |
| "sketch", |
| "slk", |
| "smv", |
| "snk", |
| "so", |
| "stl", |
| "suo", |
| "sub", |
| "swf", |
| "tar", |
| "tbz", |
| "tbz2", |
| "tga", |
| "tgz", |
| "thmx", |
| "tif", |
| "tiff", |
| "tlz", |
| "ttc", |
| "ttf", |
| "txz", |
| "udf", |
| "uvh", |
| "uvi", |
| "uvm", |
| "uvp", |
| "uvs", |
| "uvu", |
| "viv", |
| "vob", |
| "war", |
| "wav", |
| "wax", |
| "wbmp", |
| "wdp", |
| "weba", |
| "webm", |
| "webp", |
| "whl", |
| "wim", |
| "wm", |
| "wma", |
| "wmv", |
| "wmx", |
| "woff", |
| "woff2", |
| "wrm", |
| "wvx", |
| "xbm", |
| "xif", |
| "xla", |
| "xlam", |
| "xls", |
| "xlsb", |
| "xlsm", |
| "xlsx", |
| "xlt", |
| "xltm", |
| "xltx", |
| "xm", |
| "xmind", |
| "xpi", |
| "xpm", |
| "xwd", |
| "xz", |
| "z", |
| "zip", |
| "zipx" |
| ]; |
| |
| var binaryExtensions$1 = require$$0; |
| |
| const path = path$3; |
| const binaryExtensions = binaryExtensions$1; |
| |
| const extensions = new Set(binaryExtensions); |
| |
| var isBinaryPath$1 = filePath => extensions.has(path.extname(filePath).slice(1).toLowerCase()); |
| |
| var constants = {}; |
| |
| (function (exports) { |
| |
| const {sep} = path$3; |
| const {platform} = process; |
| const os = require$$2; |
| |
| exports.EV_ALL = 'all'; |
| exports.EV_READY = 'ready'; |
| exports.EV_ADD = 'add'; |
| exports.EV_CHANGE = 'change'; |
| exports.EV_ADD_DIR = 'addDir'; |
| exports.EV_UNLINK = 'unlink'; |
| exports.EV_UNLINK_DIR = 'unlinkDir'; |
| exports.EV_RAW = 'raw'; |
| exports.EV_ERROR = 'error'; |
| |
| exports.STR_DATA = 'data'; |
| exports.STR_END = 'end'; |
| exports.STR_CLOSE = 'close'; |
| |
| exports.FSEVENT_CREATED = 'created'; |
| exports.FSEVENT_MODIFIED = 'modified'; |
| exports.FSEVENT_DELETED = 'deleted'; |
| exports.FSEVENT_MOVED = 'moved'; |
| exports.FSEVENT_CLONED = 'cloned'; |
| exports.FSEVENT_UNKNOWN = 'unknown'; |
| exports.FSEVENT_TYPE_FILE = 'file'; |
| exports.FSEVENT_TYPE_DIRECTORY = 'directory'; |
| exports.FSEVENT_TYPE_SYMLINK = 'symlink'; |
| |
| exports.KEY_LISTENERS = 'listeners'; |
| exports.KEY_ERR = 'errHandlers'; |
| exports.KEY_RAW = 'rawEmitters'; |
| exports.HANDLER_KEYS = [exports.KEY_LISTENERS, exports.KEY_ERR, exports.KEY_RAW]; |
| |
| exports.DOT_SLASH = `.${sep}`; |
| |
| exports.BACK_SLASH_RE = /\\/g; |
| exports.DOUBLE_SLASH_RE = /\/\//; |
| exports.SLASH_OR_BACK_SLASH_RE = /[/\\]/; |
| exports.DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/; |
| exports.REPLACER_RE = /^\.[/\\]/; |
| |
| exports.SLASH = '/'; |
| exports.SLASH_SLASH = '//'; |
| exports.BRACE_START = '{'; |
| exports.BANG = '!'; |
| exports.ONE_DOT = '.'; |
| exports.TWO_DOTS = '..'; |
| exports.STAR = '*'; |
| exports.GLOBSTAR = '**'; |
| exports.ROOT_GLOBSTAR = '/**/*'; |
| exports.SLASH_GLOBSTAR = '/**'; |
| exports.DIR_SUFFIX = 'Dir'; |
| exports.ANYMATCH_OPTS = {dot: true}; |
| exports.STRING_TYPE = 'string'; |
| exports.FUNCTION_TYPE = 'function'; |
| exports.EMPTY_STR = ''; |
| exports.EMPTY_FN = () => {}; |
| exports.IDENTITY_FN = val => val; |
| |
| exports.isWindows = platform === 'win32'; |
| exports.isMacos = platform === 'darwin'; |
| exports.isLinux = platform === 'linux'; |
| exports.isIBMi = os.type() === 'OS400'; |
| }(constants)); |
| |
| const fs$2 = fs$4; |
| const sysPath$2 = path$3; |
| const { promisify: promisify$2 } = require$$0$1; |
| const isBinaryPath = isBinaryPath$1; |
| const { |
| isWindows: isWindows$1, |
| isLinux, |
| EMPTY_FN: EMPTY_FN$2, |
| EMPTY_STR: EMPTY_STR$1, |
| KEY_LISTENERS, |
| KEY_ERR, |
| KEY_RAW, |
| HANDLER_KEYS, |
| EV_CHANGE: EV_CHANGE$2, |
| EV_ADD: EV_ADD$2, |
| EV_ADD_DIR: EV_ADD_DIR$2, |
| EV_ERROR: EV_ERROR$2, |
| STR_DATA: STR_DATA$1, |
| STR_END: STR_END$2, |
| BRACE_START: BRACE_START$1, |
| STAR |
| } = constants; |
| |
| const THROTTLE_MODE_WATCH = 'watch'; |
| |
| const open = promisify$2(fs$2.open); |
| const stat$2 = promisify$2(fs$2.stat); |
| const lstat$1 = promisify$2(fs$2.lstat); |
| const close = promisify$2(fs$2.close); |
| const fsrealpath = promisify$2(fs$2.realpath); |
| |
| const statMethods$1 = { lstat: lstat$1, stat: stat$2 }; |
| |
| // TODO: emit errors properly. Example: EMFILE on Macos. |
| const foreach = (val, fn) => { |
| if (val instanceof Set) { |
| val.forEach(fn); |
| } else { |
| fn(val); |
| } |
| }; |
| |
| const addAndConvert = (main, prop, item) => { |
| let container = main[prop]; |
| if (!(container instanceof Set)) { |
| main[prop] = container = new Set([container]); |
| } |
| container.add(item); |
| }; |
| |
| const clearItem = cont => key => { |
| const set = cont[key]; |
| if (set instanceof Set) { |
| set.clear(); |
| } else { |
| delete cont[key]; |
| } |
| }; |
| |
| const delFromSet = (main, prop, item) => { |
| const container = main[prop]; |
| if (container instanceof Set) { |
| container.delete(item); |
| } else if (container === item) { |
| delete main[prop]; |
| } |
| }; |
| |
| const isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val; |
| |
| /** |
| * @typedef {String} Path |
| */ |
| |
| // fs_watch helpers |
| |
| // object to hold per-process fs_watch instances |
| // (may be shared across chokidar FSWatcher instances) |
| |
| /** |
| * @typedef {Object} FsWatchContainer |
| * @property {Set} listeners |
| * @property {Set} errHandlers |
| * @property {Set} rawEmitters |
| * @property {fs.FSWatcher=} watcher |
| * @property {Boolean=} watcherUnusable |
| */ |
| |
| /** |
| * @type {Map<String,FsWatchContainer>} |
| */ |
| const FsWatchInstances = new Map(); |
| |
| /** |
| * Instantiates the fs_watch interface |
| * @param {String} path to be watched |
| * @param {Object} options to be passed to fs_watch |
| * @param {Function} listener main event handler |
| * @param {Function} errHandler emits info about errors |
| * @param {Function} emitRaw emits raw event data |
| * @returns {fs.FSWatcher} new fsevents instance |
| */ |
| function createFsWatchInstance(path, options, listener, errHandler, emitRaw) { |
| const handleEvent = (rawEvent, evPath) => { |
| listener(path); |
| emitRaw(rawEvent, evPath, {watchedPath: path}); |
| |
| // emit based on events occurring for files from a directory's watcher in |
| // case the file's watcher misses it (and rely on throttling to de-dupe) |
| if (evPath && path !== evPath) { |
| fsWatchBroadcast( |
| sysPath$2.resolve(path, evPath), KEY_LISTENERS, sysPath$2.join(path, evPath) |
| ); |
| } |
| }; |
| try { |
| return fs$2.watch(path, options, handleEvent); |
| } catch (error) { |
| errHandler(error); |
| } |
| } |
| |
| /** |
| * Helper for passing fs_watch event data to a collection of listeners |
| * @param {Path} fullPath absolute path bound to fs_watch instance |
| * @param {String} type listener type |
| * @param {*=} val1 arguments to be passed to listeners |
| * @param {*=} val2 |
| * @param {*=} val3 |
| */ |
| const fsWatchBroadcast = (fullPath, type, val1, val2, val3) => { |
| const cont = FsWatchInstances.get(fullPath); |
| if (!cont) return; |
| foreach(cont[type], (listener) => { |
| listener(val1, val2, val3); |
| }); |
| }; |
| |
| /** |
| * Instantiates the fs_watch interface or binds listeners |
| * to an existing one covering the same file system entry |
| * @param {String} path |
| * @param {String} fullPath absolute path |
| * @param {Object} options to be passed to fs_watch |
| * @param {Object} handlers container for event listener functions |
| */ |
| const setFsWatchListener = (path, fullPath, options, handlers) => { |
| const {listener, errHandler, rawEmitter} = handlers; |
| let cont = FsWatchInstances.get(fullPath); |
| |
| /** @type {fs.FSWatcher=} */ |
| let watcher; |
| if (!options.persistent) { |
| watcher = createFsWatchInstance( |
| path, options, listener, errHandler, rawEmitter |
| ); |
| return watcher.close.bind(watcher); |
| } |
| if (cont) { |
| addAndConvert(cont, KEY_LISTENERS, listener); |
| addAndConvert(cont, KEY_ERR, errHandler); |
| addAndConvert(cont, KEY_RAW, rawEmitter); |
| } else { |
| watcher = createFsWatchInstance( |
| path, |
| options, |
| fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), |
| errHandler, // no need to use broadcast here |
| fsWatchBroadcast.bind(null, fullPath, KEY_RAW) |
| ); |
| if (!watcher) return; |
| watcher.on(EV_ERROR$2, async (error) => { |
| const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR); |
| cont.watcherUnusable = true; // documented since Node 10.4.1 |
| // Workaround for https://github.com/joyent/node/issues/4337 |
| if (isWindows$1 && error.code === 'EPERM') { |
| try { |
| const fd = await open(path, 'r'); |
| await close(fd); |
| broadcastErr(error); |
| } catch (err) {} |
| } else { |
| broadcastErr(error); |
| } |
| }); |
| cont = { |
| listeners: listener, |
| errHandlers: errHandler, |
| rawEmitters: rawEmitter, |
| watcher |
| }; |
| FsWatchInstances.set(fullPath, cont); |
| } |
| // const index = cont.listeners.indexOf(listener); |
| |
| // removes this instance's listeners and closes the underlying fs_watch |
| // instance if there are no more listeners left |
| return () => { |
| delFromSet(cont, KEY_LISTENERS, listener); |
| delFromSet(cont, KEY_ERR, errHandler); |
| delFromSet(cont, KEY_RAW, rawEmitter); |
| if (isEmptySet(cont.listeners)) { |
| // Check to protect against issue gh-730. |
| // if (cont.watcherUnusable) { |
| cont.watcher.close(); |
| // } |
| FsWatchInstances.delete(fullPath); |
| HANDLER_KEYS.forEach(clearItem(cont)); |
| cont.watcher = undefined; |
| Object.freeze(cont); |
| } |
| }; |
| }; |
| |
| // fs_watchFile helpers |
| |
| // object to hold per-process fs_watchFile instances |
| // (may be shared across chokidar FSWatcher instances) |
| const FsWatchFileInstances = new Map(); |
| |
| /** |
| * Instantiates the fs_watchFile interface or binds listeners |
| * to an existing one covering the same file system entry |
| * @param {String} path to be watched |
| * @param {String} fullPath absolute path |
| * @param {Object} options options to be passed to fs_watchFile |
| * @param {Object} handlers container for event listener functions |
| * @returns {Function} closer |
| */ |
| const setFsWatchFileListener = (path, fullPath, options, handlers) => { |
| const {listener, rawEmitter} = handlers; |
| let cont = FsWatchFileInstances.get(fullPath); |
| |
| const copts = cont && cont.options; |
| if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) { |
| fs$2.unwatchFile(fullPath); |
| cont = undefined; |
| } |
| |
| /* eslint-enable no-unused-vars, prefer-destructuring */ |
| |
| if (cont) { |
| addAndConvert(cont, KEY_LISTENERS, listener); |
| addAndConvert(cont, KEY_RAW, rawEmitter); |
| } else { |
| // TODO |
| // listeners.add(listener); |
| // rawEmitters.add(rawEmitter); |
| cont = { |
| listeners: listener, |
| rawEmitters: rawEmitter, |
| options, |
| watcher: fs$2.watchFile(fullPath, options, (curr, prev) => { |
| foreach(cont.rawEmitters, (rawEmitter) => { |
| rawEmitter(EV_CHANGE$2, fullPath, {curr, prev}); |
| }); |
| const currmtime = curr.mtimeMs; |
| if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) { |
| foreach(cont.listeners, (listener) => listener(path, curr)); |
| } |
| }) |
| }; |
| FsWatchFileInstances.set(fullPath, cont); |
| } |
| // const index = cont.listeners.indexOf(listener); |
| |
| // Removes this instance's listeners and closes the underlying fs_watchFile |
| // instance if there are no more listeners left. |
| return () => { |
| delFromSet(cont, KEY_LISTENERS, listener); |
| delFromSet(cont, KEY_RAW, rawEmitter); |
| if (isEmptySet(cont.listeners)) { |
| FsWatchFileInstances.delete(fullPath); |
| fs$2.unwatchFile(fullPath); |
| cont.options = cont.watcher = undefined; |
| Object.freeze(cont); |
| } |
| }; |
| }; |
| |
| /** |
| * @mixin |
| */ |
| class NodeFsHandler$1 { |
| |
| /** |
| * @param {import("../index").FSWatcher} fsW |
| */ |
| constructor(fsW) { |
| this.fsw = fsW; |
| this._boundHandleError = (error) => fsW._handleError(error); |
| } |
| |
| /** |
| * Watch file for changes with fs_watchFile or fs_watch. |
| * @param {String} path to file or dir |
| * @param {Function} listener on fs change |
| * @returns {Function} closer for the watcher instance |
| */ |
| _watchWithNodeFs(path, listener) { |
| const opts = this.fsw.options; |
| const directory = sysPath$2.dirname(path); |
| const basename = sysPath$2.basename(path); |
| const parent = this.fsw._getWatchedDir(directory); |
| parent.add(basename); |
| const absolutePath = sysPath$2.resolve(path); |
| const options = {persistent: opts.persistent}; |
| if (!listener) listener = EMPTY_FN$2; |
| |
| let closer; |
| if (opts.usePolling) { |
| options.interval = opts.enableBinaryInterval && isBinaryPath(basename) ? |
| opts.binaryInterval : opts.interval; |
| closer = setFsWatchFileListener(path, absolutePath, options, { |
| listener, |
| rawEmitter: this.fsw._emitRaw |
| }); |
| } else { |
| closer = setFsWatchListener(path, absolutePath, options, { |
| listener, |
| errHandler: this._boundHandleError, |
| rawEmitter: this.fsw._emitRaw |
| }); |
| } |
| return closer; |
| } |
| |
| /** |
| * Watch a file and emit add event if warranted. |
| * @param {Path} file Path |
| * @param {fs.Stats} stats result of fs_stat |
| * @param {Boolean} initialAdd was the file added at watch instantiation? |
| * @returns {Function} closer for the watcher instance |
| */ |
| _handleFile(file, stats, initialAdd) { |
| if (this.fsw.closed) { |
| return; |
| } |
| const dirname = sysPath$2.dirname(file); |
| const basename = sysPath$2.basename(file); |
| const parent = this.fsw._getWatchedDir(dirname); |
| // stats is always present |
| let prevStats = stats; |
| |
| // if the file is already being watched, do nothing |
| if (parent.has(basename)) return; |
| |
| const listener = async (path, newStats) => { |
| if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5)) return; |
| if (!newStats || newStats.mtimeMs === 0) { |
| try { |
| const newStats = await stat$2(file); |
| if (this.fsw.closed) return; |
| // Check that change event was not fired because of changed only accessTime. |
| const at = newStats.atimeMs; |
| const mt = newStats.mtimeMs; |
| if (!at || at <= mt || mt !== prevStats.mtimeMs) { |
| this.fsw._emit(EV_CHANGE$2, file, newStats); |
| } |
| if (isLinux && prevStats.ino !== newStats.ino) { |
| this.fsw._closeFile(path); |
| prevStats = newStats; |
| this.fsw._addPathCloser(path, this._watchWithNodeFs(file, listener)); |
| } else { |
| prevStats = newStats; |
| } |
| } catch (error) { |
| // Fix issues where mtime is null but file is still present |
| this.fsw._remove(dirname, basename); |
| } |
| // add is about to be emitted if file not already tracked in parent |
| } else if (parent.has(basename)) { |
| // Check that change event was not fired because of changed only accessTime. |
| const at = newStats.atimeMs; |
| const mt = newStats.mtimeMs; |
| if (!at || at <= mt || mt !== prevStats.mtimeMs) { |
| this.fsw._emit(EV_CHANGE$2, file, newStats); |
| } |
| prevStats = newStats; |
| } |
| }; |
| // kick off the watcher |
| const closer = this._watchWithNodeFs(file, listener); |
| |
| // emit an add event if we're supposed to |
| if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) { |
| if (!this.fsw._throttle(EV_ADD$2, file, 0)) return; |
| this.fsw._emit(EV_ADD$2, file, stats); |
| } |
| |
| return closer; |
| } |
| |
| /** |
| * Handle symlinks encountered while reading a dir. |
| * @param {Object} entry returned by readdirp |
| * @param {String} directory path of dir being read |
| * @param {String} path of this item |
| * @param {String} item basename of this item |
| * @returns {Promise<Boolean>} true if no more processing is needed for this entry. |
| */ |
| async _handleSymlink(entry, directory, path, item) { |
| if (this.fsw.closed) { |
| return; |
| } |
| const full = entry.fullPath; |
| const dir = this.fsw._getWatchedDir(directory); |
| |
| if (!this.fsw.options.followSymlinks) { |
| // watch symlink directly (don't follow) and detect changes |
| this.fsw._incrReadyCount(); |
| const linkPath = await fsrealpath(path); |
| if (this.fsw.closed) return; |
| if (dir.has(item)) { |
| if (this.fsw._symlinkPaths.get(full) !== linkPath) { |
| this.fsw._symlinkPaths.set(full, linkPath); |
| this.fsw._emit(EV_CHANGE$2, path, entry.stats); |
| } |
| } else { |
| dir.add(item); |
| this.fsw._symlinkPaths.set(full, linkPath); |
| this.fsw._emit(EV_ADD$2, path, entry.stats); |
| } |
| this.fsw._emitReady(); |
| return true; |
| } |
| |
| // don't follow the same symlink more than once |
| if (this.fsw._symlinkPaths.has(full)) { |
| return true; |
| } |
| |
| this.fsw._symlinkPaths.set(full, true); |
| } |
| |
| _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) { |
| // Normalize the directory name on Windows |
| directory = sysPath$2.join(directory, EMPTY_STR$1); |
| |
| if (!wh.hasGlob) { |
| throttler = this.fsw._throttle('readdir', directory, 1000); |
| if (!throttler) return; |
| } |
| |
| const previous = this.fsw._getWatchedDir(wh.path); |
| const current = new Set(); |
| |
| let stream = this.fsw._readdirp(directory, { |
| fileFilter: entry => wh.filterPath(entry), |
| directoryFilter: entry => wh.filterDir(entry), |
| depth: 0 |
| }).on(STR_DATA$1, async (entry) => { |
| if (this.fsw.closed) { |
| stream = undefined; |
| return; |
| } |
| const item = entry.path; |
| let path = sysPath$2.join(directory, item); |
| current.add(item); |
| |
| if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path, item)) { |
| return; |
| } |
| |
| if (this.fsw.closed) { |
| stream = undefined; |
| return; |
| } |
| // Files that present in current directory snapshot |
| // but absent in previous are added to watch list and |
| // emit `add` event. |
| if (item === target || !target && !previous.has(item)) { |
| this.fsw._incrReadyCount(); |
| |
| // ensure relativeness of path is preserved in case of watcher reuse |
| path = sysPath$2.join(dir, sysPath$2.relative(dir, path)); |
| |
| this._addToNodeFs(path, initialAdd, wh, depth + 1); |
| } |
| }).on(EV_ERROR$2, this._boundHandleError); |
| |
| return new Promise(resolve => |
| stream.once(STR_END$2, () => { |
| if (this.fsw.closed) { |
| stream = undefined; |
| return; |
| } |
| const wasThrottled = throttler ? throttler.clear() : false; |
| |
| resolve(); |
| |
| // Files that absent in current directory snapshot |
| // but present in previous emit `remove` event |
| // and are removed from @watched[directory]. |
| previous.getChildren().filter((item) => { |
| return item !== directory && |
| !current.has(item) && |
| // in case of intersecting globs; |
| // a path may have been filtered out of this readdir, but |
| // shouldn't be removed because it matches a different glob |
| (!wh.hasGlob || wh.filterPath({ |
| fullPath: sysPath$2.resolve(directory, item) |
| })); |
| }).forEach((item) => { |
| this.fsw._remove(directory, item); |
| }); |
| |
| stream = undefined; |
| |
| // one more time for any missed in case changes came in extremely quickly |
| if (wasThrottled) this._handleRead(directory, false, wh, target, dir, depth, throttler); |
| }) |
| ); |
| } |
| |
| /** |
| * Read directory to add / remove files from `@watched` list and re-read it on change. |
| * @param {String} dir fs path |
| * @param {fs.Stats} stats |
| * @param {Boolean} initialAdd |
| * @param {Number} depth relative to user-supplied path |
| * @param {String} target child path targeted for watch |
| * @param {Object} wh Common watch helpers for this path |
| * @param {String} realpath |
| * @returns {Promise<Function>} closer for the watcher instance. |
| */ |
| async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath) { |
| const parentDir = this.fsw._getWatchedDir(sysPath$2.dirname(dir)); |
| const tracked = parentDir.has(sysPath$2.basename(dir)); |
| if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) { |
| if (!wh.hasGlob || wh.globFilter(dir)) this.fsw._emit(EV_ADD_DIR$2, dir, stats); |
| } |
| |
| // ensure dir is tracked (harmless if redundant) |
| parentDir.add(sysPath$2.basename(dir)); |
| this.fsw._getWatchedDir(dir); |
| let throttler; |
| let closer; |
| |
| const oDepth = this.fsw.options.depth; |
| if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) { |
| if (!target) { |
| await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler); |
| if (this.fsw.closed) return; |
| } |
| |
| closer = this._watchWithNodeFs(dir, (dirPath, stats) => { |
| // if current directory is removed, do nothing |
| if (stats && stats.mtimeMs === 0) return; |
| |
| this._handleRead(dirPath, false, wh, target, dir, depth, throttler); |
| }); |
| } |
| return closer; |
| } |
| |
| /** |
| * Handle added file, directory, or glob pattern. |
| * Delegates call to _handleFile / _handleDir after checks. |
| * @param {String} path to file or ir |
| * @param {Boolean} initialAdd was the file added at watch instantiation? |
| * @param {Object} priorWh depth relative to user-supplied path |
| * @param {Number} depth Child path actually targeted for watch |
| * @param {String=} target Child path actually targeted for watch |
| * @returns {Promise} |
| */ |
| async _addToNodeFs(path, initialAdd, priorWh, depth, target) { |
| const ready = this.fsw._emitReady; |
| if (this.fsw._isIgnored(path) || this.fsw.closed) { |
| ready(); |
| return false; |
| } |
| |
| const wh = this.fsw._getWatchHelpers(path, depth); |
| if (!wh.hasGlob && priorWh) { |
| wh.hasGlob = priorWh.hasGlob; |
| wh.globFilter = priorWh.globFilter; |
| wh.filterPath = entry => priorWh.filterPath(entry); |
| wh.filterDir = entry => priorWh.filterDir(entry); |
| } |
| |
| // evaluate what is at the path we're being asked to watch |
| try { |
| const stats = await statMethods$1[wh.statMethod](wh.watchPath); |
| if (this.fsw.closed) return; |
| if (this.fsw._isIgnored(wh.watchPath, stats)) { |
| ready(); |
| return false; |
| } |
| |
| const follow = this.fsw.options.followSymlinks && !path.includes(STAR) && !path.includes(BRACE_START$1); |
| let closer; |
| if (stats.isDirectory()) { |
| const absPath = sysPath$2.resolve(path); |
| const targetPath = follow ? await fsrealpath(path) : path; |
| if (this.fsw.closed) return; |
| closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath); |
| if (this.fsw.closed) return; |
| // preserve this symlink's target path |
| if (absPath !== targetPath && targetPath !== undefined) { |
| this.fsw._symlinkPaths.set(absPath, targetPath); |
| } |
| } else if (stats.isSymbolicLink()) { |
| const targetPath = follow ? await fsrealpath(path) : path; |
| if (this.fsw.closed) return; |
| const parent = sysPath$2.dirname(wh.watchPath); |
| this.fsw._getWatchedDir(parent).add(wh.watchPath); |
| this.fsw._emit(EV_ADD$2, wh.watchPath, stats); |
| closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath); |
| if (this.fsw.closed) return; |
| |
| // preserve this symlink's target path |
| if (targetPath !== undefined) { |
| this.fsw._symlinkPaths.set(sysPath$2.resolve(path), targetPath); |
| } |
| } else { |
| closer = this._handleFile(wh.watchPath, stats, initialAdd); |
| } |
| ready(); |
| |
| this.fsw._addPathCloser(path, closer); |
| return false; |
| |
| } catch (error) { |
| if (this.fsw._handleError(error)) { |
| ready(); |
| return path; |
| } |
| } |
| } |
| |
| } |
| |
| var nodefsHandler = NodeFsHandler$1; |
| |
| var fseventsHandler = {exports: {}}; |
| |
| var require$$3 = /*@__PURE__*/getAugmentedNamespace(rollup.fseventsImporter); |
| |
| const fs$1 = fs$4; |
| const sysPath$1 = path$3; |
| const { promisify: promisify$1 } = require$$0$1; |
| |
| let fsevents; |
| try { |
| fsevents = require$$3.getFsEvents(); |
| } catch (error) { |
| if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error); |
| } |
| |
| if (fsevents) { |
| // TODO: real check |
| const mtch = process.version.match(/v(\d+)\.(\d+)/); |
| if (mtch && mtch[1] && mtch[2]) { |
| const maj = Number.parseInt(mtch[1], 10); |
| const min = Number.parseInt(mtch[2], 10); |
| if (maj === 8 && min < 16) { |
| fsevents = undefined; |
| } |
| } |
| } |
| |
| const { |
| EV_ADD: EV_ADD$1, |
| EV_CHANGE: EV_CHANGE$1, |
| EV_ADD_DIR: EV_ADD_DIR$1, |
| EV_UNLINK: EV_UNLINK$1, |
| EV_ERROR: EV_ERROR$1, |
| STR_DATA, |
| STR_END: STR_END$1, |
| FSEVENT_CREATED, |
| FSEVENT_MODIFIED, |
| FSEVENT_DELETED, |
| FSEVENT_MOVED, |
| // FSEVENT_CLONED, |
| FSEVENT_UNKNOWN, |
| FSEVENT_TYPE_FILE, |
| FSEVENT_TYPE_DIRECTORY, |
| FSEVENT_TYPE_SYMLINK, |
| |
| ROOT_GLOBSTAR, |
| DIR_SUFFIX, |
| DOT_SLASH, |
| FUNCTION_TYPE: FUNCTION_TYPE$1, |
| EMPTY_FN: EMPTY_FN$1, |
| IDENTITY_FN |
| } = constants; |
| |
| const Depth = (value) => isNaN(value) ? {} : {depth: value}; |
| |
| const stat$1 = promisify$1(fs$1.stat); |
| const lstat = promisify$1(fs$1.lstat); |
| const realpath = promisify$1(fs$1.realpath); |
| |
| const statMethods = { stat: stat$1, lstat }; |
| |
| /** |
| * @typedef {String} Path |
| */ |
| |
| /** |
| * @typedef {Object} FsEventsWatchContainer |
| * @property {Set<Function>} listeners |
| * @property {Function} rawEmitter |
| * @property {{stop: Function}} watcher |
| */ |
| |
| // fsevents instance helper functions |
| /** |
| * Object to hold per-process fsevents instances (may be shared across chokidar FSWatcher instances) |
| * @type {Map<Path,FsEventsWatchContainer>} |
| */ |
| const FSEventsWatchers = new Map(); |
| |
| // Threshold of duplicate path prefixes at which to start |
| // consolidating going forward |
| const consolidateThreshhold = 10; |
| |
| const wrongEventFlags = new Set([ |
| 69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912 |
| ]); |
| |
| /** |
| * Instantiates the fsevents interface |
| * @param {Path} path path to be watched |
| * @param {Function} callback called when fsevents is bound and ready |
| * @returns {{stop: Function}} new fsevents instance |
| */ |
| const createFSEventsInstance = (path, callback) => { |
| const stop = fsevents.watch(path, callback); |
| return {stop}; |
| }; |
| |
| /** |
| * Instantiates the fsevents interface or binds listeners to an existing one covering |
| * the same file tree. |
| * @param {Path} path - to be watched |
| * @param {Path} realPath - real path for symlinks |
| * @param {Function} listener - called when fsevents emits events |
| * @param {Function} rawEmitter - passes data to listeners of the 'raw' event |
| * @returns {Function} closer |
| */ |
| function setFSEventsListener(path, realPath, listener, rawEmitter) { |
| let watchPath = sysPath$1.extname(realPath) ? sysPath$1.dirname(realPath) : realPath; |
| |
| const parentPath = sysPath$1.dirname(watchPath); |
| let cont = FSEventsWatchers.get(watchPath); |
| |
| // If we've accumulated a substantial number of paths that |
| // could have been consolidated by watching one directory |
| // above the current one, create a watcher on the parent |
| // path instead, so that we do consolidate going forward. |
| if (couldConsolidate(parentPath)) { |
| watchPath = parentPath; |
| } |
| |
| const resolvedPath = sysPath$1.resolve(path); |
| const hasSymlink = resolvedPath !== realPath; |
| |
| const filteredListener = (fullPath, flags, info) => { |
| if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath); |
| if ( |
| fullPath === resolvedPath || |
| !fullPath.indexOf(resolvedPath + sysPath$1.sep) |
| ) listener(fullPath, flags, info); |
| }; |
| |
| // check if there is already a watcher on a parent path |
| // modifies `watchPath` to the parent path when it finds a match |
| let watchedParent = false; |
| for (const watchedPath of FSEventsWatchers.keys()) { |
| if (realPath.indexOf(sysPath$1.resolve(watchedPath) + sysPath$1.sep) === 0) { |
| watchPath = watchedPath; |
| cont = FSEventsWatchers.get(watchPath); |
| watchedParent = true; |
| break; |
| } |
| } |
| |
| if (cont || watchedParent) { |
| cont.listeners.add(filteredListener); |
| } else { |
| cont = { |
| listeners: new Set([filteredListener]), |
| rawEmitter, |
| watcher: createFSEventsInstance(watchPath, (fullPath, flags) => { |
| if (!cont.listeners.size) return; |
| const info = fsevents.getInfo(fullPath, flags); |
| cont.listeners.forEach(list => { |
| list(fullPath, flags, info); |
| }); |
| |
| cont.rawEmitter(info.event, fullPath, info); |
| }) |
| }; |
| FSEventsWatchers.set(watchPath, cont); |
| } |
| |
| // removes this instance's listeners and closes the underlying fsevents |
| // instance if there are no more listeners left |
| return () => { |
| const lst = cont.listeners; |
| |
| lst.delete(filteredListener); |
| if (!lst.size) { |
| FSEventsWatchers.delete(watchPath); |
| if (cont.watcher) return cont.watcher.stop().then(() => { |
| cont.rawEmitter = cont.watcher = undefined; |
| Object.freeze(cont); |
| }); |
| } |
| }; |
| } |
| |
| // Decide whether or not we should start a new higher-level |
| // parent watcher |
| const couldConsolidate = (path) => { |
| let count = 0; |
| for (const watchPath of FSEventsWatchers.keys()) { |
| if (watchPath.indexOf(path) === 0) { |
| count++; |
| if (count >= consolidateThreshhold) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| }; |
| |
| // returns boolean indicating whether fsevents can be used |
| const canUse = () => fsevents && FSEventsWatchers.size < 128; |
| |
| // determines subdirectory traversal levels from root to path |
| const calcDepth = (path, root) => { |
| let i = 0; |
| while (!path.indexOf(root) && (path = sysPath$1.dirname(path)) !== root) i++; |
| return i; |
| }; |
| |
| // returns boolean indicating whether the fsevents' event info has the same type |
| // as the one returned by fs.stat |
| const sameTypes = (info, stats) => ( |
| info.type === FSEVENT_TYPE_DIRECTORY && stats.isDirectory() || |
| info.type === FSEVENT_TYPE_SYMLINK && stats.isSymbolicLink() || |
| info.type === FSEVENT_TYPE_FILE && stats.isFile() |
| ); |
| |
| /** |
| * @mixin |
| */ |
| class FsEventsHandler$1 { |
| |
| /** |
| * @param {import('../index').FSWatcher} fsw |
| */ |
| constructor(fsw) { |
| this.fsw = fsw; |
| } |
| checkIgnored(path, stats) { |
| const ipaths = this.fsw._ignoredPaths; |
| if (this.fsw._isIgnored(path, stats)) { |
| ipaths.add(path); |
| if (stats && stats.isDirectory()) { |
| ipaths.add(path + ROOT_GLOBSTAR); |
| } |
| return true; |
| } |
| |
| ipaths.delete(path); |
| ipaths.delete(path + ROOT_GLOBSTAR); |
| } |
| |
| addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts) { |
| const event = watchedDir.has(item) ? EV_CHANGE$1 : EV_ADD$1; |
| this.handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } |
| |
| async checkExists(path, fullPath, realPath, parent, watchedDir, item, info, opts) { |
| try { |
| const stats = await stat$1(path); |
| if (this.fsw.closed) return; |
| if (sameTypes(info, stats)) { |
| this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } else { |
| this.handleEvent(EV_UNLINK$1, path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } |
| } catch (error) { |
| if (error.code === 'EACCES') { |
| this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } else { |
| this.handleEvent(EV_UNLINK$1, path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } |
| } |
| } |
| |
| handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts) { |
| if (this.fsw.closed || this.checkIgnored(path)) return; |
| |
| if (event === EV_UNLINK$1) { |
| const isDirectory = info.type === FSEVENT_TYPE_DIRECTORY; |
| // suppress unlink events on never before seen files |
| if (isDirectory || watchedDir.has(item)) { |
| this.fsw._remove(parent, item, isDirectory); |
| } |
| } else { |
| if (event === EV_ADD$1) { |
| // track new directories |
| if (info.type === FSEVENT_TYPE_DIRECTORY) this.fsw._getWatchedDir(path); |
| |
| if (info.type === FSEVENT_TYPE_SYMLINK && opts.followSymlinks) { |
| // push symlinks back to the top of the stack to get handled |
| const curDepth = opts.depth === undefined ? |
| undefined : calcDepth(fullPath, realPath) + 1; |
| return this._addToFsEvents(path, false, true, curDepth); |
| } |
| |
| // track new paths |
| // (other than symlinks being followed, which will be tracked soon) |
| this.fsw._getWatchedDir(parent).add(item); |
| } |
| /** |
| * @type {'add'|'addDir'|'unlink'|'unlinkDir'} |
| */ |
| const eventName = info.type === FSEVENT_TYPE_DIRECTORY ? event + DIR_SUFFIX : event; |
| this.fsw._emit(eventName, path); |
| if (eventName === EV_ADD_DIR$1) this._addToFsEvents(path, false, true); |
| } |
| } |
| |
| /** |
| * Handle symlinks encountered during directory scan |
| * @param {String} watchPath - file/dir path to be watched with fsevents |
| * @param {String} realPath - real path (in case of symlinks) |
| * @param {Function} transform - path transformer |
| * @param {Function} globFilter - path filter in case a glob pattern was provided |
| * @returns {Function} closer for the watcher instance |
| */ |
| _watchWithFsEvents(watchPath, realPath, transform, globFilter) { |
| if (this.fsw.closed || this.fsw._isIgnored(watchPath)) return; |
| const opts = this.fsw.options; |
| const watchCallback = async (fullPath, flags, info) => { |
| if (this.fsw.closed) return; |
| if ( |
| opts.depth !== undefined && |
| calcDepth(fullPath, realPath) > opts.depth |
| ) return; |
| const path = transform(sysPath$1.join( |
| watchPath, sysPath$1.relative(watchPath, fullPath) |
| )); |
| if (globFilter && !globFilter(path)) return; |
| // ensure directories are tracked |
| const parent = sysPath$1.dirname(path); |
| const item = sysPath$1.basename(path); |
| const watchedDir = this.fsw._getWatchedDir( |
| info.type === FSEVENT_TYPE_DIRECTORY ? path : parent |
| ); |
| |
| // correct for wrong events emitted |
| if (wrongEventFlags.has(flags) || info.event === FSEVENT_UNKNOWN) { |
| if (typeof opts.ignored === FUNCTION_TYPE$1) { |
| let stats; |
| try { |
| stats = await stat$1(path); |
| } catch (error) {} |
| if (this.fsw.closed) return; |
| if (this.checkIgnored(path, stats)) return; |
| if (sameTypes(info, stats)) { |
| this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } else { |
| this.handleEvent(EV_UNLINK$1, path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } |
| } else { |
| this.checkExists(path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } |
| } else { |
| switch (info.event) { |
| case FSEVENT_CREATED: |
| case FSEVENT_MODIFIED: |
| return this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| case FSEVENT_DELETED: |
| case FSEVENT_MOVED: |
| return this.checkExists(path, fullPath, realPath, parent, watchedDir, item, info, opts); |
| } |
| } |
| }; |
| |
| const closer = setFSEventsListener( |
| watchPath, |
| realPath, |
| watchCallback, |
| this.fsw._emitRaw |
| ); |
| |
| this.fsw._emitReady(); |
| return closer; |
| } |
| |
| /** |
| * Handle symlinks encountered during directory scan |
| * @param {String} linkPath path to symlink |
| * @param {String} fullPath absolute path to the symlink |
| * @param {Function} transform pre-existing path transformer |
| * @param {Number} curDepth level of subdirectories traversed to where symlink is |
| * @returns {Promise<void>} |
| */ |
| async _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { |
| // don't follow the same symlink more than once |
| if (this.fsw.closed || this.fsw._symlinkPaths.has(fullPath)) return; |
| |
| this.fsw._symlinkPaths.set(fullPath, true); |
| this.fsw._incrReadyCount(); |
| |
| try { |
| const linkTarget = await realpath(linkPath); |
| if (this.fsw.closed) return; |
| if (this.fsw._isIgnored(linkTarget)) { |
| return this.fsw._emitReady(); |
| } |
| |
| this.fsw._incrReadyCount(); |
| |
| // add the linkTarget for watching with a wrapper for transform |
| // that causes emitted paths to incorporate the link's path |
| this._addToFsEvents(linkTarget || linkPath, (path) => { |
| let aliasedPath = linkPath; |
| if (linkTarget && linkTarget !== DOT_SLASH) { |
| aliasedPath = path.replace(linkTarget, linkPath); |
| } else if (path !== DOT_SLASH) { |
| aliasedPath = sysPath$1.join(linkPath, path); |
| } |
| return transform(aliasedPath); |
| }, false, curDepth); |
| } catch(error) { |
| if (this.fsw._handleError(error)) { |
| return this.fsw._emitReady(); |
| } |
| } |
| } |
| |
| /** |
| * |
| * @param {Path} newPath |
| * @param {fs.Stats} stats |
| */ |
| emitAdd(newPath, stats, processPath, opts, forceAdd) { |
| const pp = processPath(newPath); |
| const isDir = stats.isDirectory(); |
| const dirObj = this.fsw._getWatchedDir(sysPath$1.dirname(pp)); |
| const base = sysPath$1.basename(pp); |
| |
| // ensure empty dirs get tracked |
| if (isDir) this.fsw._getWatchedDir(pp); |
| if (dirObj.has(base)) return; |
| dirObj.add(base); |
| |
| if (!opts.ignoreInitial || forceAdd === true) { |
| this.fsw._emit(isDir ? EV_ADD_DIR$1 : EV_ADD$1, pp, stats); |
| } |
| } |
| |
| initWatch(realPath, path, wh, processPath) { |
| if (this.fsw.closed) return; |
| const closer = this._watchWithFsEvents( |
| wh.watchPath, |
| sysPath$1.resolve(realPath || wh.watchPath), |
| processPath, |
| wh.globFilter |
| ); |
| this.fsw._addPathCloser(path, closer); |
| } |
| |
| /** |
| * Handle added path with fsevents |
| * @param {String} path file/dir path or glob pattern |
| * @param {Function|Boolean=} transform converts working path to what the user expects |
| * @param {Boolean=} forceAdd ensure add is emitted |
| * @param {Number=} priorDepth Level of subdirectories already traversed. |
| * @returns {Promise<void>} |
| */ |
| async _addToFsEvents(path, transform, forceAdd, priorDepth) { |
| if (this.fsw.closed) { |
| return; |
| } |
| const opts = this.fsw.options; |
| const processPath = typeof transform === FUNCTION_TYPE$1 ? transform : IDENTITY_FN; |
| |
| const wh = this.fsw._getWatchHelpers(path); |
| |
| // evaluate what is at the path we're being asked to watch |
| try { |
| const stats = await statMethods[wh.statMethod](wh.watchPath); |
| if (this.fsw.closed) return; |
| if (this.fsw._isIgnored(wh.watchPath, stats)) { |
| throw null; |
| } |
| if (stats.isDirectory()) { |
| // emit addDir unless this is a glob parent |
| if (!wh.globFilter) this.emitAdd(processPath(path), stats, processPath, opts, forceAdd); |
| |
| // don't recurse further if it would exceed depth setting |
| if (priorDepth && priorDepth > opts.depth) return; |
| |
| // scan the contents of the dir |
| this.fsw._readdirp(wh.watchPath, { |
| fileFilter: entry => wh.filterPath(entry), |
| directoryFilter: entry => wh.filterDir(entry), |
| ...Depth(opts.depth - (priorDepth || 0)) |
| }).on(STR_DATA, (entry) => { |
| // need to check filterPath on dirs b/c filterDir is less restrictive |
| if (this.fsw.closed) { |
| return; |
| } |
| if (entry.stats.isDirectory() && !wh.filterPath(entry)) return; |
| |
| const joinedPath = sysPath$1.join(wh.watchPath, entry.path); |
| const {fullPath} = entry; |
| |
| if (wh.followSymlinks && entry.stats.isSymbolicLink()) { |
| // preserve the current depth here since it can't be derived from |
| // real paths past the symlink |
| const curDepth = opts.depth === undefined ? |
| undefined : calcDepth(joinedPath, sysPath$1.resolve(wh.watchPath)) + 1; |
| |
| this._handleFsEventsSymlink(joinedPath, fullPath, processPath, curDepth); |
| } else { |
| this.emitAdd(joinedPath, entry.stats, processPath, opts, forceAdd); |
| } |
| }).on(EV_ERROR$1, EMPTY_FN$1).on(STR_END$1, () => { |
| this.fsw._emitReady(); |
| }); |
| } else { |
| this.emitAdd(wh.watchPath, stats, processPath, opts, forceAdd); |
| this.fsw._emitReady(); |
| } |
| } catch (error) { |
| if (!error || this.fsw._handleError(error)) { |
| // TODO: Strange thing: "should not choke on an ignored watch path" will be failed without 2 ready calls -__- |
| this.fsw._emitReady(); |
| this.fsw._emitReady(); |
| } |
| } |
| |
| if (opts.persistent && forceAdd !== true) { |
| if (typeof transform === FUNCTION_TYPE$1) { |
| // realpath has already been resolved |
| this.initWatch(undefined, path, wh, processPath); |
| } else { |
| let realPath; |
| try { |
| realPath = await realpath(wh.watchPath); |
| } catch (e) {} |
| this.initWatch(realPath, path, wh, processPath); |
| } |
| } |
| } |
| |
| } |
| |
| fseventsHandler.exports = FsEventsHandler$1; |
| fseventsHandler.exports.canUse = canUse; |
| |
| const { EventEmitter } = require$$0$2; |
| const fs = fs$4; |
| const sysPath = path$3; |
| const { promisify } = require$$0$1; |
| const readdirp = readdirp_1; |
| const anymatch = anymatch$2.exports.default; |
| const globParent = globParent$1; |
| const isGlob = isGlob$2; |
| const braces = braces_1; |
| const normalizePath = normalizePath$2; |
| |
| const NodeFsHandler = nodefsHandler; |
| const FsEventsHandler = fseventsHandler.exports; |
| const { |
| EV_ALL, |
| EV_READY, |
| EV_ADD, |
| EV_CHANGE, |
| EV_UNLINK, |
| EV_ADD_DIR, |
| EV_UNLINK_DIR, |
| EV_RAW, |
| EV_ERROR, |
| |
| STR_CLOSE, |
| STR_END, |
| |
| BACK_SLASH_RE, |
| DOUBLE_SLASH_RE, |
| SLASH_OR_BACK_SLASH_RE, |
| DOT_RE, |
| REPLACER_RE, |
| |
| SLASH, |
| SLASH_SLASH, |
| BRACE_START, |
| BANG, |
| ONE_DOT, |
| TWO_DOTS, |
| GLOBSTAR, |
| SLASH_GLOBSTAR, |
| ANYMATCH_OPTS, |
| STRING_TYPE, |
| FUNCTION_TYPE, |
| EMPTY_STR, |
| EMPTY_FN, |
| |
| isWindows, |
| isMacos, |
| isIBMi |
| } = constants; |
| |
| const stat = promisify(fs.stat); |
| const readdir = promisify(fs.readdir); |
| |
| /** |
| * @typedef {String} Path |
| * @typedef {'all'|'add'|'addDir'|'change'|'unlink'|'unlinkDir'|'raw'|'error'|'ready'} EventName |
| * @typedef {'readdir'|'watch'|'add'|'remove'|'change'} ThrottleType |
| */ |
| |
| /** |
| * |
| * @typedef {Object} WatchHelpers |
| * @property {Boolean} followSymlinks |
| * @property {'stat'|'lstat'} statMethod |
| * @property {Path} path |
| * @property {Path} watchPath |
| * @property {Function} entryPath |
| * @property {Boolean} hasGlob |
| * @property {Object} globFilter |
| * @property {Function} filterPath |
| * @property {Function} filterDir |
| */ |
| |
| const arrify = (value = []) => Array.isArray(value) ? value : [value]; |
| const flatten = (list, result = []) => { |
| list.forEach(item => { |
| if (Array.isArray(item)) { |
| flatten(item, result); |
| } else { |
| result.push(item); |
| } |
| }); |
| return result; |
| }; |
| |
| const unifyPaths = (paths_) => { |
| /** |
| * @type {Array<String>} |
| */ |
| const paths = flatten(arrify(paths_)); |
| if (!paths.every(p => typeof p === STRING_TYPE)) { |
| throw new TypeError(`Non-string provided as watch path: ${paths}`); |
| } |
| return paths.map(normalizePathToUnix); |
| }; |
| |
| // If SLASH_SLASH occurs at the beginning of path, it is not replaced |
| // because "//StoragePC/DrivePool/Movies" is a valid network path |
| const toUnix = (string) => { |
| let str = string.replace(BACK_SLASH_RE, SLASH); |
| let prepend = false; |
| if (str.startsWith(SLASH_SLASH)) { |
| prepend = true; |
| } |
| while (str.match(DOUBLE_SLASH_RE)) { |
| str = str.replace(DOUBLE_SLASH_RE, SLASH); |
| } |
| if (prepend) { |
| str = SLASH + str; |
| } |
| return str; |
| }; |
| |
| // Our version of upath.normalize |
| // TODO: this is not equal to path-normalize module - investigate why |
| const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path))); |
| |
| const normalizeIgnored = (cwd = EMPTY_STR) => (path) => { |
| if (typeof path !== STRING_TYPE) return path; |
| return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path)); |
| }; |
| |
| const getAbsolutePath = (path, cwd) => { |
| if (sysPath.isAbsolute(path)) { |
| return path; |
| } |
| if (path.startsWith(BANG)) { |
| return BANG + sysPath.join(cwd, path.slice(1)); |
| } |
| return sysPath.join(cwd, path); |
| }; |
| |
| const undef = (opts, key) => opts[key] === undefined; |
| |
| /** |
| * Directory entry. |
| * @property {Path} path |
| * @property {Set<Path>} items |
| */ |
| class DirEntry { |
| /** |
| * @param {Path} dir |
| * @param {Function} removeWatcher |
| */ |
| constructor(dir, removeWatcher) { |
| this.path = dir; |
| this._removeWatcher = removeWatcher; |
| /** @type {Set<Path>} */ |
| this.items = new Set(); |
| } |
| |
| add(item) { |
| const {items} = this; |
| if (!items) return; |
| if (item !== ONE_DOT && item !== TWO_DOTS) items.add(item); |
| } |
| |
| async remove(item) { |
| const {items} = this; |
| if (!items) return; |
| items.delete(item); |
| if (items.size > 0) return; |
| |
| const dir = this.path; |
| try { |
| await readdir(dir); |
| } catch (err) { |
| if (this._removeWatcher) { |
| this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir)); |
| } |
| } |
| } |
| |
| has(item) { |
| const {items} = this; |
| if (!items) return; |
| return items.has(item); |
| } |
| |
| /** |
| * @returns {Array<String>} |
| */ |
| getChildren() { |
| const {items} = this; |
| if (!items) return; |
| return [...items.values()]; |
| } |
| |
| dispose() { |
| this.items.clear(); |
| delete this.path; |
| delete this._removeWatcher; |
| delete this.items; |
| Object.freeze(this); |
| } |
| } |
| |
| const STAT_METHOD_F = 'stat'; |
| const STAT_METHOD_L = 'lstat'; |
| class WatchHelper { |
| constructor(path, watchPath, follow, fsw) { |
| this.fsw = fsw; |
| this.path = path = path.replace(REPLACER_RE, EMPTY_STR); |
| this.watchPath = watchPath; |
| this.fullWatchPath = sysPath.resolve(watchPath); |
| this.hasGlob = watchPath !== path; |
| /** @type {object|boolean} */ |
| if (path === EMPTY_STR) this.hasGlob = false; |
| this.globSymlink = this.hasGlob && follow ? undefined : false; |
| this.globFilter = this.hasGlob ? anymatch(path, undefined, ANYMATCH_OPTS) : false; |
| this.dirParts = this.getDirParts(path); |
| this.dirParts.forEach((parts) => { |
| if (parts.length > 1) parts.pop(); |
| }); |
| this.followSymlinks = follow; |
| this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L; |
| } |
| |
| checkGlobSymlink(entry) { |
| // only need to resolve once |
| // first entry should always have entry.parentDir === EMPTY_STR |
| if (this.globSymlink === undefined) { |
| this.globSymlink = entry.fullParentDir === this.fullWatchPath ? |
| false : {realPath: entry.fullParentDir, linkPath: this.fullWatchPath}; |
| } |
| |
| if (this.globSymlink) { |
| return entry.fullPath.replace(this.globSymlink.realPath, this.globSymlink.linkPath); |
| } |
| |
| return entry.fullPath; |
| } |
| |
| entryPath(entry) { |
| return sysPath.join(this.watchPath, |
| sysPath.relative(this.watchPath, this.checkGlobSymlink(entry)) |
| ); |
| } |
| |
| filterPath(entry) { |
| const {stats} = entry; |
| if (stats && stats.isSymbolicLink()) return this.filterDir(entry); |
| const resolvedPath = this.entryPath(entry); |
| const matchesGlob = this.hasGlob && typeof this.globFilter === FUNCTION_TYPE ? |
| this.globFilter(resolvedPath) : true; |
| return matchesGlob && |
| this.fsw._isntIgnored(resolvedPath, stats) && |
| this.fsw._hasReadPermissions(stats); |
| } |
| |
| getDirParts(path) { |
| if (!this.hasGlob) return []; |
| const parts = []; |
| const expandedPath = path.includes(BRACE_START) ? braces.expand(path) : [path]; |
| expandedPath.forEach((path) => { |
| parts.push(sysPath.relative(this.watchPath, path).split(SLASH_OR_BACK_SLASH_RE)); |
| }); |
| return parts; |
| } |
| |
| filterDir(entry) { |
| if (this.hasGlob) { |
| const entryParts = this.getDirParts(this.checkGlobSymlink(entry)); |
| let globstar = false; |
| this.unmatchedGlob = !this.dirParts.some((parts) => { |
| return parts.every((part, i) => { |
| if (part === GLOBSTAR) globstar = true; |
| return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i], ANYMATCH_OPTS); |
| }); |
| }); |
| } |
| return !this.unmatchedGlob && this.fsw._isntIgnored(this.entryPath(entry), entry.stats); |
| } |
| } |
| |
| /** |
| * Watches files & directories for changes. Emitted events: |
| * `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error` |
| * |
| * new FSWatcher() |
| * .add(directories) |
| * .on('add', path => log('File', path, 'was added')) |
| */ |
| class FSWatcher extends EventEmitter { |
| // Not indenting methods for history sake; for now. |
| constructor(_opts) { |
| super(); |
| |
| const opts = {}; |
| if (_opts) Object.assign(opts, _opts); // for frozen objects |
| |
| /** @type {Map<String, DirEntry>} */ |
| this._watched = new Map(); |
| /** @type {Map<String, Array>} */ |
| this._closers = new Map(); |
| /** @type {Set<String>} */ |
| this._ignoredPaths = new Set(); |
| |
| /** @type {Map<ThrottleType, Map>} */ |
| this._throttled = new Map(); |
| |
| /** @type {Map<Path, String|Boolean>} */ |
| this._symlinkPaths = new Map(); |
| |
| this._streams = new Set(); |
| this.closed = false; |
| |
| // Set up default options. |
| if (undef(opts, 'persistent')) opts.persistent = true; |
| if (undef(opts, 'ignoreInitial')) opts.ignoreInitial = false; |
| if (undef(opts, 'ignorePermissionErrors')) opts.ignorePermissionErrors = false; |
| if (undef(opts, 'interval')) opts.interval = 100; |
| if (undef(opts, 'binaryInterval')) opts.binaryInterval = 300; |
| if (undef(opts, 'disableGlobbing')) opts.disableGlobbing = false; |
| opts.enableBinaryInterval = opts.binaryInterval !== opts.interval; |
| |
| // Enable fsevents on OS X when polling isn't explicitly enabled. |
| if (undef(opts, 'useFsEvents')) opts.useFsEvents = !opts.usePolling; |
| |
| // If we can't use fsevents, ensure the options reflect it's disabled. |
| const canUseFsEvents = FsEventsHandler.canUse(); |
| if (!canUseFsEvents) opts.useFsEvents = false; |
| |
| // Use polling on Mac if not using fsevents. |
| // Other platforms use non-polling fs_watch. |
| if (undef(opts, 'usePolling') && !opts.useFsEvents) { |
| opts.usePolling = isMacos; |
| } |
| |
| // Always default to polling on IBM i because fs.watch() is not available on IBM i. |
| if(isIBMi) { |
| opts.usePolling = true; |
| } |
| |
| // Global override (useful for end-developers that need to force polling for all |
| // instances of chokidar, regardless of usage/dependency depth) |
| const envPoll = process.env.CHOKIDAR_USEPOLLING; |
| if (envPoll !== undefined) { |
| const envLower = envPoll.toLowerCase(); |
| |
| if (envLower === 'false' || envLower === '0') { |
| opts.usePolling = false; |
| } else if (envLower === 'true' || envLower === '1') { |
| opts.usePolling = true; |
| } else { |
| opts.usePolling = !!envLower; |
| } |
| } |
| const envInterval = process.env.CHOKIDAR_INTERVAL; |
| if (envInterval) { |
| opts.interval = Number.parseInt(envInterval, 10); |
| } |
| |
| // Editor atomic write normalization enabled by default with fs.watch |
| if (undef(opts, 'atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents; |
| if (opts.atomic) this._pendingUnlinks = new Map(); |
| |
| if (undef(opts, 'followSymlinks')) opts.followSymlinks = true; |
| |
| if (undef(opts, 'awaitWriteFinish')) opts.awaitWriteFinish = false; |
| if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {}; |
| const awf = opts.awaitWriteFinish; |
| if (awf) { |
| if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000; |
| if (!awf.pollInterval) awf.pollInterval = 100; |
| this._pendingWrites = new Map(); |
| } |
| if (opts.ignored) opts.ignored = arrify(opts.ignored); |
| |
| let readyCalls = 0; |
| this._emitReady = () => { |
| readyCalls++; |
| if (readyCalls >= this._readyCount) { |
| this._emitReady = EMPTY_FN; |
| this._readyEmitted = true; |
| // use process.nextTick to allow time for listener to be bound |
| process.nextTick(() => this.emit(EV_READY)); |
| } |
| }; |
| this._emitRaw = (...args) => this.emit(EV_RAW, ...args); |
| this._readyEmitted = false; |
| this.options = opts; |
| |
| // Initialize with proper watcher. |
| if (opts.useFsEvents) { |
| this._fsEventsHandler = new FsEventsHandler(this); |
| } else { |
| this._nodeFsHandler = new NodeFsHandler(this); |
| } |
| |
| // You’re frozen when your heart’s not open. |
| Object.freeze(opts); |
| } |
| |
| // Public methods |
| |
| /** |
| * Adds paths to be watched on an existing FSWatcher instance |
| * @param {Path|Array<Path>} paths_ |
| * @param {String=} _origAdd private; for handling non-existent paths to be watched |
| * @param {Boolean=} _internal private; indicates a non-user add |
| * @returns {FSWatcher} for chaining |
| */ |
| add(paths_, _origAdd, _internal) { |
| const {cwd, disableGlobbing} = this.options; |
| this.closed = false; |
| let paths = unifyPaths(paths_); |
| if (cwd) { |
| paths = paths.map((path) => { |
| const absPath = getAbsolutePath(path, cwd); |
| |
| // Check `path` instead of `absPath` because the cwd portion can't be a glob |
| if (disableGlobbing || !isGlob(path)) { |
| return absPath; |
| } |
| return normalizePath(absPath); |
| }); |
| } |
| |
| // set aside negated glob strings |
| paths = paths.filter((path) => { |
| if (path.startsWith(BANG)) { |
| this._ignoredPaths.add(path.slice(1)); |
| return false; |
| } |
| |
| // if a path is being added that was previously ignored, stop ignoring it |
| this._ignoredPaths.delete(path); |
| this._ignoredPaths.delete(path + SLASH_GLOBSTAR); |
| |
| // reset the cached userIgnored anymatch fn |
| // to make ignoredPaths changes effective |
| this._userIgnored = undefined; |
| |
| return true; |
| }); |
| |
| if (this.options.useFsEvents && this._fsEventsHandler) { |
| if (!this._readyCount) this._readyCount = paths.length; |
| if (this.options.persistent) this._readyCount *= 2; |
| paths.forEach((path) => this._fsEventsHandler._addToFsEvents(path)); |
| } else { |
| if (!this._readyCount) this._readyCount = 0; |
| this._readyCount += paths.length; |
| Promise.all( |
| paths.map(async path => { |
| const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd); |
| if (res) this._emitReady(); |
| return res; |
| }) |
| ).then(results => { |
| if (this.closed) return; |
| results.filter(item => item).forEach(item => { |
| this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item)); |
| }); |
| }); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Close watchers or start ignoring events from specified paths. |
| * @param {Path|Array<Path>} paths_ - string or array of strings, file/directory paths and/or globs |
| * @returns {FSWatcher} for chaining |
| */ |
| unwatch(paths_) { |
| if (this.closed) return this; |
| const paths = unifyPaths(paths_); |
| const {cwd} = this.options; |
| |
| paths.forEach((path) => { |
| // convert to absolute path unless relative path already matches |
| if (!sysPath.isAbsolute(path) && !this._closers.has(path)) { |
| if (cwd) path = sysPath.join(cwd, path); |
| path = sysPath.resolve(path); |
| } |
| |
| this._closePath(path); |
| |
| this._ignoredPaths.add(path); |
| if (this._watched.has(path)) { |
| this._ignoredPaths.add(path + SLASH_GLOBSTAR); |
| } |
| |
| // reset the cached userIgnored anymatch fn |
| // to make ignoredPaths changes effective |
| this._userIgnored = undefined; |
| }); |
| |
| return this; |
| } |
| |
| /** |
| * Close watchers and remove all listeners from watched paths. |
| * @returns {Promise<void>}. |
| */ |
| close() { |
| if (this.closed) return this._closePromise; |
| this.closed = true; |
| |
| // Memory management. |
| this.removeAllListeners(); |
| const closers = []; |
| this._closers.forEach(closerList => closerList.forEach(closer => { |
| const promise = closer(); |
| if (promise instanceof Promise) closers.push(promise); |
| })); |
| this._streams.forEach(stream => stream.destroy()); |
| this._userIgnored = undefined; |
| this._readyCount = 0; |
| this._readyEmitted = false; |
| this._watched.forEach(dirent => dirent.dispose()); |
| ['closers', 'watched', 'streams', 'symlinkPaths', 'throttled'].forEach(key => { |
| this[`_${key}`].clear(); |
| }); |
| |
| this._closePromise = closers.length ? Promise.all(closers).then(() => undefined) : Promise.resolve(); |
| return this._closePromise; |
| } |
| |
| /** |
| * Expose list of watched paths |
| * @returns {Object} for chaining |
| */ |
| getWatched() { |
| const watchList = {}; |
| this._watched.forEach((entry, dir) => { |
| const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir; |
| watchList[key || ONE_DOT] = entry.getChildren().sort(); |
| }); |
| return watchList; |
| } |
| |
| emitWithAll(event, args) { |
| this.emit(...args); |
| if (event !== EV_ERROR) this.emit(EV_ALL, ...args); |
| } |
| |
| // Common helpers |
| // -------------- |
| |
| /** |
| * Normalize and emit events. |
| * Calling _emit DOES NOT MEAN emit() would be called! |
| * @param {EventName} event Type of event |
| * @param {Path} path File or directory path |
| * @param {*=} val1 arguments to be passed with event |
| * @param {*=} val2 |
| * @param {*=} val3 |
| * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag |
| */ |
| async _emit(event, path, val1, val2, val3) { |
| if (this.closed) return; |
| |
| const opts = this.options; |
| if (isWindows) path = sysPath.normalize(path); |
| if (opts.cwd) path = sysPath.relative(opts.cwd, path); |
| /** @type Array<any> */ |
| const args = [event, path]; |
| if (val3 !== undefined) args.push(val1, val2, val3); |
| else if (val2 !== undefined) args.push(val1, val2); |
| else if (val1 !== undefined) args.push(val1); |
| |
| const awf = opts.awaitWriteFinish; |
| let pw; |
| if (awf && (pw = this._pendingWrites.get(path))) { |
| pw.lastChange = new Date(); |
| return this; |
| } |
| |
| if (opts.atomic) { |
| if (event === EV_UNLINK) { |
| this._pendingUnlinks.set(path, args); |
| setTimeout(() => { |
| this._pendingUnlinks.forEach((entry, path) => { |
| this.emit(...entry); |
| this.emit(EV_ALL, ...entry); |
| this._pendingUnlinks.delete(path); |
| }); |
| }, typeof opts.atomic === 'number' ? opts.atomic : 100); |
| return this; |
| } |
| if (event === EV_ADD && this._pendingUnlinks.has(path)) { |
| event = args[0] = EV_CHANGE; |
| this._pendingUnlinks.delete(path); |
| } |
| } |
| |
| if (awf && (event === EV_ADD || event === EV_CHANGE) && this._readyEmitted) { |
| const awfEmit = (err, stats) => { |
| if (err) { |
| event = args[0] = EV_ERROR; |
| args[1] = err; |
| this.emitWithAll(event, args); |
| } else if (stats) { |
| // if stats doesn't exist the file must have been deleted |
| if (args.length > 2) { |
| args[2] = stats; |
| } else { |
| args.push(stats); |
| } |
| this.emitWithAll(event, args); |
| } |
| }; |
| |
| this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit); |
| return this; |
| } |
| |
| if (event === EV_CHANGE) { |
| const isThrottled = !this._throttle(EV_CHANGE, path, 50); |
| if (isThrottled) return this; |
| } |
| |
| if (opts.alwaysStat && val1 === undefined && |
| (event === EV_ADD || event === EV_ADD_DIR || event === EV_CHANGE) |
| ) { |
| const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path; |
| let stats; |
| try { |
| stats = await stat(fullPath); |
| } catch (err) {} |
| // Suppress event when fs_stat fails, to avoid sending undefined 'stat' |
| if (!stats || this.closed) return; |
| args.push(stats); |
| } |
| this.emitWithAll(event, args); |
| |
| return this; |
| } |
| |
| /** |
| * Common handler for errors |
| * @param {Error} error |
| * @returns {Error|Boolean} The error if defined, otherwise the value of the FSWatcher instance's `closed` flag |
| */ |
| _handleError(error) { |
| const code = error && error.code; |
| if (error && code !== 'ENOENT' && code !== 'ENOTDIR' && |
| (!this.options.ignorePermissionErrors || (code !== 'EPERM' && code !== 'EACCES')) |
| ) { |
| this.emit(EV_ERROR, error); |
| } |
| return error || this.closed; |
| } |
| |
| /** |
| * Helper utility for throttling |
| * @param {ThrottleType} actionType type being throttled |
| * @param {Path} path being acted upon |
| * @param {Number} timeout duration of time to suppress duplicate actions |
| * @returns {Object|false} tracking object or false if action should be suppressed |
| */ |
| _throttle(actionType, path, timeout) { |
| if (!this._throttled.has(actionType)) { |
| this._throttled.set(actionType, new Map()); |
| } |
| |
| /** @type {Map<Path, Object>} */ |
| const action = this._throttled.get(actionType); |
| /** @type {Object} */ |
| const actionPath = action.get(path); |
| |
| if (actionPath) { |
| actionPath.count++; |
| return false; |
| } |
| |
| let timeoutObject; |
| const clear = () => { |
| const item = action.get(path); |
| const count = item ? item.count : 0; |
| action.delete(path); |
| clearTimeout(timeoutObject); |
| if (item) clearTimeout(item.timeoutObject); |
| return count; |
| }; |
| timeoutObject = setTimeout(clear, timeout); |
| const thr = {timeoutObject, clear, count: 0}; |
| action.set(path, thr); |
| return thr; |
| } |
| |
| _incrReadyCount() { |
| return this._readyCount++; |
| } |
| |
| /** |
| * Awaits write operation to finish. |
| * Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback. |
| * @param {Path} path being acted upon |
| * @param {Number} threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished |
| * @param {EventName} event |
| * @param {Function} awfEmit Callback to be called when ready for event to be emitted. |
| */ |
| _awaitWriteFinish(path, threshold, event, awfEmit) { |
| let timeoutHandler; |
| |
| let fullPath = path; |
| if (this.options.cwd && !sysPath.isAbsolute(path)) { |
| fullPath = sysPath.join(this.options.cwd, path); |
| } |
| |
| const now = new Date(); |
| |
| const awaitWriteFinish = (prevStat) => { |
| fs.stat(fullPath, (err, curStat) => { |
| if (err || !this._pendingWrites.has(path)) { |
| if (err && err.code !== 'ENOENT') awfEmit(err); |
| return; |
| } |
| |
| const now = Number(new Date()); |
| |
| if (prevStat && curStat.size !== prevStat.size) { |
| this._pendingWrites.get(path).lastChange = now; |
| } |
| const pw = this._pendingWrites.get(path); |
| const df = now - pw.lastChange; |
| |
| if (df >= threshold) { |
| this._pendingWrites.delete(path); |
| awfEmit(undefined, curStat); |
| } else { |
| timeoutHandler = setTimeout( |
| awaitWriteFinish, |
| this.options.awaitWriteFinish.pollInterval, |
| curStat |
| ); |
| } |
| }); |
| }; |
| |
| if (!this._pendingWrites.has(path)) { |
| this._pendingWrites.set(path, { |
| lastChange: now, |
| cancelWait: () => { |
| this._pendingWrites.delete(path); |
| clearTimeout(timeoutHandler); |
| return event; |
| } |
| }); |
| timeoutHandler = setTimeout( |
| awaitWriteFinish, |
| this.options.awaitWriteFinish.pollInterval |
| ); |
| } |
| } |
| |
| _getGlobIgnored() { |
| return [...this._ignoredPaths.values()]; |
| } |
| |
| /** |
| * Determines whether user has asked to ignore this path. |
| * @param {Path} path filepath or dir |
| * @param {fs.Stats=} stats result of fs.stat |
| * @returns {Boolean} |
| */ |
| _isIgnored(path, stats) { |
| if (this.options.atomic && DOT_RE.test(path)) return true; |
| if (!this._userIgnored) { |
| const {cwd} = this.options; |
| const ign = this.options.ignored; |
| |
| const ignored = ign && ign.map(normalizeIgnored(cwd)); |
| const paths = arrify(ignored) |
| .filter((path) => typeof path === STRING_TYPE && !isGlob(path)) |
| .map((path) => path + SLASH_GLOBSTAR); |
| const list = this._getGlobIgnored().map(normalizeIgnored(cwd)).concat(ignored, paths); |
| this._userIgnored = anymatch(list, undefined, ANYMATCH_OPTS); |
| } |
| |
| return this._userIgnored([path, stats]); |
| } |
| |
| _isntIgnored(path, stat) { |
| return !this._isIgnored(path, stat); |
| } |
| |
| /** |
| * Provides a set of common helpers and properties relating to symlink and glob handling. |
| * @param {Path} path file, directory, or glob pattern being watched |
| * @param {Number=} depth at any depth > 0, this isn't a glob |
| * @returns {WatchHelper} object containing helpers for this path |
| */ |
| _getWatchHelpers(path, depth) { |
| const watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path); |
| const follow = this.options.followSymlinks; |
| |
| return new WatchHelper(path, watchPath, follow, this); |
| } |
| |
| // Directory helpers |
| // ----------------- |
| |
| /** |
| * Provides directory tracking objects |
| * @param {String} directory path of the directory |
| * @returns {DirEntry} the directory's tracking object |
| */ |
| _getWatchedDir(directory) { |
| if (!this._boundRemove) this._boundRemove = this._remove.bind(this); |
| const dir = sysPath.resolve(directory); |
| if (!this._watched.has(dir)) this._watched.set(dir, new DirEntry(dir, this._boundRemove)); |
| return this._watched.get(dir); |
| } |
| |
| // File helpers |
| // ------------ |
| |
| /** |
| * Check for read permissions. |
| * Based on this answer on SO: https://stackoverflow.com/a/11781404/1358405 |
| * @param {fs.Stats} stats - object, result of fs_stat |
| * @returns {Boolean} indicates whether the file can be read |
| */ |
| _hasReadPermissions(stats) { |
| if (this.options.ignorePermissionErrors) return true; |
| |
| // stats.mode may be bigint |
| const md = stats && Number.parseInt(stats.mode, 10); |
| const st = md & 0o777; |
| const it = Number.parseInt(st.toString(8)[0], 10); |
| return Boolean(4 & it); |
| } |
| |
| /** |
| * Handles emitting unlink events for |
| * files and directories, and via recursion, for |
| * files and directories within directories that are unlinked |
| * @param {String} directory within which the following item is located |
| * @param {String} item base path of item/directory |
| * @returns {void} |
| */ |
| _remove(directory, item, isDirectory) { |
| // if what is being deleted is a directory, get that directory's paths |
| // for recursive deleting and cleaning of watched object |
| // if it is not a directory, nestedDirectoryChildren will be empty array |
| const path = sysPath.join(directory, item); |
| const fullPath = sysPath.resolve(path); |
| isDirectory = isDirectory != null |
| ? isDirectory |
| : this._watched.has(path) || this._watched.has(fullPath); |
| |
| // prevent duplicate handling in case of arriving here nearly simultaneously |
| // via multiple paths (such as _handleFile and _handleDir) |
| if (!this._throttle('remove', path, 100)) return; |
| |
| // if the only watched file is removed, watch for its return |
| if (!isDirectory && !this.options.useFsEvents && this._watched.size === 1) { |
| this.add(directory, item, true); |
| } |
| |
| // This will create a new entry in the watched object in either case |
| // so we got to do the directory check beforehand |
| const wp = this._getWatchedDir(path); |
| const nestedDirectoryChildren = wp.getChildren(); |
| |
| // Recursively remove children directories / files. |
| nestedDirectoryChildren.forEach(nested => this._remove(path, nested)); |
| |
| // Check if item was on the watched list and remove it |
| const parent = this._getWatchedDir(directory); |
| const wasTracked = parent.has(item); |
| parent.remove(item); |
| |
| // Fixes issue #1042 -> Relative paths were detected and added as symlinks |
| // (https://github.com/paulmillr/chokidar/blob/e1753ddbc9571bdc33b4a4af172d52cb6e611c10/lib/nodefs-handler.js#L612), |
| // but never removed from the map in case the path was deleted. |
| // This leads to an incorrect state if the path was recreated: |
| // https://github.com/paulmillr/chokidar/blob/e1753ddbc9571bdc33b4a4af172d52cb6e611c10/lib/nodefs-handler.js#L553 |
| if (this._symlinkPaths.has(fullPath)) { |
| this._symlinkPaths.delete(fullPath); |
| } |
| |
| // If we wait for this file to be fully written, cancel the wait. |
| let relPath = path; |
| if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path); |
| if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) { |
| const event = this._pendingWrites.get(relPath).cancelWait(); |
| if (event === EV_ADD) return; |
| } |
| |
| // The Entry will either be a directory that just got removed |
| // or a bogus entry to a file, in either case we have to remove it |
| this._watched.delete(path); |
| this._watched.delete(fullPath); |
| const eventName = isDirectory ? EV_UNLINK_DIR : EV_UNLINK; |
| if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path); |
| |
| // Avoid conflicts if we later create another file with the same name |
| if (!this.options.useFsEvents) { |
| this._closePath(path); |
| } |
| } |
| |
| /** |
| * Closes all watchers for a path |
| * @param {Path} path |
| */ |
| _closePath(path) { |
| this._closeFile(path); |
| const dir = sysPath.dirname(path); |
| this._getWatchedDir(dir).remove(sysPath.basename(path)); |
| } |
| |
| /** |
| * Closes only file-specific watchers |
| * @param {Path} path |
| */ |
| _closeFile(path) { |
| const closers = this._closers.get(path); |
| if (!closers) return; |
| closers.forEach(closer => closer()); |
| this._closers.delete(path); |
| } |
| |
| /** |
| * |
| * @param {Path} path |
| * @param {Function} closer |
| */ |
| _addPathCloser(path, closer) { |
| if (!closer) return; |
| let list = this._closers.get(path); |
| if (!list) { |
| list = []; |
| this._closers.set(path, list); |
| } |
| list.push(closer); |
| } |
| |
| _readdirp(root, opts) { |
| if (this.closed) return; |
| const options = {type: EV_ALL, alwaysStat: true, lstat: true, ...opts}; |
| let stream = readdirp(root, options); |
| this._streams.add(stream); |
| stream.once(STR_CLOSE, () => { |
| stream = undefined; |
| }); |
| stream.once(STR_END, () => { |
| if (stream) { |
| this._streams.delete(stream); |
| stream = undefined; |
| } |
| }); |
| return stream; |
| } |
| |
| } |
| |
| // Export FSWatcher class |
| chokidar$1.FSWatcher = FSWatcher; |
| |
| /** |
| * Instantiates watcher with paths to be tracked. |
| * @param {String|Array<String>} paths file/directory paths and/or globs |
| * @param {Object=} options chokidar opts |
| * @returns an instance of FSWatcher for chaining. |
| */ |
| const watch = (paths, options) => { |
| const watcher = new FSWatcher(options); |
| watcher.add(paths); |
| return watcher; |
| }; |
| |
| chokidar$1.watch = watch; |
| |
| var chokidar = chokidar$1; |
| |
| exports.braces_1 = braces_1; |
| exports.chokidar = chokidar; |
| exports.picomatch = picomatch$2; |
| exports.utils = utils$3; |
| //# sourceMappingURL=index.js.map |