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