@adguard/filters-compiler 3.2.11 → 3.2.12

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,279 @@ 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.
1191
+ * Existing `stats.json` files will be overwritten.
1192
+ *
1193
+ * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
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
+ * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
1199
+ */
1200
+ download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
1201
+ if (includedFilterIds.length > 0 && excludedFilterIds.length > 0) {
1202
+ throw new Error('includedFilterIds and excludedFilterIds cannot both be non-empty');
1111
1203
  }
1204
+ const percent = JSON.parse(await downloadOptimizationPercent());
1205
+ const configs = percent.config.filter(({ filterId }) => {
1206
+ if (includedFilterIds.length > 0) {
1207
+ return includedFilterIds.includes(filterId);
1208
+ }
1209
+ if (excludedFilterIds.length > 0) {
1210
+ return !excludedFilterIds.includes(filterId);
1211
+ }
1212
+ return true;
1213
+ });
1214
+ const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
1215
+ /**
1216
+ * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1217
+ * parallel requests if the underlying transport ever becomes truly async.
1218
+ */
1219
+ const DOWNLOAD_CONCURRENCY = 8;
1220
+ await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1221
+ const dir = path$1.join(FILTERS_PATH, String(filterId));
1222
+ const statsPath = path$1.join(dir, STATS_JSON);
1223
+ const content = await downloadOptimizationStats(filterId);
1224
+ await fs$1.mkdir(dir, { recursive: true });
1225
+ await fs$1.writeFile(statsPath, content, 'utf-8');
1226
+ });
1227
+ },
1228
+ /**
1229
+ * Configures `getOptimizationStatistics` to read stats from local files under
1230
+ * `basePath` instead of fetching from the remote server.
1231
+ * Stats are loaded lazily on demand during compilation. `percent.json` is
1232
+ * still fetched remotely to determine which filters are optimizable.
1233
+ *
1234
+ * @param basePath - Directory containing `filters/<filterId>/stats.json`.
1235
+ */
1236
+ use(basePath) {
1237
+ localStatsPath = basePath;
1238
+ optimizableFilterIdsPromise = null;
1239
+ },
1240
+ /**
1241
+ * Removes the cache directory and clears in-memory state.
1242
+ *
1243
+ * @param basePath - Directory to remove.
1244
+ */
1245
+ async reset(basePath) {
1246
+ await fs$1.rm(basePath, { recursive: true, force: true });
1247
+ localStatsPath = null;
1248
+ optimizableFilterIdsPromise = null;
1249
+ },
1250
+ };
1251
+ /**
1252
+ * Returns the optimization stats for the given filter, or `null` when
1253
+ * optimization is disabled or the filter is not listed in `percent.json`.
1254
+ *
1255
+ * When `localOptimizationStatistics.use(path)` has been called, stats
1256
+ * are read lazily from local files. Otherwise stats are fetched from the
1257
+ * remote server.
1258
+ *
1259
+ * @param filterId - Numeric filter identifier.
1260
+ * @returns Parsed stats object, or `null` when the filter has no optimization stats.
1261
+ * @throws {Error} When the stats are missing or malformed.
1262
+ */
1263
+ const getOptimizationStatistics = async (filterId) => {
1264
+ const ids = await getOptimizableFilterIds();
1265
+ if (!ids.has(filterId)) {
1266
+ return null;
1112
1267
  }
1113
-
1114
- return optimizationConfig;
1268
+ let stats;
1269
+ try {
1270
+ const content = localStatsPath !== null
1271
+ ? await fs$1.readFile(path$1.join(localStatsPath, FILTERS_DIR_NAME, String(filterId), STATS_JSON), 'utf-8')
1272
+ : await downloadOptimizationStats(filterId);
1273
+ stats = JSON.parse(content);
1274
+ }
1275
+ catch (originalError) {
1276
+ const statsPath = localStatsPath === null
1277
+ ? getOptimizationStatsUrl(filterId)
1278
+ : `${localStatsPath}/filters/${filterId}/stats.json`;
1279
+ throw new OptimizationStatsError(filterId, statsPath, { cause: originalError });
1280
+ }
1281
+ assertValidStats(filterId, stats);
1282
+ return stats;
1115
1283
  };
1116
-
1117
1284
  /**
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)
1285
+ * Checks if rule should be skipped because optimization is enabled for this filter
1286
+ * and the hit count for this rule is below the configured threshold.
1287
+ *
1288
+ * @param ruleText - Rule text to check.
1289
+ * @param optimizationStats - Optimization config for this filter.
1290
+ * @returns `true` if the rule should be skipped, `false` otherwise.
1122
1291
  */
1123
- const skipRuleWithOptimization = (ruleText, optimizationConfig) => {
1124
- if (!optimizationConfig) {
1292
+ const skipRuleWithOptimization = (ruleText, optimizationStats) => {
1293
+ if (!optimizationStats) {
1125
1294
  return false;
1126
1295
  }
1127
-
1128
1296
  // eslint-disable-next-line no-restricted-syntax
1129
- for (const group of optimizationConfig.groups) {
1297
+ for (const group of optimizationStats.groups) {
1130
1298
  const hits = group.rules[ruleText];
1131
1299
  if (hits !== undefined && hits < group.config.hits) {
1132
1300
  return true;
1133
1301
  }
1134
1302
  }
1135
-
1136
1303
  return false;
1137
1304
  };
1138
1305
 
@@ -1493,7 +1660,7 @@ let adguardFiltersServerUrl = null;
1493
1660
  */
1494
1661
  const readFile$2 = function (path) {
1495
1662
  try {
1496
- return fs$1.readFileSync(path, { encoding: 'utf-8' });
1663
+ return fs$2.readFileSync(path, { encoding: 'utf-8' });
1497
1664
  } catch (e) {
1498
1665
  return null;
1499
1666
  }
@@ -1606,7 +1773,7 @@ const createDir = (dir) => {
1606
1773
  return dir.split(sep).reduce((parentDir, childDir) => {
1607
1774
  const curDir = path$1.resolve(baseDir, parentDir, childDir);
1608
1775
  try {
1609
- fs$1.mkdirSync(curDir);
1776
+ fs$2.mkdirSync(curDir);
1610
1777
  } catch (err) {
1611
1778
  if (err.code === 'EEXIST') { // curDir already exists!
1612
1779
  return curDir;
@@ -1877,11 +2044,11 @@ const loadLocales = function (dir) {
1877
2044
  filters: {},
1878
2045
  };
1879
2046
 
1880
- const locales = fs$1.readdirSync(dir);
2047
+ const locales = fs$2.readdirSync(dir);
1881
2048
  // eslint-disable-next-line no-restricted-syntax
1882
2049
  for (const directory of locales) {
1883
2050
  const localeDir = path$1.join(dir, directory);
1884
- if (fs$1.lstatSync(localeDir).isDirectory()) {
2051
+ if (fs$2.lstatSync(localeDir).isDirectory()) {
1885
2052
  const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
1886
2053
  if (groups) {
1887
2054
  // eslint-disable-next-line no-restricted-syntax
@@ -2106,8 +2273,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
2106
2273
 
2107
2274
  const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
2108
2275
 
2109
- fs$1.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2110
- fs$1.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2276
+ fs$2.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2277
+ fs$2.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2111
2278
 
2112
2279
  logger.info(`Writing filters localizations: ${config.path}`);
2113
2280
  const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
@@ -2148,8 +2315,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
2148
2315
 
2149
2316
  const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
2150
2317
 
2151
- fs$1.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2152
- fs$1.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2318
+ fs$2.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2319
+ fs$2.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2153
2320
  }
2154
2321
 
2155
2322
  logger.info('Writing filters metadata done');
@@ -2209,12 +2376,12 @@ const writeLocalScriptRules = function (platformsPath) {
2209
2376
  // remove scriptlet rules in local_script_rules.json
2210
2377
  rulesJson.rules = removeScriptletRules(rulesJson.rules);
2211
2378
 
2212
- fs$1.writeFileSync(
2379
+ fs$2.writeFileSync(
2213
2380
  path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
2214
2381
  rulesTxt.join(RULES_SEPARATOR),
2215
2382
  'utf8',
2216
2383
  );
2217
- fs$1.writeFileSync(
2384
+ fs$2.writeFileSync(
2218
2385
  path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
2219
2386
  JSON.stringify(rulesJson, null, 4),
2220
2387
  'utf8',
@@ -2228,12 +2395,12 @@ const writeLocalScriptRules = function (platformsPath) {
2228
2395
  * Loads and processes filter metadata from the specified directory.
2229
2396
  *
2230
2397
  * @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.
2398
+ * @param {number[]} [includedFilterIds] - An optional array of filter IDs to include.
2399
+ * @param {number[]} [excludedFilterIds] - An optional array of filter IDs to exclude.
2233
2400
  * @returns {Object} The processed filter metadata.
2234
2401
  * @throws {Error} If the metadata or revision file cannot be read.
2235
2402
  */
2236
- const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2403
+ const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilterIds) {
2237
2404
  const metadataFilePath = path$1.join(filterDir, metadataFile);
2238
2405
  const metadataString = readFile$2(metadataFilePath);
2239
2406
  if (!metadataString) {
@@ -2256,8 +2423,8 @@ const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2256
2423
 
2257
2424
  const { filterId } = result;
2258
2425
  if (
2259
- (whitelist && whitelist.includes(filterId))
2260
- || (blacklist && !blacklist.includes(filterId))
2426
+ (includedFilterIds && includedFilterIds.includes(filterId))
2427
+ || (excludedFilterIds && !excludedFilterIds.includes(filterId))
2261
2428
  ) {
2262
2429
  checkFilterId(metadataFilterIdsPool, filterId);
2263
2430
  }
@@ -2301,7 +2468,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
2301
2468
  data = [adbHeader].concat(data);
2302
2469
  }
2303
2470
 
2304
- fs$1.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2471
+ fs$2.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2305
2472
  };
2306
2473
 
2307
2474
  /**
@@ -2381,10 +2548,10 @@ const removeRuleDuplicates = function (list) {
2381
2548
  *
2382
2549
  * @param filterDir - Path to filter directory
2383
2550
  * @param platformsPath - Path to platforms folder
2384
- * @param whitelist - Array of filter ids to whitelist
2385
- * @param blacklist - Array of filter ids to blacklist
2551
+ * @param includedFilterIds - Array of filter ids to include
2552
+ * @param excludedFilterIds - Array of filter ids to exclude
2386
2553
  */
2387
- const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) => {
2554
+ const buildFilter$1 = async (filterDir, platformsPath, includedFilterIds, excludedFilterIds) => {
2388
2555
  const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
2389
2556
 
2390
2557
  const metadataFilePath = path$1.join(filterDir, metadataFile);
@@ -2394,17 +2561,17 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
2394
2561
  const { filterId } = metadata;
2395
2562
  checkFilterId(filterIdsPool, filterId);
2396
2563
 
2397
- if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
2398
- logger.info(`Filter ${filterId} skipped with whitelist`);
2564
+ if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
2565
+ logger.info(`Filter ${filterId} skipped due to '--include' option`);
2399
2566
  return;
2400
2567
  }
2401
2568
 
2402
- if (blacklist && blacklist.length > 0 && blacklist.indexOf(filterId) >= 0) {
2403
- logger.info(`Filter ${filterId} skipped with blacklist`);
2569
+ if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
2570
+ logger.info(`Filter ${filterId} skipped due to '--skip' option`);
2404
2571
  return;
2405
2572
  }
2406
2573
 
2407
- const optimizationConfig = getFilterOptimizationConfig(filterId);
2574
+ const optimizationConfig = await getOptimizationStatistics(filterId);
2408
2575
 
2409
2576
  // eslint-disable-next-line guard-for-in,no-restricted-syntax
2410
2577
  for (const platform in platformPathsConfig) {
@@ -2498,30 +2665,30 @@ const isObsoleteFilter = (metadata) => metadata.tags && metadata.tags.some((tag)
2498
2665
  * @param filtersDir
2499
2666
  * @param filtersMetadata
2500
2667
  * @param platformsPath
2501
- * @param whitelist
2502
- * @param blacklist
2668
+ * @param includedFilterIds
2669
+ * @param excludedFilterIds
2503
2670
  * @param obsoleteFiltersMetadata
2504
2671
  */
2505
2672
  const parseDirectory$1 = async (
2506
2673
  filtersDir,
2507
2674
  filtersMetadata,
2508
2675
  platformsPath,
2509
- whitelist,
2510
- blacklist,
2676
+ includedFilterIds,
2677
+ excludedFilterIds,
2511
2678
  obsoleteFiltersMetadata,
2512
2679
  ) => {
2513
- const items = fs$1.readdirSync(filtersDir);
2680
+ const items = fs$2.readdirSync(filtersDir);
2514
2681
  // eslint-disable-next-line no-restricted-syntax
2515
2682
  for (const directory of items) {
2516
2683
  const filterDir = path$1.join(filtersDir, directory);
2517
- if (fs$1.lstatSync(filterDir).isDirectory()) {
2684
+ if (fs$2.lstatSync(filterDir).isDirectory()) {
2518
2685
  const metadataFilePath = path$1.join(filterDir, metadataFile);
2519
- if (fs$1.existsSync(metadataFilePath)) {
2686
+ if (fs$2.existsSync(metadataFilePath)) {
2520
2687
  logger.info(`Building filter platforms: ${directory}`);
2521
2688
  // eslint-disable-next-line no-await-in-loop
2522
- await buildFilter$1(filterDir, platformsPath, whitelist, blacklist);
2689
+ await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
2523
2690
  logger.info(`Building filter platforms: ${directory} done`);
2524
- const filterMetadata = loadFilterMetadata(filterDir, whitelist, blacklist);
2691
+ const filterMetadata = loadFilterMetadata(filterDir, includedFilterIds, excludedFilterIds);
2525
2692
  filtersMetadata.push(filterMetadata);
2526
2693
  if (isObsoleteFilter(filterMetadata)) {
2527
2694
  obsoleteFiltersMetadata.push(filterMetadata);
@@ -2532,8 +2699,8 @@ const parseDirectory$1 = async (
2532
2699
  filterDir,
2533
2700
  filtersMetadata,
2534
2701
  platformsPath,
2535
- whitelist,
2536
- blacklist,
2702
+ includedFilterIds,
2703
+ excludedFilterIds,
2537
2704
  obsoleteFiltersMetadata,
2538
2705
  );
2539
2706
  }
@@ -2546,14 +2713,12 @@ const parseDirectory$1 = async (
2546
2713
  *
2547
2714
  * @async
2548
2715
  * @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.
2716
+ * @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
2717
+ * @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include.
2718
+ * @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude.
2552
2719
  * @returns {Promise<void>} Resolves when the generation process is complete.
2553
- *
2554
- * @throws {Error} If `platformsPath` or `platformPathsConfig` is not specified.
2555
2720
  */
2556
- const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
2721
+ const generate = async (filtersDir, platformsPath, includedFilterIds, excludedFilterIds) => {
2557
2722
  if (!platformsPath) {
2558
2723
  logger.warn('Platforms build output path is not specified');
2559
2724
  return;
@@ -2569,7 +2734,14 @@ const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
2569
2734
  const filtersMetadata = [];
2570
2735
  const obsoleteFiltersMetadata = [];
2571
2736
 
2572
- await parseDirectory$1(filtersDir, filtersMetadata, platformsPath, whitelist, blacklist, obsoleteFiltersMetadata);
2737
+ await parseDirectory$1(
2738
+ filtersDir,
2739
+ filtersMetadata,
2740
+ platformsPath,
2741
+ includedFilterIds,
2742
+ excludedFilterIds,
2743
+ obsoleteFiltersMetadata,
2744
+ );
2573
2745
 
2574
2746
  writeFiltersMetadata(platformsPath, filtersDir, filtersMetadata, obsoleteFiltersMetadata);
2575
2747
  writeLocalScriptRules(platformsPath);
@@ -2635,7 +2807,7 @@ const skipFilter = (metadata) => {
2635
2807
  */
2636
2808
  const create = (reportPath) => {
2637
2809
  if (reportPath) {
2638
- fs$1.writeFileSync(reportPath, reportData, 'utf8');
2810
+ fs$2.writeFileSync(reportPath, reportData, 'utf8');
2639
2811
  return;
2640
2812
  }
2641
2813
  log(reportData);
@@ -2838,11 +3010,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
2838
3010
  * @returns {*}
2839
3011
  */
2840
3012
  const readFile$1 = function (path) {
2841
- if (!fs$1.existsSync(path)) {
3013
+ if (!fs$2.existsSync(path)) {
2842
3014
  return null;
2843
3015
  }
2844
3016
 
2845
- return fs$1.readFileSync(path, { encoding: 'utf-8' });
3017
+ return fs$2.readFileSync(path, { encoding: 'utf-8' });
2846
3018
  };
2847
3019
 
2848
3020
  /**
@@ -2852,7 +3024,7 @@ const readFile$1 = function (path) {
2852
3024
  * @param data
2853
3025
  */
2854
3026
  const writeFile = function (path, data) {
2855
- fs$1.writeFileSync(path, data, 'utf8');
3027
+ fs$2.writeFileSync(path, data, 'utf8');
2856
3028
  };
2857
3029
 
2858
3030
  /**
@@ -3149,7 +3321,7 @@ const include = async (filterDir, directiveLine, excluded) => {
3149
3321
  const externalInclude = url.includes(':');
3150
3322
 
3151
3323
  const included = externalInclude
3152
- ? downloadFile(url)
3324
+ ? await downloadFile(url)
3153
3325
  : readFile$1(path$1.join(filterDir, url));
3154
3326
 
3155
3327
  if (included) {
@@ -3416,11 +3588,11 @@ const makeRevision = function (path, hash) {
3416
3588
  * Builds filter txt file from directory contents
3417
3589
  *
3418
3590
  * @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.
3591
+ * @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
3592
+ * @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
3421
3593
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3422
3594
  */
3423
- const buildFilter = async function (filterDir, whitelist, blacklist) {
3595
+ const buildFilter = async function (filterDir, includedFilterIds, excludedFilterIds) {
3424
3596
  const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
3425
3597
  if (!templateContent) {
3426
3598
  throw new Error('Invalid template');
@@ -3437,12 +3609,12 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3437
3609
  return;
3438
3610
  }
3439
3611
 
3440
- if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
3612
+ if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
3441
3613
  logger.info(`Filter ${filterId} skipped due to '--include' option`);
3442
3614
  return;
3443
3615
  }
3444
3616
 
3445
- if (blacklist && blacklist.length > 0 && blacklist.indexOf(filterId) >= 0) {
3617
+ if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
3446
3618
  logger.info(`Filter ${filterId} skipped due to '--skip' option`);
3447
3619
  return;
3448
3620
  }
@@ -3482,30 +3654,30 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3482
3654
  };
3483
3655
 
3484
3656
  /**
3485
- * Asynchronously parses a directory and processes filters based on the provided whitelist and blacklist.
3657
+ * Asynchronously parses a directory and processes filters based on the provided included/excluded filter IDs.
3486
3658
  *
3487
3659
  * @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.
3660
+ * @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
3661
+ * @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
3490
3662
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3491
3663
  */
3492
- const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3493
- const items = fs$1.readdirSync(filtersDir)
3664
+ const parseDirectory = async function (filtersDir, includedFilterIds, excludedFilterIds) {
3665
+ const items = fs$2.readdirSync(filtersDir)
3494
3666
  .sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
3495
3667
 
3496
3668
  // eslint-disable-next-line no-restricted-syntax
3497
3669
  for (const directory of items) {
3498
3670
  const filterDir = path$1.join(filtersDir, directory);
3499
- if (fs$1.lstatSync(filterDir).isDirectory()) {
3671
+ if (fs$2.lstatSync(filterDir).isDirectory()) {
3500
3672
  const template = path$1.join(filterDir, TEMPLATE_FILE);
3501
- if (fs$1.existsSync(template)) {
3673
+ if (fs$2.existsSync(template)) {
3502
3674
  logger.info(`Building filter ${directory}...`);
3503
3675
  // eslint-disable-next-line no-await-in-loop
3504
- await buildFilter(filterDir, whitelist, blacklist);
3676
+ await buildFilter(filterDir, includedFilterIds, excludedFilterIds);
3505
3677
  logger.info(`Filter ${directory} ok`);
3506
3678
  } else {
3507
3679
  // eslint-disable-next-line no-await-in-loop
3508
- await parseDirectory(filterDir, whitelist, blacklist);
3680
+ await parseDirectory(filterDir, includedFilterIds, excludedFilterIds);
3509
3681
  }
3510
3682
  }
3511
3683
  }
@@ -3519,10 +3691,10 @@ const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3519
3691
  * @param {string} filtersDir - The directory containing filter files to be processed.
3520
3692
  * @param {string} logFile - The path to the log file where logs will be written.
3521
3693
  * @param {string} reportFile - The path to the report file to be created.
3522
- * @param {string} platformsPath - The path where platform data will be generated.
3694
+ * @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
3523
3695
  * @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.
3696
+ * @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include in processing.
3697
+ * @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude from processing.
3526
3698
  * @returns {Promise<void>} A promise that resolves when the build process is complete.
3527
3699
  */
3528
3700
  const build = async (
@@ -3531,16 +3703,16 @@ const build = async (
3531
3703
  reportFile,
3532
3704
  platformsPath,
3533
3705
  platformsConfig,
3534
- whitelist,
3535
- blacklist,
3706
+ includedFilterIds,
3707
+ excludedFilterIds,
3536
3708
  ) => {
3537
3709
  logger.initialize(logFile);
3538
3710
  init(FILTER_FILE, METADATA_FILE, REVISION_FILE, platformsConfig, ADGUARD_FILTERS_SERVER_URL);
3539
3711
 
3540
- await parseDirectory(filtersDir, whitelist, blacklist);
3712
+ await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
3541
3713
 
3542
3714
  logger.info('Generating platforms');
3543
- await generate(filtersDir, platformsPath, whitelist, blacklist);
3715
+ await generate(filtersDir, platformsPath, includedFilterIds, excludedFilterIds);
3544
3716
  logger.info('Generating platforms done');
3545
3717
  create(reportFile);
3546
3718
  };
@@ -3561,14 +3733,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
3561
3733
  const loadSchemas = (dir) => {
3562
3734
  const schemas = {};
3563
3735
 
3564
- const items = fs$1.readdirSync(dir);
3736
+ const items = fs$2.readdirSync(dir);
3565
3737
  // eslint-disable-next-line no-restricted-syntax
3566
3738
  for (const f of items) {
3567
3739
  if (f.endsWith(SCHEMA_EXTENSION)) {
3568
3740
  const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
3569
3741
 
3570
3742
  logger.info(`Loading schema for ${validationFileName}`);
3571
- schemas[validationFileName] = JSON.parse(fs$1.readFileSync(path$1.join(dir, f)));
3743
+ schemas[validationFileName] = JSON.parse(fs$2.readFileSync(path$1.join(dir, f)));
3572
3744
  }
3573
3745
  }
3574
3746
 
@@ -3588,7 +3760,7 @@ const loadSchemas = (dir) => {
3588
3760
  const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
3589
3761
  let items;
3590
3762
  try {
3591
- items = fs$1.readdirSync(dir);
3763
+ items = fs$2.readdirSync(dir);
3592
3764
  } catch (e) {
3593
3765
  logger.info(e.message);
3594
3766
  return false;
@@ -3596,7 +3768,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3596
3768
  // eslint-disable-next-line no-restricted-syntax
3597
3769
  for (const f of items) {
3598
3770
  const item = path$1.join(dir, f);
3599
- if (fs$1.lstatSync(item).isDirectory()) {
3771
+ if (fs$2.lstatSync(item).isDirectory()) {
3600
3772
  if (!validateDir(item, validator, schemas, oldSchemas)) {
3601
3773
  return false;
3602
3774
  }
@@ -3619,7 +3791,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3619
3791
  if (schema) {
3620
3792
  logger.info(`Validating ${item}`);
3621
3793
 
3622
- const json = JSON.parse(fs$1.readFileSync(item));
3794
+ const json = JSON.parse(fs$2.readFileSync(item));
3623
3795
 
3624
3796
  // Validate filters amount
3625
3797
  if (fileName === 'filters') {
@@ -3633,11 +3805,11 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3633
3805
  const valid = validate(json);
3634
3806
 
3635
3807
  // json can be updated with default values
3636
- fs$1.writeFileSync(item, JSON.stringify(json, null, '\t'));
3808
+ fs$2.writeFileSync(item, JSON.stringify(json, null, '\t'));
3637
3809
 
3638
3810
  // duplicate to .js file as well
3639
3811
  const jsFileName = `${fileName}.js`;
3640
- fs$1.writeFileSync(
3812
+ fs$2.writeFileSync(
3641
3813
  path$1.join(path$1.dirname(item), jsFileName),
3642
3814
  JSON.stringify(json, null, '\t'),
3643
3815
  );
@@ -3728,13 +3900,13 @@ const WARNING_TYPES = {
3728
3900
  * Sync reads file content
3729
3901
  * @param filePath - path to locales file
3730
3902
  */
3731
- const readFile = (filePath) => fs$1.readFileSync(path$1.resolve(__dirname$2, filePath), 'utf8');
3903
+ const readFile = (filePath) => fs$2.readFileSync(path$1.resolve(__dirname$2, filePath), 'utf8');
3732
3904
 
3733
3905
  /**
3734
3906
  * Sync reads directory content
3735
3907
  * @param dirPath - path to directory
3736
3908
  */
3737
- const readDir = (dirPath) => fs$1.readdirSync(path$1.resolve(__dirname$2, dirPath), 'utf8');
3909
+ const readDir = (dirPath) => fs$2.readdirSync(path$1.resolve(__dirname$2, dirPath), 'utf8');
3738
3910
 
3739
3911
  /**
3740
3912
  * Validates messages keys
@@ -4832,7 +5004,15 @@ process.on('unhandledRejection', (error) => {
4832
5004
  throw error;
4833
5005
  });
4834
5006
 
4835
- const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist, customPlatformsConfig) => {
5007
+ const compile = (
5008
+ path,
5009
+ logPath,
5010
+ reportFile,
5011
+ platformsPath,
5012
+ includedFilterIds,
5013
+ excludedFilterIds,
5014
+ customPlatformsConfig,
5015
+ ) => {
4836
5016
  if (customPlatformsConfig) {
4837
5017
  logger.info('Using custom platforms configuration');
4838
5018
  // eslint-disable-next-line no-restricted-syntax, guard-for-in
@@ -4848,8 +5028,8 @@ const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist,
4848
5028
  reportFile,
4849
5029
  platformsPath,
4850
5030
  platformsConfig,
4851
- whitelist,
4852
- blacklist,
5031
+ includedFilterIds,
5032
+ excludedFilterIds,
4853
5033
  );
4854
5034
  };
4855
5035
 
@@ -4861,6 +5041,8 @@ const validateLocales = (localesDirPath, requiredLocales) => {
4861
5041
  return localesValidator.validate(localesDirPath, requiredLocales);
4862
5042
  };
4863
5043
 
5044
+ exports.OptimizationStatsError = OptimizationStatsError;
4864
5045
  exports.compile = compile;
5046
+ exports.localOptimizationStatistics = localOptimizationStatistics;
4865
5047
  exports.validateJSONSchema = validateJSONSchema;
4866
5048
  exports.validateLocales = validateLocales;