@adguard/filters-compiler 3.2.11 → 3.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path$1 from 'path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { RuleFactory, CosmeticRule, NetworkRule, setConfiguration, CompatibilityTypes } from '@adguard/tsurlfilter';
4
- import fs$1 from 'fs';
4
+ import fs$2 from 'fs';
5
5
  import md5 from 'md5';
6
6
  import { FiltersDownloader } from '@adguard/filters-downloader';
7
7
  import { ADG_SCRIPTLET_MASK, CosmeticRuleType, AdblockSyntax, RuleParser, RuleConverter, RuleGenerator, CommentParser, RuleCategory, RegExpUtils } from '@adguard/agtree';
@@ -16,6 +16,7 @@ import { createRequire } from 'module';
16
16
  import { JSDOM } from 'jsdom';
17
17
  import crypto from 'crypto';
18
18
  import moment from 'moment';
19
+ import fs$1 from 'fs/promises';
19
20
  import { parse as parse$1 } from 'tldts';
20
21
  import Ajv from 'ajv';
21
22
 
@@ -69,53 +70,43 @@ class CompilerLogger extends Logger {
69
70
  /**
70
71
  * File descriptor
71
72
  *
72
- * @type {number | null}
73
- *
74
73
  * @private
75
74
  */
76
75
  #fd = null;
77
-
76
+ /**
77
+ * Log file path, set after successful initialization.
78
+ */
79
+ logFile;
78
80
  /**
79
81
  * Helper to append message to log file
80
- *
81
- * @param {string} message
82
- * @param {'INFO'|'WARN'|'ERROR'} level
83
- *
84
- * @private
85
82
  */
86
83
  #append(message, level) {
87
84
  if (this.#fd == null) {
88
85
  return;
89
86
  }
90
-
91
87
  const line = `[${new Date().toLocaleTimeString()}] [${level}]: ${message}${os.EOL}`;
92
-
93
88
  // Using appendFileSync with an fd ensures atomic append semantics.
94
89
  fs.appendFileSync(this.#fd, line, 'utf8');
95
90
  }
96
-
97
91
  /** @inheritdoc */
98
92
  info(message) {
99
93
  super.info(message);
100
94
  this.#append(message, 'INFO');
101
95
  }
102
-
103
96
  /** @inheritdoc */
104
97
  error(message) {
105
98
  super.error(message);
106
99
  this.#append(message, 'ERROR');
107
100
  }
108
-
109
101
  /** @inheritdoc */
110
102
  warn(message) {
111
103
  super.warn(message);
112
104
  this.#append(message, 'WARN');
113
105
  }
114
-
115
106
  /**
116
107
  * Initializes logger
117
108
  *
118
- * @param {string} logFilePath - log file path
109
+ * @param logFilePath - log file path
119
110
  *
120
111
  * The log file is opened with 'w' (truncate/create). Subsequent writes are appended.
121
112
  */
@@ -125,26 +116,23 @@ class CompilerLogger extends Logger {
125
116
  console.warn('Log file is not specified');
126
117
  return;
127
118
  }
128
-
129
119
  // Ensure the directory exists before creating the log file
130
120
  const dir = path.dirname(logFilePath);
131
121
  fs.mkdirSync(dir, { recursive: true });
132
-
133
122
  // Close any previous descriptor to avoid leaks
134
123
  if (this.#fd != null) {
135
124
  try {
136
125
  fs.closeSync(this.#fd);
137
- } catch {
126
+ }
127
+ catch {
138
128
  /* noop */
139
129
  }
140
130
  this.#fd = null;
141
131
  }
142
-
143
- // Open (truncate) now; we’ll append to the same fd later.
132
+ // Open (truncate) now; we'll append to the same fd later.
144
133
  this.#fd = fs.openSync(logFilePath, 'w');
145
134
  this.logFile = logFilePath;
146
135
  }
147
-
148
136
  /**
149
137
  * Optional: call to close the file descriptor when done (e.g., on shutdown)
150
138
  */
@@ -152,13 +140,13 @@ class CompilerLogger extends Logger {
152
140
  if (this.#fd != null) {
153
141
  try {
154
142
  fs.closeSync(this.#fd);
155
- } finally {
143
+ }
144
+ finally {
156
145
  this.#fd = null;
157
146
  }
158
147
  }
159
148
  }
160
149
  }
161
-
162
150
  const logger = new CompilerLogger();
163
151
 
164
152
  // TODO: a lot of these masks can be imported from @adguard/agtree
@@ -1009,27 +997,24 @@ const checkAffinityDirectives = (lines) => {
1009
997
  };
1010
998
 
1011
999
  /* eslint-disable global-require */
1012
-
1013
1000
  const require = createRequire(import.meta.url);
1014
-
1015
1001
  /**
1016
1002
  * Some sources require proper user-agents and forbid downloading without.
1017
1003
  */
1018
1004
  const USER_AGENT = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko)'
1019
1005
  + 'Chrome/63.0.3239.132 Mobile Safari/537.36';
1020
-
1021
1006
  /**
1022
- * Sync downloads file from url
1007
+ * Downloads file from url
1023
1008
  *
1024
1009
  * @param url
1025
- * @param {number} [retryNum=0] number of times to retry downloading, defaults to 0
1026
- * @returns {*}
1010
+ * @param retryNum number of times to retry downloading, defaults to 0
1011
+ * @returns raw content of the file
1027
1012
  */
1028
- const tryDownloadFile = function (url, retryNum = 0) {
1013
+ const tryDownloadFile = async function (url, retryNum = 0) {
1029
1014
  let args = ['--fail', '--silent', '--user-agent', USER_AGENT, '-L', url];
1030
1015
  if (retryNum) {
1031
1016
  args.push('--retry');
1032
- args.push(retryNum);
1017
+ args.push(String(retryNum));
1033
1018
  }
1034
1019
  const options = { encoding: 'utf8', maxBuffer: Infinity };
1035
1020
  const tlsCheck = process.env.TLS;
@@ -1039,97 +1024,286 @@ const tryDownloadFile = function (url, retryNum = 0) {
1039
1024
  return require('child_process')
1040
1025
  .execFileSync('curl', args, options);
1041
1026
  };
1042
-
1043
1027
  /**
1044
- * Sync downloads file from url with two attempts
1028
+ * Number of times to retry downloading after the first failed attempt for `downloadFile` function.
1029
+ */
1030
+ const RETRY_NUM = 5;
1031
+ /**
1032
+ * Downloads file from url with two attempts
1045
1033
  *
1046
1034
  * @param url
1047
- * @returns {*}
1035
+ * @returns raw content of the file
1048
1036
  */
1049
- const downloadFile = (url) => {
1037
+ const downloadFile = async (url) => {
1050
1038
  logger.info(`Downloading: ${url}`);
1051
-
1052
1039
  // 5 times to retry after first fail attempt:
1053
1040
  // 1 sec for first time, double for every forthcoming attempts
1054
1041
  // so it will take: 1 + 2 + 4 + 8 + 16 = 31 seconds
1055
1042
  // https://curl.se/docs/manpage.html#--retry
1056
- const RETRY_NUM = 5;
1057
-
1058
1043
  try {
1059
- return tryDownloadFile(url);
1060
- } catch (e) {
1044
+ return await tryDownloadFile(url);
1045
+ }
1046
+ catch (e) {
1061
1047
  logger.warn(e);
1062
1048
  logger.warn(`Retry downloading: ${url}`);
1063
1049
  return tryDownloadFile(url, RETRY_NUM);
1064
1050
  }
1065
1051
  };
1066
1052
 
1067
- /* eslint-disable global-require */
1068
-
1069
- // Here we can access optimizable filters and its optimization percentages
1070
- // eslint-disable-next-line max-len
1071
- const OPTIMIZATION_PERCENT_URL = 'https://chrome.adtidy.org/optimization_config/percent.json?key=4DDBE80A3DA94D819A00523252FB6380';
1072
- // eslint-disable-next-line max-len
1073
- const OPTIMIZATION_STATS_URL = 'https://chrome.adtidy.org/filters/{0}/stats.json?key=4DDBE80A3DA94D819A00523252FB6380';
1074
-
1075
- let filtersOptimizationPercent = null;
1076
-
1077
1053
  /**
1078
- * Downloads and caches filters optimization percentages configuration
1054
+ * Runs `fn` over `items` with at most `concurrency` calls in flight at once.
1055
+ *
1056
+ * @param items - Items to process.
1057
+ * @param max - Max number of concurrent `fn` calls.
1058
+ * @param fn - Async worker invoked for each item.
1079
1059
  */
1080
- const getFiltersOptimizationPercent = () => {
1060
+ const mapWithConcurrency = async (items, max, fn) => {
1061
+ const workerCount = Math.min(max, items.length);
1062
+ // Each worker owns a fixed, disjoint slice of indices (workerIndex, workerIndex + workerCount, ...)
1063
+ // decided up front, so there's no shared mutable state for workers to race over.
1064
+ const worker = async (startIndex) => {
1065
+ for (let i = startIndex; i < items.length; i += workerCount) {
1066
+ // eslint-disable-next-line no-await-in-loop
1067
+ await fn(items[i]);
1068
+ }
1069
+ };
1070
+ await Promise.all(Array.from({ length: workerCount }, (_, startIndex) => worker(startIndex)));
1071
+ };
1081
1072
 
1082
- if (filtersOptimizationPercent === null) {
1083
- filtersOptimizationPercent = JSON.parse(downloadFile(OPTIMIZATION_PERCENT_URL));
1073
+ /**
1074
+ * Optimizes filters by removing low-hit rules using each filter's hit-counts
1075
+ * collected from AdGuard users who opted into filter rules statistics.
1076
+ * Statistics are fetched remotely or read from a local cache.
1077
+ *
1078
+ * @see {@link https://github.com/AdguardTeam/FiltersRegistry#optimization} for more information.
1079
+ *
1080
+ * @see localOptimizationStatistics for managing a local cache of optimization stats files.
1081
+ * @see getOptimizationStatistics for retrieving optimization stats for a filter.
1082
+ * @see skipRuleWithOptimization for checking if a rule should be skipped based on optimization stats.
1083
+ */
1084
+ // Here we can access optimizable filters and its optimization percentages
1085
+ const OPTIMIZATION_KEY = '4DDBE80A3DA94D819A00523252FB6380';
1086
+ const OPTIMIZATION_PERCENT_URL = `https://chrome.adtidy.org/optimization_config/percent.json?key=${OPTIMIZATION_KEY}`;
1087
+ // Path segment constants for the local config directory layout:
1088
+ // <basePath>/filters/<filterId>/stats.json
1089
+ const STATS_JSON = 'stats.json';
1090
+ const FILTERS_DIR_NAME = 'filters';
1091
+ /**
1092
+ * Thrown by `getOptimizationStatistics` when a filter's stats cannot be
1093
+ * retrieved, from either a local file or the remote server.
1094
+ *
1095
+ * Carries `filterId` and `sourcePath` as structured fields so callers can
1096
+ * build their own actionable message instead of matching on `error.message`.
1097
+ */
1098
+ class OptimizationStatsError extends Error {
1099
+ filterId;
1100
+ sourcePath;
1101
+ code = 'OPTIMIZATION_STATS_UNAVAILABLE';
1102
+ constructor(filterId, sourcePath, options) {
1103
+ super(`Unable to retrieve optimization stats for ${filterId}, at ${sourcePath}. `
1104
+ + 'Please ensure the stats file exists and is accessible.', options);
1105
+ this.filterId = filterId;
1106
+ this.sourcePath = sourcePath;
1107
+ this.name = 'OptimizationStatsError';
1084
1108
  }
1085
-
1086
- if (filtersOptimizationPercent.config.length === 0) {
1087
- // eslint-disable-next-line no-throw-literal
1088
- throw 'Invalid configuration';
1109
+ }
1110
+ const downloadOptimizationPercent = async () => downloadFile(OPTIMIZATION_PERCENT_URL);
1111
+ const getOptimizationStatsUrl = (filterId) => `https://chrome.adtidy.org/filters/${filterId}/stats.json?key=${OPTIMIZATION_KEY}`;
1112
+ /**
1113
+ * Downloads optimization stats for a single filter from the remote server.
1114
+ *
1115
+ * @param filterId - Numeric filter identifier.
1116
+ * @returns Raw JSON string of the stats file.
1117
+ */
1118
+ const downloadOptimizationStats = async (filterId) => {
1119
+ const optimizationStatsUrl = getOptimizationStatsUrl(filterId);
1120
+ return downloadFile(optimizationStatsUrl);
1121
+ };
1122
+ /**
1123
+ * When set, `getOptimizationStatistics` reads stats from local files under this
1124
+ * directory instead of fetching from the remote server.
1125
+ */
1126
+ let localStatsPath = null;
1127
+ /**
1128
+ * Cached set of filter IDs that have optimization stats available.
1129
+ * Populated lazily on the first `getOptimizableFilterIds` call.
1130
+ * Shared across concurrent callers to avoid redundant percent.json fetches.
1131
+ */
1132
+ let optimizableFilterIdsPromise = null;
1133
+ /**
1134
+ * Returns the set of optimizable filter IDs.
1135
+ *
1136
+ * Lazily initializes a shared singleton on first call so that concurrent
1137
+ * callers share one in-flight fetch of `percent.json`.
1138
+ * `percent.json` is always fetched remotely, even when `use` has
1139
+ * been called — only the per-filter stats content is read from local files.
1140
+ *
1141
+ * @returns Set of filter IDs that have optimization stats available.
1142
+ */
1143
+ const getOptimizableFilterIds = async () => {
1144
+ if (optimizableFilterIdsPromise === null) {
1145
+ optimizableFilterIdsPromise = (async () => {
1146
+ const percent = JSON.parse(await downloadOptimizationPercent());
1147
+ return new Set(percent.config.map(({ filterId: id }) => id));
1148
+ })();
1149
+ // Clear the singleton on rejection so a transient network error
1150
+ // doesn't poison the cache for all subsequent callers.
1151
+ optimizableFilterIdsPromise.catch(() => {
1152
+ optimizableFilterIdsPromise = null;
1153
+ });
1089
1154
  }
1090
-
1091
- return filtersOptimizationPercent;
1155
+ return optimizableFilterIdsPromise;
1092
1156
  };
1093
-
1094
1157
  /**
1095
- * Downloads filter optimization config for the filter
1158
+ * Validates that stats have non-empty groups.
1159
+ *
1160
+ * @param filterId - Numeric filter identifier.
1161
+ * @param stats - Parsed optimization stats object.
1162
+ * @throws {Error} if stats is not an object, or if stats.groups is missing or empty.
1096
1163
  */
1097
- const getFilterOptimizationConfig = (filterId) => {
1098
-
1099
- // config: [{filterId: 1, percent: 45}, ...]
1100
- const filterOptimizationPercent = getFiltersOptimizationPercent().config
1101
- .find((config) => config.filterId === filterId);
1102
-
1103
- let optimizationConfig = null;
1104
- if (filterOptimizationPercent) {
1105
- optimizationConfig = JSON.parse(downloadFile(OPTIMIZATION_STATS_URL.replace('{0}', filterId)));
1106
- if (!optimizationConfig || !optimizationConfig.groups || optimizationConfig.groups.length === 0) {
1107
- throw new Error(`Unable to retrieve optimization stats for ${filterId}`);
1164
+ function assertValidStats(filterId, stats) {
1165
+ if (stats === null || typeof stats !== 'object') {
1166
+ throw new Error(`Invalid optimization stats for ${filterId}: expected an object`);
1167
+ }
1168
+ if (!('groups' in stats) || !Array.isArray(stats.groups) || stats.groups.length === 0) {
1169
+ throw new Error(`Invalid optimization stats for ${filterId}: missing or empty groups`);
1170
+ }
1171
+ }
1172
+ /**
1173
+ * Manages a local on-disk cache of optimization stats files.
1174
+ *
1175
+ * Typical usage for generating the cache:
1176
+ * 1. `download(basePath, includedFilterIds, excludedFilterIds)` — save
1177
+ * `stats.json` for filters listed in the remote `percent.json`.
1178
+ *
1179
+ * Typical usage for using the cache:
1180
+ * 1. `use(basePath)` — tells `getOptimizationStatistics` to read stats
1181
+ * from local files lazily during compilation instead of fetching remotely.
1182
+ * 2. `reset(basePath)` — remove the cache directory and clear in-memory state.
1183
+ */
1184
+ const localOptimizationStatistics = {
1185
+ /**
1186
+ * Downloads `stats.json` files for filters listed in the remote
1187
+ * `percent.json` and saves them to disk. Downloads into a staged
1188
+ * directory first and swaps it in only after all downloads succeed,
1189
+ * so a failed refresh leaves any previously cached stats intact.
1190
+ *
1191
+ * When both `includedFilterIds` and `excludedFilterIds` are non-empty,
1192
+ * a filter is processed only if it is in `includedFilterIds` and not in
1193
+ * `excludedFilterIds`.
1194
+ *
1195
+ * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
1196
+ * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
1197
+ * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
1198
+ */
1199
+ download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
1200
+ const percent = JSON.parse(await downloadOptimizationPercent());
1201
+ const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
1202
+ && (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
1203
+ const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
1204
+ const STAGED_PREFIX = `${process.pid}_${Date.now()}`;
1205
+ const STAGED_FILTERS_PATH = path$1.join(basePath, `${STAGED_PREFIX}_${FILTERS_DIR_NAME}`);
1206
+ /**
1207
+ * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1208
+ * parallel requests if the underlying transport ever becomes truly async.
1209
+ */
1210
+ const DOWNLOAD_CONCURRENCY = 8;
1211
+ try {
1212
+ await fs$1.mkdir(STAGED_FILTERS_PATH, { recursive: true });
1213
+ await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1214
+ const dir = path$1.join(STAGED_FILTERS_PATH, String(filterId));
1215
+ const statsPath = path$1.join(dir, STATS_JSON);
1216
+ const content = await downloadOptimizationStats(filterId);
1217
+ await fs$1.mkdir(dir, { recursive: true });
1218
+ await fs$1.writeFile(statsPath, content, 'utf-8');
1219
+ });
1220
+ }
1221
+ catch (error) {
1222
+ // Downloads incomplete: staged dir is garbage, safe to discard.
1223
+ await fs$1.rm(STAGED_FILTERS_PATH, { recursive: true, force: true });
1224
+ throw error;
1108
1225
  }
1226
+ // Swap only after every fetch succeeded. From here on, staged dir is
1227
+ // a complete cache — never rm it on failure, that'd destroy the only
1228
+ // remaining valid copy once the old FILTERS_PATH is gone.
1229
+ await fs$1.rm(FILTERS_PATH, { recursive: true, force: true });
1230
+ await fs$1.rename(STAGED_FILTERS_PATH, FILTERS_PATH);
1231
+ },
1232
+ /**
1233
+ * Configures `getOptimizationStatistics` to read stats from local files under
1234
+ * `basePath` instead of fetching from the remote server.
1235
+ * Stats are loaded lazily on demand during compilation. `percent.json` is
1236
+ * still fetched remotely to determine which filters are optimizable.
1237
+ *
1238
+ * @param basePath - Directory containing `filters/<filterId>/stats.json`.
1239
+ */
1240
+ use(basePath) {
1241
+ localStatsPath = basePath;
1242
+ optimizableFilterIdsPromise = null;
1243
+ },
1244
+ /**
1245
+ * Removes the cache directory and clears in-memory state.
1246
+ *
1247
+ * @param basePath - Directory to remove.
1248
+ */
1249
+ async reset(basePath) {
1250
+ await fs$1.rm(basePath, { recursive: true, force: true });
1251
+ localStatsPath = null;
1252
+ optimizableFilterIdsPromise = null;
1253
+ },
1254
+ };
1255
+ /**
1256
+ * Returns the optimization stats for the given filter, or `null` when
1257
+ * optimization is disabled or the filter is not listed in `percent.json`.
1258
+ *
1259
+ * When `localOptimizationStatistics.use(path)` has been called, stats
1260
+ * are read lazily from local files. Otherwise stats are fetched from the
1261
+ * remote server.
1262
+ *
1263
+ * @param filterId - Numeric filter identifier.
1264
+ * @returns Parsed stats object, or `null` when the filter has no optimization stats.
1265
+ * @throws {Error} When the stats are missing or malformed.
1266
+ */
1267
+ const getOptimizationStatistics = async (filterId) => {
1268
+ const ids = await getOptimizableFilterIds();
1269
+ if (!ids.has(filterId)) {
1270
+ return null;
1109
1271
  }
1110
-
1111
- return optimizationConfig;
1272
+ let stats;
1273
+ try {
1274
+ const content = localStatsPath !== null
1275
+ ? await fs$1.readFile(path$1.join(localStatsPath, FILTERS_DIR_NAME, String(filterId), STATS_JSON), 'utf-8')
1276
+ : await downloadOptimizationStats(filterId);
1277
+ stats = JSON.parse(content);
1278
+ }
1279
+ catch (originalError) {
1280
+ const statsPath = localStatsPath === null
1281
+ ? getOptimizationStatsUrl(filterId)
1282
+ : `${localStatsPath}/filters/${filterId}/stats.json`;
1283
+ throw new OptimizationStatsError(filterId, statsPath, { cause: originalError });
1284
+ }
1285
+ assertValidStats(filterId, stats);
1286
+ return stats;
1112
1287
  };
1113
-
1114
1288
  /**
1115
- * Checks if rule should be skipped, because optimization is enabled for this filter
1116
- * and hits of this rule is lower than some value
1117
- * @param ruleText Rule text
1118
- * @param optimizationConfig Optimization config for this filter (retrieved with getFilterOptimizationConfig)
1289
+ * Checks if rule should be skipped because optimization is enabled for this filter
1290
+ * and the hit count for this rule is below the configured threshold.
1291
+ *
1292
+ * @param ruleText - Rule text to check.
1293
+ * @param optimizationStats - Optimization config for this filter.
1294
+ * @returns `true` if the rule should be skipped, `false` otherwise.
1119
1295
  */
1120
- const skipRuleWithOptimization = (ruleText, optimizationConfig) => {
1121
- if (!optimizationConfig) {
1296
+ const skipRuleWithOptimization = (ruleText, optimizationStats) => {
1297
+ if (!optimizationStats) {
1122
1298
  return false;
1123
1299
  }
1124
-
1125
1300
  // eslint-disable-next-line no-restricted-syntax
1126
- for (const group of optimizationConfig.groups) {
1301
+ for (const group of optimizationStats.groups) {
1127
1302
  const hits = group.rules[ruleText];
1128
1303
  if (hits !== undefined && hits < group.config.hits) {
1129
1304
  return true;
1130
1305
  }
1131
1306
  }
1132
-
1133
1307
  return false;
1134
1308
  };
1135
1309
 
@@ -1490,7 +1664,7 @@ let adguardFiltersServerUrl = null;
1490
1664
  */
1491
1665
  const readFile$2 = function (path) {
1492
1666
  try {
1493
- return fs$1.readFileSync(path, { encoding: 'utf-8' });
1667
+ return fs$2.readFileSync(path, { encoding: 'utf-8' });
1494
1668
  } catch (e) {
1495
1669
  return null;
1496
1670
  }
@@ -1603,7 +1777,7 @@ const createDir = (dir) => {
1603
1777
  return dir.split(sep).reduce((parentDir, childDir) => {
1604
1778
  const curDir = path$1.resolve(baseDir, parentDir, childDir);
1605
1779
  try {
1606
- fs$1.mkdirSync(curDir);
1780
+ fs$2.mkdirSync(curDir);
1607
1781
  } catch (err) {
1608
1782
  if (err.code === 'EEXIST') { // curDir already exists!
1609
1783
  return curDir;
@@ -1874,11 +2048,11 @@ const loadLocales = function (dir) {
1874
2048
  filters: {},
1875
2049
  };
1876
2050
 
1877
- const locales = fs$1.readdirSync(dir);
2051
+ const locales = fs$2.readdirSync(dir);
1878
2052
  // eslint-disable-next-line no-restricted-syntax
1879
2053
  for (const directory of locales) {
1880
2054
  const localeDir = path$1.join(dir, directory);
1881
- if (fs$1.lstatSync(localeDir).isDirectory()) {
2055
+ if (fs$2.lstatSync(localeDir).isDirectory()) {
1882
2056
  const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
1883
2057
  if (groups) {
1884
2058
  // eslint-disable-next-line no-restricted-syntax
@@ -2103,8 +2277,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
2103
2277
 
2104
2278
  const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
2105
2279
 
2106
- fs$1.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2107
- fs$1.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2280
+ fs$2.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2281
+ fs$2.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2108
2282
 
2109
2283
  logger.info(`Writing filters localizations: ${config.path}`);
2110
2284
  const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
@@ -2145,8 +2319,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
2145
2319
 
2146
2320
  const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
2147
2321
 
2148
- fs$1.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2149
- fs$1.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2322
+ fs$2.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2323
+ fs$2.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2150
2324
  }
2151
2325
 
2152
2326
  logger.info('Writing filters metadata done');
@@ -2206,12 +2380,12 @@ const writeLocalScriptRules = function (platformsPath) {
2206
2380
  // remove scriptlet rules in local_script_rules.json
2207
2381
  rulesJson.rules = removeScriptletRules(rulesJson.rules);
2208
2382
 
2209
- fs$1.writeFileSync(
2383
+ fs$2.writeFileSync(
2210
2384
  path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
2211
2385
  rulesTxt.join(RULES_SEPARATOR),
2212
2386
  'utf8',
2213
2387
  );
2214
- fs$1.writeFileSync(
2388
+ fs$2.writeFileSync(
2215
2389
  path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
2216
2390
  JSON.stringify(rulesJson, null, 4),
2217
2391
  'utf8',
@@ -2225,12 +2399,12 @@ const writeLocalScriptRules = function (platformsPath) {
2225
2399
  * Loads and processes filter metadata from the specified directory.
2226
2400
  *
2227
2401
  * @param {string} filterDir - The directory containing the filter metadata and revision files.
2228
- * @param {number[]} [whitelist] - An optional array of whitelist filter IDs.
2229
- * @param {number[]} [blacklist] - An optional array of blacklist filter IDs.
2402
+ * @param {number[]} [includedFilterIds] - An optional array of filter IDs to include.
2403
+ * @param {number[]} [excludedFilterIds] - An optional array of filter IDs to exclude.
2230
2404
  * @returns {Object} The processed filter metadata.
2231
2405
  * @throws {Error} If the metadata or revision file cannot be read.
2232
2406
  */
2233
- const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2407
+ const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilterIds) {
2234
2408
  const metadataFilePath = path$1.join(filterDir, metadataFile);
2235
2409
  const metadataString = readFile$2(metadataFilePath);
2236
2410
  if (!metadataString) {
@@ -2253,8 +2427,8 @@ const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2253
2427
 
2254
2428
  const { filterId } = result;
2255
2429
  if (
2256
- (whitelist && whitelist.includes(filterId))
2257
- || (blacklist && !blacklist.includes(filterId))
2430
+ (includedFilterIds && includedFilterIds.includes(filterId))
2431
+ || (excludedFilterIds && !excludedFilterIds.includes(filterId))
2258
2432
  ) {
2259
2433
  checkFilterId(metadataFilterIdsPool, filterId);
2260
2434
  }
@@ -2298,7 +2472,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
2298
2472
  data = [adbHeader].concat(data);
2299
2473
  }
2300
2474
 
2301
- fs$1.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2475
+ fs$2.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2302
2476
  };
2303
2477
 
2304
2478
  /**
@@ -2378,10 +2552,10 @@ const removeRuleDuplicates = function (list) {
2378
2552
  *
2379
2553
  * @param filterDir - Path to filter directory
2380
2554
  * @param platformsPath - Path to platforms folder
2381
- * @param whitelist - Array of filter ids to whitelist
2382
- * @param blacklist - Array of filter ids to blacklist
2555
+ * @param includedFilterIds - Array of filter ids to include
2556
+ * @param excludedFilterIds - Array of filter ids to exclude
2383
2557
  */
2384
- const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) => {
2558
+ const buildFilter$1 = async (filterDir, platformsPath, includedFilterIds, excludedFilterIds) => {
2385
2559
  const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
2386
2560
 
2387
2561
  const metadataFilePath = path$1.join(filterDir, metadataFile);
@@ -2391,17 +2565,17 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
2391
2565
  const { filterId } = metadata;
2392
2566
  checkFilterId(filterIdsPool, filterId);
2393
2567
 
2394
- if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
2395
- logger.info(`Filter ${filterId} skipped with whitelist`);
2568
+ if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
2569
+ logger.info(`Filter ${filterId} skipped due to '--include' option`);
2396
2570
  return;
2397
2571
  }
2398
2572
 
2399
- if (blacklist && blacklist.length > 0 && blacklist.indexOf(filterId) >= 0) {
2400
- logger.info(`Filter ${filterId} skipped with blacklist`);
2573
+ if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
2574
+ logger.info(`Filter ${filterId} skipped due to '--skip' option`);
2401
2575
  return;
2402
2576
  }
2403
2577
 
2404
- const optimizationConfig = getFilterOptimizationConfig(filterId);
2578
+ const optimizationConfig = await getOptimizationStatistics(filterId);
2405
2579
 
2406
2580
  // eslint-disable-next-line guard-for-in,no-restricted-syntax
2407
2581
  for (const platform in platformPathsConfig) {
@@ -2495,30 +2669,30 @@ const isObsoleteFilter = (metadata) => metadata.tags && metadata.tags.some((tag)
2495
2669
  * @param filtersDir
2496
2670
  * @param filtersMetadata
2497
2671
  * @param platformsPath
2498
- * @param whitelist
2499
- * @param blacklist
2672
+ * @param includedFilterIds
2673
+ * @param excludedFilterIds
2500
2674
  * @param obsoleteFiltersMetadata
2501
2675
  */
2502
2676
  const parseDirectory$1 = async (
2503
2677
  filtersDir,
2504
2678
  filtersMetadata,
2505
2679
  platformsPath,
2506
- whitelist,
2507
- blacklist,
2680
+ includedFilterIds,
2681
+ excludedFilterIds,
2508
2682
  obsoleteFiltersMetadata,
2509
2683
  ) => {
2510
- const items = fs$1.readdirSync(filtersDir);
2684
+ const items = fs$2.readdirSync(filtersDir);
2511
2685
  // eslint-disable-next-line no-restricted-syntax
2512
2686
  for (const directory of items) {
2513
2687
  const filterDir = path$1.join(filtersDir, directory);
2514
- if (fs$1.lstatSync(filterDir).isDirectory()) {
2688
+ if (fs$2.lstatSync(filterDir).isDirectory()) {
2515
2689
  const metadataFilePath = path$1.join(filterDir, metadataFile);
2516
- if (fs$1.existsSync(metadataFilePath)) {
2690
+ if (fs$2.existsSync(metadataFilePath)) {
2517
2691
  logger.info(`Building filter platforms: ${directory}`);
2518
2692
  // eslint-disable-next-line no-await-in-loop
2519
- await buildFilter$1(filterDir, platformsPath, whitelist, blacklist);
2693
+ await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
2520
2694
  logger.info(`Building filter platforms: ${directory} done`);
2521
- const filterMetadata = loadFilterMetadata(filterDir, whitelist, blacklist);
2695
+ const filterMetadata = loadFilterMetadata(filterDir, includedFilterIds, excludedFilterIds);
2522
2696
  filtersMetadata.push(filterMetadata);
2523
2697
  if (isObsoleteFilter(filterMetadata)) {
2524
2698
  obsoleteFiltersMetadata.push(filterMetadata);
@@ -2529,8 +2703,8 @@ const parseDirectory$1 = async (
2529
2703
  filterDir,
2530
2704
  filtersMetadata,
2531
2705
  platformsPath,
2532
- whitelist,
2533
- blacklist,
2706
+ includedFilterIds,
2707
+ excludedFilterIds,
2534
2708
  obsoleteFiltersMetadata,
2535
2709
  );
2536
2710
  }
@@ -2543,14 +2717,12 @@ const parseDirectory$1 = async (
2543
2717
  *
2544
2718
  * @async
2545
2719
  * @param {string} filtersDir - The directory containing filter files to be processed.
2546
- * @param {string} platformsPath - The output path where the generated platform files will be stored.
2547
- * @param {Array<number>} whitelist - A list of whitelist filter IDs.
2548
- * @param {Array<number>} blacklist - A list of blacklist filter IDs.
2720
+ * @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
2721
+ * @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include.
2722
+ * @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude.
2549
2723
  * @returns {Promise<void>} Resolves when the generation process is complete.
2550
- *
2551
- * @throws {Error} If `platformsPath` or `platformPathsConfig` is not specified.
2552
2724
  */
2553
- const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
2725
+ const generate = async (filtersDir, platformsPath, includedFilterIds, excludedFilterIds) => {
2554
2726
  if (!platformsPath) {
2555
2727
  logger.warn('Platforms build output path is not specified');
2556
2728
  return;
@@ -2566,7 +2738,14 @@ const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
2566
2738
  const filtersMetadata = [];
2567
2739
  const obsoleteFiltersMetadata = [];
2568
2740
 
2569
- await parseDirectory$1(filtersDir, filtersMetadata, platformsPath, whitelist, blacklist, obsoleteFiltersMetadata);
2741
+ await parseDirectory$1(
2742
+ filtersDir,
2743
+ filtersMetadata,
2744
+ platformsPath,
2745
+ includedFilterIds,
2746
+ excludedFilterIds,
2747
+ obsoleteFiltersMetadata,
2748
+ );
2570
2749
 
2571
2750
  writeFiltersMetadata(platformsPath, filtersDir, filtersMetadata, obsoleteFiltersMetadata);
2572
2751
  writeLocalScriptRules(platformsPath);
@@ -2632,7 +2811,7 @@ const skipFilter = (metadata) => {
2632
2811
  */
2633
2812
  const create = (reportPath) => {
2634
2813
  if (reportPath) {
2635
- fs$1.writeFileSync(reportPath, reportData, 'utf8');
2814
+ fs$2.writeFileSync(reportPath, reportData, 'utf8');
2636
2815
  return;
2637
2816
  }
2638
2817
  log(reportData);
@@ -2835,11 +3014,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
2835
3014
  * @returns {*}
2836
3015
  */
2837
3016
  const readFile$1 = function (path) {
2838
- if (!fs$1.existsSync(path)) {
3017
+ if (!fs$2.existsSync(path)) {
2839
3018
  return null;
2840
3019
  }
2841
3020
 
2842
- return fs$1.readFileSync(path, { encoding: 'utf-8' });
3021
+ return fs$2.readFileSync(path, { encoding: 'utf-8' });
2843
3022
  };
2844
3023
 
2845
3024
  /**
@@ -2849,7 +3028,7 @@ const readFile$1 = function (path) {
2849
3028
  * @param data
2850
3029
  */
2851
3030
  const writeFile = function (path, data) {
2852
- fs$1.writeFileSync(path, data, 'utf8');
3031
+ fs$2.writeFileSync(path, data, 'utf8');
2853
3032
  };
2854
3033
 
2855
3034
  /**
@@ -3146,7 +3325,7 @@ const include = async (filterDir, directiveLine, excluded) => {
3146
3325
  const externalInclude = url.includes(':');
3147
3326
 
3148
3327
  const included = externalInclude
3149
- ? downloadFile(url)
3328
+ ? await downloadFile(url)
3150
3329
  : readFile$1(path$1.join(filterDir, url));
3151
3330
 
3152
3331
  if (included) {
@@ -3413,11 +3592,11 @@ const makeRevision = function (path, hash) {
3413
3592
  * Builds filter txt file from directory contents
3414
3593
  *
3415
3594
  * @param {string} filterDir - The path to the directory containing filters.
3416
- * @param {Array<number>} whitelist - An array whitelist filters IDs.
3417
- * @param {Array<number>} blacklist - An array blacklist filters IDs.
3595
+ * @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
3596
+ * @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
3418
3597
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3419
3598
  */
3420
- const buildFilter = async function (filterDir, whitelist, blacklist) {
3599
+ const buildFilter = async function (filterDir, includedFilterIds, excludedFilterIds) {
3421
3600
  const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
3422
3601
  if (!templateContent) {
3423
3602
  throw new Error('Invalid template');
@@ -3434,12 +3613,12 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3434
3613
  return;
3435
3614
  }
3436
3615
 
3437
- if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
3616
+ if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
3438
3617
  logger.info(`Filter ${filterId} skipped due to '--include' option`);
3439
3618
  return;
3440
3619
  }
3441
3620
 
3442
- if (blacklist && blacklist.length > 0 && blacklist.indexOf(filterId) >= 0) {
3621
+ if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
3443
3622
  logger.info(`Filter ${filterId} skipped due to '--skip' option`);
3444
3623
  return;
3445
3624
  }
@@ -3479,30 +3658,30 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3479
3658
  };
3480
3659
 
3481
3660
  /**
3482
- * Asynchronously parses a directory and processes filters based on the provided whitelist and blacklist.
3661
+ * Asynchronously parses a directory and processes filters based on the provided included/excluded filter IDs.
3483
3662
  *
3484
3663
  * @param {string} filtersDir - The path to the directory containing filters.
3485
- * @param {Array<number>} whitelist - An array whitelist filters IDs.
3486
- * @param {Array<number>} blacklist - An array blacklist filters IDs.
3664
+ * @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
3665
+ * @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
3487
3666
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3488
3667
  */
3489
- const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3490
- const items = fs$1.readdirSync(filtersDir)
3668
+ const parseDirectory = async function (filtersDir, includedFilterIds, excludedFilterIds) {
3669
+ const items = fs$2.readdirSync(filtersDir)
3491
3670
  .sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
3492
3671
 
3493
3672
  // eslint-disable-next-line no-restricted-syntax
3494
3673
  for (const directory of items) {
3495
3674
  const filterDir = path$1.join(filtersDir, directory);
3496
- if (fs$1.lstatSync(filterDir).isDirectory()) {
3675
+ if (fs$2.lstatSync(filterDir).isDirectory()) {
3497
3676
  const template = path$1.join(filterDir, TEMPLATE_FILE);
3498
- if (fs$1.existsSync(template)) {
3677
+ if (fs$2.existsSync(template)) {
3499
3678
  logger.info(`Building filter ${directory}...`);
3500
3679
  // eslint-disable-next-line no-await-in-loop
3501
- await buildFilter(filterDir, whitelist, blacklist);
3680
+ await buildFilter(filterDir, includedFilterIds, excludedFilterIds);
3502
3681
  logger.info(`Filter ${directory} ok`);
3503
3682
  } else {
3504
3683
  // eslint-disable-next-line no-await-in-loop
3505
- await parseDirectory(filterDir, whitelist, blacklist);
3684
+ await parseDirectory(filterDir, includedFilterIds, excludedFilterIds);
3506
3685
  }
3507
3686
  }
3508
3687
  }
@@ -3516,10 +3695,10 @@ const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3516
3695
  * @param {string} filtersDir - The directory containing filter files to be processed.
3517
3696
  * @param {string} logFile - The path to the log file where logs will be written.
3518
3697
  * @param {string} reportFile - The path to the report file to be created.
3519
- * @param {string} platformsPath - The path where platform data will be generated.
3698
+ * @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
3520
3699
  * @param {Object} platformsConfig - The configuration object for platforms.
3521
- * @param {Array<number>} whitelist - A list of filter file names to include in processing.
3522
- * @param {Array<number>} blacklist - A list of filter file names to exclude from processing.
3700
+ * @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include in processing.
3701
+ * @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude from processing.
3523
3702
  * @returns {Promise<void>} A promise that resolves when the build process is complete.
3524
3703
  */
3525
3704
  const build = async (
@@ -3528,16 +3707,16 @@ const build = async (
3528
3707
  reportFile,
3529
3708
  platformsPath,
3530
3709
  platformsConfig,
3531
- whitelist,
3532
- blacklist,
3710
+ includedFilterIds,
3711
+ excludedFilterIds,
3533
3712
  ) => {
3534
3713
  logger.initialize(logFile);
3535
3714
  init(FILTER_FILE, METADATA_FILE, REVISION_FILE, platformsConfig, ADGUARD_FILTERS_SERVER_URL);
3536
3715
 
3537
- await parseDirectory(filtersDir, whitelist, blacklist);
3716
+ await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
3538
3717
 
3539
3718
  logger.info('Generating platforms');
3540
- await generate(filtersDir, platformsPath, whitelist, blacklist);
3719
+ await generate(filtersDir, platformsPath, includedFilterIds, excludedFilterIds);
3541
3720
  logger.info('Generating platforms done');
3542
3721
  create(reportFile);
3543
3722
  };
@@ -3558,14 +3737,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
3558
3737
  const loadSchemas = (dir) => {
3559
3738
  const schemas = {};
3560
3739
 
3561
- const items = fs$1.readdirSync(dir);
3740
+ const items = fs$2.readdirSync(dir);
3562
3741
  // eslint-disable-next-line no-restricted-syntax
3563
3742
  for (const f of items) {
3564
3743
  if (f.endsWith(SCHEMA_EXTENSION)) {
3565
3744
  const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
3566
3745
 
3567
3746
  logger.info(`Loading schema for ${validationFileName}`);
3568
- schemas[validationFileName] = JSON.parse(fs$1.readFileSync(path$1.join(dir, f)));
3747
+ schemas[validationFileName] = JSON.parse(fs$2.readFileSync(path$1.join(dir, f)));
3569
3748
  }
3570
3749
  }
3571
3750
 
@@ -3585,7 +3764,7 @@ const loadSchemas = (dir) => {
3585
3764
  const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
3586
3765
  let items;
3587
3766
  try {
3588
- items = fs$1.readdirSync(dir);
3767
+ items = fs$2.readdirSync(dir);
3589
3768
  } catch (e) {
3590
3769
  logger.info(e.message);
3591
3770
  return false;
@@ -3593,7 +3772,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3593
3772
  // eslint-disable-next-line no-restricted-syntax
3594
3773
  for (const f of items) {
3595
3774
  const item = path$1.join(dir, f);
3596
- if (fs$1.lstatSync(item).isDirectory()) {
3775
+ if (fs$2.lstatSync(item).isDirectory()) {
3597
3776
  if (!validateDir(item, validator, schemas, oldSchemas)) {
3598
3777
  return false;
3599
3778
  }
@@ -3616,7 +3795,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3616
3795
  if (schema) {
3617
3796
  logger.info(`Validating ${item}`);
3618
3797
 
3619
- const json = JSON.parse(fs$1.readFileSync(item));
3798
+ const json = JSON.parse(fs$2.readFileSync(item));
3620
3799
 
3621
3800
  // Validate filters amount
3622
3801
  if (fileName === 'filters') {
@@ -3630,11 +3809,11 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3630
3809
  const valid = validate(json);
3631
3810
 
3632
3811
  // json can be updated with default values
3633
- fs$1.writeFileSync(item, JSON.stringify(json, null, '\t'));
3812
+ fs$2.writeFileSync(item, JSON.stringify(json, null, '\t'));
3634
3813
 
3635
3814
  // duplicate to .js file as well
3636
3815
  const jsFileName = `${fileName}.js`;
3637
- fs$1.writeFileSync(
3816
+ fs$2.writeFileSync(
3638
3817
  path$1.join(path$1.dirname(item), jsFileName),
3639
3818
  JSON.stringify(json, null, '\t'),
3640
3819
  );
@@ -3725,13 +3904,13 @@ const WARNING_TYPES = {
3725
3904
  * Sync reads file content
3726
3905
  * @param filePath - path to locales file
3727
3906
  */
3728
- const readFile = (filePath) => fs$1.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
3907
+ const readFile = (filePath) => fs$2.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
3729
3908
 
3730
3909
  /**
3731
3910
  * Sync reads directory content
3732
3911
  * @param dirPath - path to directory
3733
3912
  */
3734
- const readDir = (dirPath) => fs$1.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
3913
+ const readDir = (dirPath) => fs$2.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
3735
3914
 
3736
3915
  /**
3737
3916
  * Validates messages keys
@@ -4829,7 +5008,15 @@ process.on('unhandledRejection', (error) => {
4829
5008
  throw error;
4830
5009
  });
4831
5010
 
4832
- const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist, customPlatformsConfig) => {
5011
+ const compile = (
5012
+ path,
5013
+ logPath,
5014
+ reportFile,
5015
+ platformsPath,
5016
+ includedFilterIds,
5017
+ excludedFilterIds,
5018
+ customPlatformsConfig,
5019
+ ) => {
4833
5020
  if (customPlatformsConfig) {
4834
5021
  logger.info('Using custom platforms configuration');
4835
5022
  // eslint-disable-next-line no-restricted-syntax, guard-for-in
@@ -4845,8 +5032,8 @@ const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist,
4845
5032
  reportFile,
4846
5033
  platformsPath,
4847
5034
  platformsConfig,
4848
- whitelist,
4849
- blacklist,
5035
+ includedFilterIds,
5036
+ excludedFilterIds,
4850
5037
  );
4851
5038
  };
4852
5039
 
@@ -4858,4 +5045,4 @@ const validateLocales = (localesDirPath, requiredLocales) => {
4858
5045
  return localesValidator.validate(localesDirPath, requiredLocales);
4859
5046
  };
4860
5047
 
4861
- export { compile, validateJSONSchema, validateLocales };
5048
+ export { OptimizationStatsError, compile, localOptimizationStatistics, validateJSONSchema, validateLocales };