GitHub
Tests: 12 • Commercial: 2 • Pet projects: 4 • Legacy: 4
Total: 22

.NET Framework

Test
2021

Project Request

ASP.NET MVC • C# • SQL Server
Idea of the project: if someone wants to order a project development, here you can send an application.
Test
2020

ProjectC

ASP.NET MVC • C# • JSON • jQuery
JSON data processing.
Test
2020

Vehicle Maintenance

ASP.NET MVC • VB.NET • JSON
Idea of the project: if someone wants to order a project development, here you can send an application.
Test
2019

Movie Navigator

ASP.NET MVC • VB.NET
Request information about movie from IMDB.
Test
2018

Customers Exchange

ASP.NET MVC • C# • SQL Server
Automated teller machine emulation.
Test
2016

ATM

ASP.NET MVC • C#
Automated teller machine emulation.

.NET Core

Pet project
2022

Mail Daemon

.NET 9 • Console • JSON
Utility to send mails with customizable settings.

Custom

Code
2024

Buns of code

.NET Framework • C# • JavaScript
Code snippets from my projects, ready to use; tiny tests; code examples.

PHP

Test
2024

Mediabox

PHP 8 • Laravel 11 • Vue.js • Composer • SQLite
Test project for media files management.
Test
2020

Loan Castle

PHP • MariaDB
Jums jāizstrādā kāda lielāk projekta prototips. Izstrādājot prototipu, paturiet prātā, ka projektam attīstoties, šo prototipu varētu vajadzēt pilnveidot.
Test
2020

Content Management

PHP • MySQL • AJAX
Создать простой сайт, где будет страница с формой для авторизации и страница для авторизованного пользователя.
Test
2019

Laravel

PHP • Laravel • Vue.js • Composer • SQLite
Izveidot aplikāciju, kura ik pēc noteikta intervāla (60 sekundes) veic ierakstu datubāzē izmantojot Laravel freimworka iebūvēto funkcionalitāti.
Test
2019

Phone Check

PHP • JavaScript • JSON • Docker
Implement application to detect country by phone number.

Frontend

Test
2021

Forex Wall

npm • React
For this exercise, what we need is a simple live wall for tracking currencies.

Business projects

Commercial
2008

Certification Center

.NET Framework 4.8 • ASP.NET Web Forms • C# • LINQ • SQL Server • ADO.NET • Dapper • JavaScript • jQuery • Git
Transport registration and certification services in Latvia, Customer Relationship Management.
Commercial
2000

Amerikas Auto

.NET Framework 4.8 • ASP.NET Web Forms • C# • LINQ • SQL Server • ADO.NET • Entity Framework • JavaScript • jQuery • Git
Car service and spare parts for all USA and European car models, Customer Relationship Management.

Pet projects

Pet project
2023

Geolocation Assistant

.NET 8 • ASP.NET Core • C# • Web API • JSON • Git
Website for determining geolocation by IP or geotagged photo.
Pet project
2008

Web Dynamics

.NET Framework 4.8 • ASP.NET Web Forms • C# • LINQ • Web API • JSON • SQL Server • Dapper • JavaScript • jQuery • SVG • Git
Software development blog. Articles, books, videos, content management.
Pet project
2000

Blackball

.NET Framework 4.8 • ASP.NET Web Forms • C# • LINQ • Web API • JSON • XML • SQL Server • Dapper • JavaScript • jQuery • SVG • Git
My entertainment portal created from scratch.

Good old times

Legacy
2000

DOS Clock

Turbo Pascal • Assembler
Digital clock.
Legacy
2000

BrainOut

Turbo Pascal • Assembler
Tank battle game.
Legacy
1999

Airport Administrator

Turbo Pascal
Курсовая работа в институте.
Legacy
1998

Atomizer

Turbo Pascal • Assembler
Atomizer, aka «Studio2D». Graphic raster editor. AGI is my own «Atomizer Generated Image» file format.

Mediabox

2024 Test

Test project for media files management.

PHP 8 Laravel 11 Vue.js Composer SQLite
Information
Source code
  app
  Http
  .bin
  vite
  lib
  dist
  esm
  node
  lib
  core
  env
  node
  lib
  lib
  lib
  lib
  dist
  lib
  lib
  dist
  es
  lib
  vite
  dist
  node
  dist
  css
  js
  app
  data
  logs
  Unit
  math
  src
  src
  src
  docs
  en
  lib
  src
  src
  Cron
  src
  src
  Core
  ORM
  Spot
  filp
  src
  css
  js
  Util
  src
  src
  src
  src
  psr7
  src
  src
  Core
  Text
  Type
  Xml
  Core
  Text
  Type
  Xml
  src
  Auth
  make
  Bus
  View
  Auth
  Bus
  Http
  Mail
  View
  Auth
  Bus
  Http
  dist
  Http
  Json
  Log
  Mail
  html
  text
  Jobs
  lang
  en
  View
  pint
  pint
  src
  sail
  8.0
  8.1
  8.2
  8.3
  src
  src
  src
  src
  Node
  Util
  Node
  Node
  Node
  Data
  Node
  Node
  Util
  Xml
  src
  src
  src
  docs
  Pass
  src
  Curl
  Test
  src
  Date
  Spl
  lazy
  src
  Cli
  Lang
  List
  src
  src
  lib
  Node
  Expr
  Cast
  Name
  Stmt
  src
  src
  Html
  src
  xml
  src
  src
  src
  Data
  Node
  Html
  css
  js
  Xml
  Util
  src
  src
  src
  src
  src
  Test
  Test
  Math
  Type
  Api
  Rule
  Stub
  Api
  PHPT
  Cli
  Xml
  Util
  PHP
  Xml
  psr
  src
  src
  src
  src
  src
  docs
  src
  log
  src
  src
  psy
  src
  Hoa
  77
  78
  Sudo
  Util
  src
  src
  Map
  Tool
  uuid
  src
  Time
  Guid
  Lazy
  Math
  Dce
  Node
  Time
  Type
  src
  src
  src
  src
  src
  diff
  src
  src
  src
  src
  src
  src
  src
  src
  type
  src
  type
  src
  Test
  CI
  Node
  css
  js
  File
  Test
  Log
  Test
  Smtp
  Auth
  mime
  Part
  Test
  Test
  data
  data
  Test
  Util
  Test
  uid
  css
  js
  Test
  yaml
  Tag
  src
  src
  Css
  Rule
  src
  File
  Util
  voku
  src
  voku
  data
  src
  .env
Root / node_modules / rollup / dist / es / shared / watch.js
/* @license     Rollup.js v4.21.0     Sun, 18 Aug 2024 05:55:06 GMT - commit c4bb050938778bcbe7b3b3ea3419f7fa70d60f5b     https://github.com/rollup/rollup     Released under the MIT License. */ import path$1 from 'node:path'; import process$1 from 'node:process'; import { picomatch as picomatch$2, getAugmentedNamespace, fseventsImporter, createFilter, rollupInternal } from './node-entry.js'; import { platform } from 'node:os'; import require$$0$1 from 'fs'; import require$$2 from 'util'; import require$$1 from 'stream'; import require$$0$2 from 'path'; import require$$2$1 from 'os'; import require$$0$3 from 'events'; import './parseAst.js'; import '../../native.js'; import 'node:perf_hooks'; import 'node:fs/promises'; import 'tty'; var chokidar = {}; const fs$3 = require$$0$1; const { Readable } = require$$1; const sysPath$3 = require$$0$2; const { promisify: promisify$3 } = require$$2; 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('/'); }; var anymatch_1 = anymatch$2.exports; Object.defineProperty(anymatch_1, "__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, false); 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; var anymatchExports = anymatch$2.exports; /*! * 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 strictCheck = function(str) { if (str[0] === '!') { return true; } var index = 0; var pipeIndex = -2; var closeSquareIndex = -2; var closeCurlyIndex = -2; var closeParenIndex = -2; var backSlashIndex = -2; while (index < str.length) { if (str[index] === '*') { return true; } if (str[index + 1] === '?' && /[\].+)]/.test(str[index])) { return true; } if (closeSquareIndex !== -1 && str[index] === '[' && str[index + 1] !== ']') { if (closeSquareIndex < index) { closeSquareIndex = str.indexOf(']', index); } if (closeSquareIndex > index) { if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) { return true; } backSlashIndex = str.indexOf('\\', index); if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) { return true; } } } if (closeCurlyIndex !== -1 && str[index] === '{' && str[index + 1] !== '}') { closeCurlyIndex = str.indexOf('}', index); if (closeCurlyIndex > index) { backSlashIndex = str.indexOf('\\', index); if (backSlashIndex === -1 || backSlashIndex > closeCurlyIndex) { return true; } } } if (closeParenIndex !== -1 && str[index] === '(' && str[index + 1] === '?' && /[:!=]/.test(str[index + 2]) && str[index + 3] !== ')') { closeParenIndex = str.indexOf(')', index); if (closeParenIndex > index) { backSlashIndex = str.indexOf('\\', index); if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) { return true; } } } if (pipeIndex !== -1 && str[index] === '(' && str[index + 1] !== '|') { if (pipeIndex < index) { pipeIndex = str.indexOf('|', index); } if (pipeIndex !== -1 && str[pipeIndex + 1] !== ')') { closeParenIndex = str.indexOf(')', pipeIndex); if (closeParenIndex > pipeIndex) { backSlashIndex = str.indexOf('\\', pipeIndex); if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) { return true; } } } } if (str[index] === '\\') { var open = str[index + 1]; index += 2; var close = chars[open]; if (close) { var n = str.indexOf(close, index); if (n !== -1) { index = n + 1; } } if (str[index] === '!') { return true; } } else { index++; } } return false; }; var relaxedCheck = function(str) { if (str[0] === '!') { return true; } var index = 0; while (index < str.length) { if (/[*?{}()[\]]/.test(str[index])) { return true; } if (str[index] === '\\') { var open = str[index + 1]; index += 2; var close = chars[open]; if (close) { var n = str.indexOf(close, index); if (n !== -1) { index = n + 1; } } if (str[index] === '!') { return true; } } else { index++; } } return false; }; var isGlob$2 = function isGlob(str, options) { if (typeof str !== 'string' || str === '') { return false; } if (isExtglob(str)) { return true; } var check = strictCheck; // optionally relax check if (options && options.strict === false) { check = relaxedCheck; } return check(str); }; var isGlob$1 = isGlob$2; var pathPosixDirname = require$$0$2.posix.dirname; var isWin32 = require$$2$1.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 utils$3 = {}; (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) => {      const 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++) {      const ele = arr[i];      if (Array.isArray(ele)) {      flat(ele);      continue;      }      if (ele !== undefined) {      result.push(ele);      }      }      return result;      };      flat(args);      return result;     }; } (utils$3)); const utils$2 = utils$3; var stringify$4 = (ast, options = {}) => { const stringify = (node, parent = {}) => { const invalidBlock = options.escapeInvalid && utils$2.isInvalidBrace(parent); const invalidNode = node.invalid === true && options.escapeInvalid === true; let output = ''; if (node.value) { if ((invalidBlock || invalidNode) && utils$2.isOpenOrClose(node)) { return '\\' + node.value; } return node.value; } if (node.value) { return node.value; } if (node.nodes) { for (const 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$$2; const toRegexRange = toRegexRange_1; const isObject = 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, maxLen) => { 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.map(v => toMaxLen(String(v), maxLen)).join('|'); } if (parts.negatives.length) { negatives = `-(${prefix}${parts.negatives.map(v => toMaxLen(String(v), maxLen)).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, maxLen) : 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(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(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$1 = utils$3; const compile$1 = (ast, options = {}) => { const walk = (node, parent = {}) => { const invalidBlock = utils$1.isInvalidBrace(parent); const invalidNode = node.invalid === true && options.escapeInvalid === true; const invalid = invalidBlock === true || invalidNode === true; const prefix = options.escapeInvalid === true ? '\\' : ''; let output = ''; if (node.isOpen === true) { return prefix + node.value; } if (node.isClose === true) { console.log('node.isClose', prefix, node.value); 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) { const args = utils$1.reduce(node.nodes); const range = fill$1(...args, { ...options, wrap: false, toRegex: true, strictZeros: true }); if (range.length !== 0) { return args.length > 1 && range.length > 1 ? `(${range})` : range; } } if (node.nodes) { for (const 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 = utils$3; const append = (queue = '', stash = '', enclose = false) => { const result = []; queue = [].concat(queue); stash = [].concat(stash); if (!stash.length) return queue; if (!queue.length) { return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; } for (const item of queue) { if (Array.isArray(item)) { for (const 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.flatten(result); }; const expand$1 = (ast, options = {}) => { const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit; const 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) { const args = utils.reduce(node.nodes); if (utils.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; } const enclose = utils.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++) { const 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.flatten(walk(ast)); }; var expand_1 = expand$1; var constants$1 = { MAX_LENGTH: 10000, // 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, CHAR_BACKSLASH, /* \ */ CHAR_BACKTICK, /* ` */ CHAR_COMMA, /* , */ CHAR_DOT, /* . */ CHAR_LEFT_PARENTHESES, /* ( */ CHAR_RIGHT_PARENTHESES, /* ) */ CHAR_LEFT_CURLY_BRACE, /* { */ CHAR_RIGHT_CURLY_BRACE, /* } */ CHAR_LEFT_SQUARE_BRACKET, /* [ */ CHAR_RIGHT_SQUARE_BRACKET, /* ] */ CHAR_DOUBLE_QUOTE, /* " */ CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE } = constants$1; /** * parse */ const parse$1 = (input, options = {}) => { if (typeof input !== 'string') { throw new TypeError('Expected a string'); } const opts = options || {}; const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; if (input.length > max) { throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); } const ast = { type: 'root', input, nodes: [] }; const stack = [ast]; let block = ast; let prev = ast; let brackets = 0; const 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) { push({ type: 'text', value: '\\' + value }); continue; } /** * Left square bracket: '[' */ if (value === CHAR_LEFT_SQUARE_BRACKET) { brackets++; let next; while (index < length && (next = advance())) { value += next; if (next === CHAR_LEFT_SQUARE_BRACKET) { brackets++; continue; } if (next === CHAR_BACKSLASH) { value += advance(); continue; } if (next === CHAR_RIGHT_SQUARE_BRACKET) { brackets--; if (brackets === 0) { break; } } } push({ type: 'text', value }); continue; } /** * Parentheses */ if (value === CHAR_LEFT_PARENTHESES) { block = push({ type: 'paren', nodes: [] }); stack.push(block); push({ type: 'text', value }); continue; } if (value === CHAR_RIGHT_PARENTHESES) { 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) { const 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) { depth++; const dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; const 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) { if (block.type !== 'brace') { push({ type: 'text', value }); continue; } const type = 'close'; block = stack.pop(); block.close = true; push({ type, value }); depth--; block = stack[stack.length - 1]; continue; } /** * Comma: ',' */ if (value === CHAR_COMMA && depth > 0) { if (block.ranges > 0) { block.ranges = 0; const 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 && depth > 0 && block.commas === 0) { const 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(); const 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) const parent = stack[stack.length - 1]; const 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 = parse$1; const stringify = stringify$4; const compile = compile_1; const expand = expand_1; const parse = parse_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 (const pattern of input) { const 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(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; const require$$0 = [     "3dm",     "3ds",     "3g2",     "3gp",     "7z",     "a",     "aac",     "adp",     "afdesign",     "afphoto",     "afpub",     "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 = require$$0$2; 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} = require$$0$2;     const {platform} = process;     const os = require$$2$1;     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_FLAG_MUST_SCAN_SUBDIRS = 1;     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 = require$$0$1; const sysPath$2 = require$$0$2; const { promisify: promisify$2 } = require$$2; 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 */ let NodeFsHandler$1 = class NodeFsHandler { /** * @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(); let linkPath; try { linkPath = await fsrealpath(path); } catch (e) { this.fsw._emitReady(); return true; } 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: {}}; const require$$3 = /*@__PURE__*/getAugmentedNamespace(fseventsImporter); const fs$1 = require$$0$1; const sysPath$1 = require$$0$2; const { promisify: promisify$1 } = require$$2; 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_FLAG_MUST_SCAN_SUBDIRS, 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; if (flags & FSEVENT_FLAG_MUST_SCAN_SUBDIRS) 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 */ let FsEventsHandler$1 = class FsEventsHandler { /** * @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; var fseventsHandlerExports = fseventsHandler.exports; const { EventEmitter } = require$$0$3; const fs = require$$0$1; const sysPath = require$$0$2; const { promisify } = require$$2; const readdirp = readdirp_1; const anymatch = anymatchExports.default; const globParent = globParent$1; const isGlob = isGlob$2; const braces = braces_1; const normalizePath = normalizePath$2; const NodeFsHandler = nodefsHandler; const FsEventsHandler = fseventsHandlerExports; 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 += paths.length; 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.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.watch = watch; class FileWatcher { constructor(task, chokidarOptions) { this.transformWatchers = new Map(); this.chokidarOptions = chokidarOptions; this.task = task; this.watcher = this.createWatcher(null); } close() { this.watcher.close(); for (const watcher of this.transformWatchers.values()) { watcher.close(); } } unwatch(id) { this.watcher.unwatch(id); const transformWatcher = this.transformWatchers.get(id); if (transformWatcher) { this.transformWatchers.delete(id); transformWatcher.close(); } } watch(id, isTransformDependency) { if (isTransformDependency) { const watcher = this.transformWatchers.get(id) ?? this.createWatcher(id); watcher.add(id); this.transformWatchers.set(id, watcher); } else { this.watcher.add(id); } } createWatcher(transformWatcherId) { const task = this.task; const isLinux = platform() === 'linux'; const isTransformDependency = transformWatcherId !== null; const handleChange = (id, event) => { const changedId = transformWatcherId || id; if (isLinux) { // unwatching and watching fixes an issue with chokidar where on certain systems, // a file that was unlinked and immediately recreated would create a change event // but then no longer any further events watcher.unwatch(changedId); watcher.add(changedId); } task.invalidate(changedId, { event, isTransformDependency }); }; const watcher = chokidar .watch([], this.chokidarOptions) .on('add', id => handleChange(id, 'create')) .on('change', id => handleChange(id, 'update')) .on('unlink', id => handleChange(id, 'delete')); return watcher; } } const eventsRewrites = { create: { create: 'buggy', delete: null, //delete file from map update: 'create' }, delete: { create: 'update', delete: 'buggy', update: 'buggy' }, update: { create: 'buggy', delete: 'delete', update: 'update' } }; class Watcher { constructor(optionsList, emitter) { this.buildDelay = 0; this.buildTimeout = null; this.closed = false; this.invalidatedIds = new Map(); this.rerun = false; this.running = true; this.emitter = emitter; emitter.close = this.close.bind(this); this.tasks = optionsList.map(options => new Task(this, options)); for (const { watch } of optionsList) { if (watch && typeof watch.buildDelay === 'number') { this.buildDelay = Math.max(this.buildDelay, watch.buildDelay); } } process$1.nextTick(() => this.run()); } async close() { if (this.closed) return; this.closed = true; if (this.buildTimeout) clearTimeout(this.buildTimeout); for (const task of this.tasks) { task.close(); } await this.emitter.emit('close'); this.emitter.removeAllListeners(); } invalidate(file) { if (file) { const previousEvent = this.invalidatedIds.get(file.id); const event = previousEvent ? eventsRewrites[previousEvent][file.event] : file.event; if (event === 'buggy') { //TODO: throws or warn? Currently just ignore, uses new event this.invalidatedIds.set(file.id, file.event); } else if (event === null) { this.invalidatedIds.delete(file.id); } else { this.invalidatedIds.set(file.id, event); } } if (this.running) { this.rerun = true; return; } if (this.buildTimeout) clearTimeout(this.buildTimeout); this.buildTimeout = setTimeout(async () => { this.buildTimeout = null; try { await Promise.all([...this.invalidatedIds].map(([id, event]) => this.emitter.emit('change', id, { event }))); this.invalidatedIds.clear(); await this.emitter.emit('restart'); this.emitter.removeListenersForCurrentRun(); this.run(); } catch (error) { this.invalidatedIds.clear(); await this.emitter.emit('event', { code: 'ERROR', error, result: null }); await this.emitter.emit('event', { code: 'END' }); } }, this.buildDelay); } async run() { this.running = true; await this.emitter.emit('event', { code: 'START' }); for (const task of this.tasks) { await task.run(); } this.running = false; await this.emitter.emit('event', { code: 'END' }); if (this.rerun) { this.rerun = false; this.invalidate(); } } } class Task { constructor(watcher, options) { this.cache = { modules: [] }; this.watchFiles = []; this.closed = false; this.invalidated = true; this.watched = new Set(); this.watcher = watcher; this.options = options; this.skipWrite = Boolean(options.watch && options.watch.skipWrite); this.outputs = this.options.output; this.outputFiles = this.outputs.map(output => { if (output.file || output.dir) return path$1.resolve(output.file || output.dir); return undefined; }); const watchOptions = this.options.watch || {}; this.filter = createFilter(watchOptions.include, watchOptions.exclude); this.fileWatcher = new FileWatcher(this, { ...watchOptions.chokidar, disableGlobbing: true, ignoreInitial: true }); } close() { this.closed = true; this.fileWatcher.close(); } invalidate(id, details) { this.invalidated = true; if (details.isTransformDependency) { for (const module of this.cache.modules) { if (!module.transformDependencies.includes(id)) continue; // effective invalidation module.originalCode = null; } } this.watcher.invalidate({ event: details.event, id }); } async run() { if (!this.invalidated) return; this.invalidated = false; const options = { ...this.options, cache: this.cache }; const start = Date.now(); await this.watcher.emitter.emit('event', { code: 'BUNDLE_START', input: this.options.input, output: this.outputFiles }); let result = null; try { result = await rollupInternal(options, this.watcher.emitter); if (this.closed) { return; } this.updateWatchedFiles(result); if (!this.skipWrite) { await Promise.all(this.outputs.map(output => result.write(output))); if (this.closed) { return; } this.updateWatchedFiles(result); } await this.watcher.emitter.emit('event', { code: 'BUNDLE_END', duration: Date.now() - start, input: this.options.input, output: this.outputFiles, result }); } catch (error) { if (!this.closed) { if (Array.isArray(error.watchFiles)) { for (const id of error.watchFiles) { this.watchFile(id); } } if (error.id) { this.cache.modules = this.cache.modules.filter(module => module.id !== error.id); } } await this.watcher.emitter.emit('event', { code: 'ERROR', error, result }); } } updateWatchedFiles(result) { const previouslyWatched = this.watched; this.watched = new Set(); this.watchFiles = result.watchFiles; this.cache = result.cache; for (const id of this.watchFiles) { this.watchFile(id); } for (const module of this.cache.modules) { for (const depId of module.transformDependencies) { this.watchFile(depId, true); } } for (const id of previouslyWatched) { if (!this.watched.has(id)) { this.fileWatcher.unwatch(id); } } } watchFile(id, isTransformDependency = false) { if (!this.filter(id)) return; this.watched.add(id); if (this.outputFiles.includes(id)) { throw new Error('Cannot import the generated bundle'); } // this is necessary to ensure that any 'renamed' files // continue to be watched following an error this.fileWatcher.watch(id, isTransformDependency); } } export { Task, Watcher };