diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 48912d244b..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build -node_modules \ No newline at end of file diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 93f7ec7947..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,53 +0,0 @@ -{ - "env": { - "browser": true, - "node": true, - "es6": true - }, - "parser": "@babel/eslint-parser", - "parserOptions": { - "sourceType": "module", - "requireConfigFile": false, - "allowImportExportEverywhere": true, - "babelOptions": { - "presets": ["@babel/preset-react"] - } - }, - "extends": [ - "plugin:import/recommended", - "plugin:react/recommended", - "plugin:eslint-plugin/recommended", - "plugin:prettier/recommended" - ], - "rules": { - "prettier/prettier": ["error"], - "import/extensions": ["error", "ignorePackages"], - "react/prop-types": "off", - "react/jsx-props-no-spreading": "off", - "no-param-reassign": "off", - "no-underscore-dangle": "off", - "no-continue": "off", - "no-restricted-syntax": "off", - "class-methods-use-this": "off", - "no-plusplus": "off", - "no-empty": "off", - "max-classes-per-file": "off", - "no-bitwise": "off", - "import/no-named-as-default": "off", - "import/order": [ - "error", - { - "groups": ["builtin", "external", "parent", "sibling", "index"], - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ] - }, - "settings": { - "react": { - "version": "detect" - } - } -} diff --git a/dev/post-install.js b/dev/post-install.js index 43b06e9909..ec61ad8e20 100644 --- a/dev/post-install.js +++ b/dev/post-install.js @@ -1,5 +1,5 @@ import {statSync, writeFileSync, readFileSync, lstatSync} from 'fs'; -// eslint-disable-next-line import/no-unresolved + import {globSync} from 'glob'; import {escapeRegExp} from '../src/utils/regex.js'; @@ -16,7 +16,6 @@ const files = globSync('node_modules/*rsuite*/**/*.+(js|ts|tsx|less|css)', {}).f ); for (const pathname of files) { if (lstatSync(pathname).isDirectory()) { - // eslint-disable-next-line no-console console.warn(`[POST INSTALL] Processing file ${pathname}, but it is a directory. skipping...`); continue; } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..8a9c94e115 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,85 @@ +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import babelParser from '@babel/eslint-parser'; +import {fixupConfigRules} from '@eslint/compat'; +import {FlatCompat} from '@eslint/eslintrc'; +import js from '@eslint/js'; +import globals from 'globals'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default [ + { + ignores: ['**/build', '**/node_modules'], + }, + ...fixupConfigRules( + compat.extends( + 'plugin:import/recommended', + 'plugin:react/recommended', + 'plugin:eslint-plugin/recommended', + 'plugin:prettier/recommended' + ) + ), + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + + parser: babelParser, + ecmaVersion: 6, + sourceType: 'module', + + parserOptions: { + requireConfigFile: false, + allowImportExportEverywhere: true, + + babelOptions: { + presets: ['@babel/preset-react'], + }, + }, + }, + + settings: { + react: { + version: 'detect', + }, + }, + + rules: { + 'prettier/prettier': ['error'], + 'import/extensions': ['error', 'ignorePackages'], + 'react/prop-types': 'off', + 'react/jsx-props-no-spreading': 'off', + 'no-param-reassign': 'off', + 'no-underscore-dangle': 'off', + 'no-continue': 'off', + 'no-restricted-syntax': 'off', + 'class-methods-use-this': 'off', + 'no-plusplus': 'off', + 'no-empty': 'off', + 'max-classes-per-file': 'off', + 'no-bitwise': 'off', + 'import/no-named-as-default': 'off', + + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'parent', 'sibling', 'index'], + + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + }, + ], + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index 872722444e..42a7ac68cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,6 +84,12 @@ "webpack-cli": "6.0.1", "webpack-dev-server": "5.2.0", "webpack-virtual-modules": "0.6.2" + }, + "devDependencies": { + "@eslint/compat": "^1.2.4", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.17.0", + "globals": "^15.14.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -792,6 +798,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", @@ -1659,6 +1674,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", @@ -1722,6 +1746,24 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.4.tgz", + "integrity": "sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/config-array": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", @@ -1761,6 +1803,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1805,6 +1848,7 @@ "version": "9.17.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -6883,10 +6927,16 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -12888,6 +12938,13 @@ "@babel/helper-replace-supers": "^7.25.9", "@babel/traverse": "^7.25.9", "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + } } }, "@babel/plugin-transform-computed-properties": { @@ -13447,6 +13504,13 @@ "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + } } }, "@babel/types": { @@ -13493,6 +13557,13 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" }, + "@eslint/compat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.4.tgz", + "integrity": "sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==", + "dev": true, + "requires": {} + }, "@eslint/config-array": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", @@ -17147,8 +17218,10 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "globals": { - "version": "11.12.0", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true }, "globalthis": { "version": "1.0.4", diff --git a/package.json b/package.json index ce9e198dc2..33d2d04e22 100644 --- a/package.json +++ b/package.json @@ -97,5 +97,11 @@ "browserslist": [ "last 1 version", "> 0.25%, not dead" - ] + ], + "devDependencies": { + "@eslint/compat": "^1.2.4", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.17.0", + "globals": "^15.14.0" + } } diff --git a/src/constants.js b/src/constants.js index f6dad29c72..78b4601b56 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,5 +1,3 @@ -/* eslint-disable prefer-destructuring */ - export const NODE_ENV = process.env.NODE_ENV; export const EXT_VER = process.env.EXT_VER; export const GIT_REV = process.env.GIT_REV; diff --git a/src/i18n/index.js b/src/i18n/index.js index 25fd6c2a47..7f9c6755af 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -28,7 +28,6 @@ let browserLocale = Array.isArray(navigator.languages) ? navigator.languages[0] browserLocale = browserLocale || navigator.language || navigator.browserLanguage || navigator.userLanguage; browserLocale = browserLocale.replace('_', '-'); if (!SUPPORTED_LOCALES.includes(browserLocale)) { - // eslint-disable-next-line prefer-destructuring browserLocale = browserLocale.split('-')[0]; } if (!SUPPORTED_LOCALES.includes(browserLocale)) { @@ -46,7 +45,6 @@ function getSiteLocale() { } locale = locale.replace('_', '-'); if (!SUPPORTED_LOCALES.includes(locale)) { - // eslint-disable-next-line prefer-destructuring locale = locale.split('-')[0]; } if (!SUPPORTED_LOCALES.includes(locale)) { @@ -71,7 +69,6 @@ export async function load() { Settings.defaultLocale = locale; } -// eslint-disable-next-line import/prefer-default-export export default function formatMessage(descriptor, values = undefined) { if (intl == null) { throw new Error('i18n not yet loaded'); diff --git a/src/modules/chat_custom_timeouts/index.js b/src/modules/chat_custom_timeouts/index.js index bb46ed953c..dbb61ed3bc 100644 --- a/src/modules/chat_custom_timeouts/index.js +++ b/src/modules/chat_custom_timeouts/index.js @@ -71,7 +71,6 @@ function createCustomTimeout() { } function setReason(type) { - /* eslint-disable-next-line no-alert */ const reason = prompt(formatMessage({defaultMessage: 'Enter {type} reason: (leave blank for none)'}, {type})); return reason || ''; } diff --git a/src/modules/chat_deleted_messages/index.js b/src/modules/chat_deleted_messages/index.js index 7c8fd7ca70..bec72c05d5 100644 --- a/src/modules/chat_deleted_messages/index.js +++ b/src/modules/chat_deleted_messages/index.js @@ -98,7 +98,6 @@ class ChatDeletedMessagesModule { } const messages = findAllUserMessages(name, targetMessageId); messages.forEach((message) => { - // eslint-disable-next-line default-case switch (deletedMessages) { case DeletedMessageTypes.HIDE: message.style.display = 'none'; @@ -109,7 +108,7 @@ class ChatDeletedMessagesModule { ChatHighlightBlacklistKeywords.markHighlighted(message); } message.classList.toggle(CHAT_LINE_DELETED_CLASS, true); - /* eslint-disable-next-line func-names */ + message.querySelectorAll(CHAT_LINE_LINK_SELECTOR).forEach((node) => { node.removeAttribute('href'); }); diff --git a/src/modules/chat_font_settings/index.js b/src/modules/chat_font_settings/index.js index 63356e42e5..66dd5a8eaf 100644 --- a/src/modules/chat_font_settings/index.js +++ b/src/modules/chat_font_settings/index.js @@ -44,7 +44,6 @@ function updateFontSettings() { } function changeFontSetting(promptBody, storageID) { - /* eslint-disable-next-line no-alert */ let keywords = prompt(promptBody, storage.get(storageID) || ''); if (keywords !== null) { keywords = keywords.trim(); diff --git a/src/modules/chat_nicknames/index.js b/src/modules/chat_nicknames/index.js index 2d40fb43ae..7b4bfba283 100644 --- a/src/modules/chat_nicknames/index.js +++ b/src/modules/chat_nicknames/index.js @@ -9,7 +9,6 @@ class ChatNicknamesModule { } set(name) { - /* eslint-disable-next-line no-alert */ let nickname = prompt( formatMessage({defaultMessage: 'Enter the updated nickname for {name} (Leave blank to reset)'}, {name}), nicknames[name] || name diff --git a/src/modules/emote_menu/utils/emojis.js b/src/modules/emote_menu/utils/emojis.js index 3c7d16498e..1211da22eb 100644 --- a/src/modules/emote_menu/utils/emojis.js +++ b/src/modules/emote_menu/utils/emojis.js @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ import {EmoteCategories, EmoteProviders} from '../../../constants.js'; import emoji from '../../emotes/emojis.js'; import Icons from '../components/Icons.jsx'; diff --git a/src/modules/emote_menu/utils/twitch-emotes.js b/src/modules/emote_menu/utils/twitch-emotes.js index 796e08d9cd..14d6c937be 100644 --- a/src/modules/emote_menu/utils/twitch-emotes.js +++ b/src/modules/emote_menu/utils/twitch-emotes.js @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ import gql from 'graphql-tag'; import sortBy from 'lodash.sortby'; import uniqBy from 'lodash.uniqby'; diff --git a/src/modules/emote_menu/utils/youtube-emotes.js b/src/modules/emote_menu/utils/youtube-emotes.js index b8c74713ee..ecf905fb98 100644 --- a/src/modules/emote_menu/utils/youtube-emotes.js +++ b/src/modules/emote_menu/utils/youtube-emotes.js @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ import sortBy from 'lodash.sortby'; import uniqBy from 'lodash.uniqby'; import {EmoteCategories, EmoteProviders} from '../../../constants.js'; diff --git a/src/modules/emotes/emojis.js b/src/modules/emotes/emojis.js index 8087599227..84a78a70a3 100644 --- a/src/modules/emotes/emojis.js +++ b/src/modules/emotes/emojis.js @@ -82,7 +82,6 @@ class Emojis extends AbstractEmotes { }); if (!emoji.isAlternative) { - // eslint-disable-next-line no-prototype-builtins let categoryEmotes = this.emotesByCategory[emoji.category]; if (categoryEmotes == null) { diff --git a/src/utils/emote.js b/src/utils/emote.js index c62ddf29a1..52db7f2cbb 100644 --- a/src/utils/emote.js +++ b/src/utils/emote.js @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ export function getCanonicalEmoteId(emoteId, emoteProvider) { return `${emoteProvider}-${emoteId}`; } diff --git a/src/utils/image.js b/src/utils/image.js index 0a3c25e564..253e3a17a4 100644 --- a/src/utils/image.js +++ b/src/utils/image.js @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ export const DEFAULT_SIZES = ['1x', '2x', '4x']; const STATIC_SIZES = { '1x': '1x_static', diff --git a/src/utils/legacy-settings.js b/src/utils/legacy-settings.js index 802228b808..e4280bc535 100644 --- a/src/utils/legacy-settings.js +++ b/src/utils/legacy-settings.js @@ -1,5 +1,3 @@ -/* eslint-disable import/prefer-default-export */ - import { AutoPlayFlags, ChannelPointsFlags, diff --git a/src/utils/modules.js b/src/utils/modules.js index c38455c63c..807775c89a 100644 --- a/src/utils/modules.js +++ b/src/utils/modules.js @@ -1,5 +1,3 @@ -/* eslint-disable import/prefer-default-export */ - import {getPlatform} from './window.js'; const currentPlatform = getPlatform(); diff --git a/src/utils/sentry-global-handlers-integration.js b/src/utils/sentry-global-handlers-integration.js index 9aa98a1696..f6bebc3bfd 100644 --- a/src/utils/sentry-global-handlers-integration.js +++ b/src/utils/sentry-global-handlers-integration.js @@ -1,10 +1,5 @@ -/* eslint-disable import/prefer-default-export */ -/* eslint-disable no-restricted-globals */ -/* eslint-disable no-multi-assign */ -/* eslint-disable prefer-destructuring */ -// eslint-disable-next-line import/no-extraneous-dependencies import {UNKNOWN_FUNCTION, isString, getLocationHref} from '@sentry/core'; -// eslint-disable-next-line import/no-relative-packages + import {eventFromUnknownInput} from '../../node_modules/@sentry/browser/build/npm/esm/eventbuilder.js'; function _enhanceEventWithInitialFrame(event, url, line, column) { @@ -72,7 +67,6 @@ export function registerBetterTTVGlobalHandlers(scope) { }); if (_oldOnErrorHandler) { - // eslint-disable-next-line prefer-rest-params return _oldOnErrorHandler.apply(this, arguments); } diff --git a/src/utils/twitch.js b/src/utils/twitch.js index ee80a3a8a5..5e23bc158a 100644 --- a/src/utils/twitch.js +++ b/src/utils/twitch.js @@ -200,7 +200,7 @@ export default { twitchWebpackRequire = require; }, }, - // eslint-disable-next-line import/no-unresolved + (require) => require('betterttv'), ]); } diff --git a/src/utils/youtube-ephemeral-messages.js b/src/utils/youtube-ephemeral-messages.js index b3139dfe3d..dbb3f96204 100644 --- a/src/utils/youtube-ephemeral-messages.js +++ b/src/utils/youtube-ephemeral-messages.js @@ -33,7 +33,7 @@ function unmountExpiredEphemeralMessage() { } let YoutubeEphemeralMessage = null; -// eslint-disable-next-line import/prefer-default-export + export async function sendEphemeralMessage(message) { const items = document.querySelector(CHAT_ITEMS_SELECTOR); if (items == null) { @@ -49,7 +49,7 @@ export async function sendEphemeralMessage(message) { const bttvMessageContainer = document.createElement('div'); bttvMessageContainer.id = 'bttv-chat-admin-message'; const root = ReactDOM.createRoot(bttvMessageContainer); - /* eslint-disable-next-line react/jsx-filename-extension, react/react-in-jsx-scope */ + root.render(); // Forces youtube to append following messages after the ephemeral message visibleItems.push({bttvMessageRenderer: {}}); diff --git a/src/utils/youtube.js b/src/utils/youtube.js index eabc0b3298..5f305bde27 100644 --- a/src/utils/youtube.js +++ b/src/utils/youtube.js @@ -1,6 +1,5 @@ import {createSrc, createSrcSet} from './image.js'; -/* eslint-disable import/prefer-default-export */ export function createYoutubeEmojiNode(emote) { const newNode = document.createElement('img'); newNode.className = 'emoji yt-formatted-string style-scope yt-live-chat-text-input-field-renderer'; diff --git a/src/watchers/youtube.js b/src/watchers/youtube.js index 59f2a307fc..75e89df6f9 100644 --- a/src/watchers/youtube.js +++ b/src/watchers/youtube.js @@ -39,7 +39,7 @@ export default function youtubeWatcher(watcher) { const decodedParams = atob(decodeURIComponent(atob(endpointParams))); // this is proto but we don't know the schema and we don't wanna import a proto lib to decode this // this is "probably" going to work ok. - // eslint-disable-next-line prefer-destructuring + newChannelId = decodedParams.split("*'\n\u0018")[1].split('\u0012\u000b')[0]; } diff --git a/webpack.config.js b/webpack.config.js index 44e2addbd7..33fc35540c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ import {CleanWebpackPlugin} from 'clean-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin'; import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; import FileManagerPlugin from 'filemanager-webpack-plugin'; -// eslint-disable-next-line import/no-unresolved + import {globSync} from 'glob'; // eslint-disable-next-line import/no-unresolved import got from 'got';