@adguard/filters-compiler 3.2.10 → 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.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,279 @@ 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.
1188
+ * Existing `stats.json` files will be overwritten.
1189
+ *
1190
+ * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
1191
+ *
1192
+ * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
1193
+ * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
1194
+ * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
1195
+ * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
1196
+ */
1197
+ download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
1198
+ if (includedFilterIds.length > 0 && excludedFilterIds.length > 0) {
1199
+ throw new Error('includedFilterIds and excludedFilterIds cannot both be non-empty');
1108
1200
  }
1201
+ const percent = JSON.parse(await downloadOptimizationPercent());
1202
+ const configs = percent.config.filter(({ filterId }) => {
1203
+ if (includedFilterIds.length > 0) {
1204
+ return includedFilterIds.includes(filterId);
1205
+ }
1206
+ if (excludedFilterIds.length > 0) {
1207
+ return !excludedFilterIds.includes(filterId);
1208
+ }
1209
+ return true;
1210
+ });
1211
+ const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
1212
+ /**
1213
+ * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1214
+ * parallel requests if the underlying transport ever becomes truly async.
1215
+ */
1216
+ const DOWNLOAD_CONCURRENCY = 8;
1217
+ await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1218
+ const dir = path$1.join(FILTERS_PATH, String(filterId));
1219
+ const statsPath = path$1.join(dir, STATS_JSON);
1220
+ const content = await downloadOptimizationStats(filterId);
1221
+ await fs$1.mkdir(dir, { recursive: true });
1222
+ await fs$1.writeFile(statsPath, content, 'utf-8');
1223
+ });
1224
+ },
1225
+ /**
1226
+ * Configures `getOptimizationStatistics` to read stats from local files under
1227
+ * `basePath` instead of fetching from the remote server.
1228
+ * Stats are loaded lazily on demand during compilation. `percent.json` is
1229
+ * still fetched remotely to determine which filters are optimizable.
1230
+ *
1231
+ * @param basePath - Directory containing `filters/<filterId>/stats.json`.
1232
+ */
1233
+ use(basePath) {
1234
+ localStatsPath = basePath;
1235
+ optimizableFilterIdsPromise = null;
1236
+ },
1237
+ /**
1238
+ * Removes the cache directory and clears in-memory state.
1239
+ *
1240
+ * @param basePath - Directory to remove.
1241
+ */
1242
+ async reset(basePath) {
1243
+ await fs$1.rm(basePath, { recursive: true, force: true });
1244
+ localStatsPath = null;
1245
+ optimizableFilterIdsPromise = null;
1246
+ },
1247
+ };
1248
+ /**
1249
+ * Returns the optimization stats for the given filter, or `null` when
1250
+ * optimization is disabled or the filter is not listed in `percent.json`.
1251
+ *
1252
+ * When `localOptimizationStatistics.use(path)` has been called, stats
1253
+ * are read lazily from local files. Otherwise stats are fetched from the
1254
+ * remote server.
1255
+ *
1256
+ * @param filterId - Numeric filter identifier.
1257
+ * @returns Parsed stats object, or `null` when the filter has no optimization stats.
1258
+ * @throws {Error} When the stats are missing or malformed.
1259
+ */
1260
+ const getOptimizationStatistics = async (filterId) => {
1261
+ const ids = await getOptimizableFilterIds();
1262
+ if (!ids.has(filterId)) {
1263
+ return null;
1109
1264
  }
1110
-
1111
- return optimizationConfig;
1265
+ let stats;
1266
+ try {
1267
+ const content = localStatsPath !== null
1268
+ ? await fs$1.readFile(path$1.join(localStatsPath, FILTERS_DIR_NAME, String(filterId), STATS_JSON), 'utf-8')
1269
+ : await downloadOptimizationStats(filterId);
1270
+ stats = JSON.parse(content);
1271
+ }
1272
+ catch (originalError) {
1273
+ const statsPath = localStatsPath === null
1274
+ ? getOptimizationStatsUrl(filterId)
1275
+ : `${localStatsPath}/filters/${filterId}/stats.json`;
1276
+ throw new OptimizationStatsError(filterId, statsPath, { cause: originalError });
1277
+ }
1278
+ assertValidStats(filterId, stats);
1279
+ return stats;
1112
1280
  };
1113
-
1114
1281
  /**
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)
1282
+ * Checks if rule should be skipped because optimization is enabled for this filter
1283
+ * and the hit count for this rule is below the configured threshold.
1284
+ *
1285
+ * @param ruleText - Rule text to check.
1286
+ * @param optimizationStats - Optimization config for this filter.
1287
+ * @returns `true` if the rule should be skipped, `false` otherwise.
1119
1288
  */
1120
- const skipRuleWithOptimization = (ruleText, optimizationConfig) => {
1121
- if (!optimizationConfig) {
1289
+ const skipRuleWithOptimization = (ruleText, optimizationStats) => {
1290
+ if (!optimizationStats) {
1122
1291
  return false;
1123
1292
  }
1124
-
1125
1293
  // eslint-disable-next-line no-restricted-syntax
1126
- for (const group of optimizationConfig.groups) {
1294
+ for (const group of optimizationStats.groups) {
1127
1295
  const hits = group.rules[ruleText];
1128
1296
  if (hits !== undefined && hits < group.config.hits) {
1129
1297
  return true;
1130
1298
  }
1131
1299
  }
1132
-
1133
1300
  return false;
1134
1301
  };
1135
1302
 
@@ -1490,7 +1657,7 @@ let adguardFiltersServerUrl = null;
1490
1657
  */
1491
1658
  const readFile$2 = function (path) {
1492
1659
  try {
1493
- return fs$1.readFileSync(path, { encoding: 'utf-8' });
1660
+ return fs$2.readFileSync(path, { encoding: 'utf-8' });
1494
1661
  } catch (e) {
1495
1662
  return null;
1496
1663
  }
@@ -1603,7 +1770,7 @@ const createDir = (dir) => {
1603
1770
  return dir.split(sep).reduce((parentDir, childDir) => {
1604
1771
  const curDir = path$1.resolve(baseDir, parentDir, childDir);
1605
1772
  try {
1606
- fs$1.mkdirSync(curDir);
1773
+ fs$2.mkdirSync(curDir);
1607
1774
  } catch (err) {
1608
1775
  if (err.code === 'EEXIST') { // curDir already exists!
1609
1776
  return curDir;
@@ -1874,11 +2041,11 @@ const loadLocales = function (dir) {
1874
2041
  filters: {},
1875
2042
  };
1876
2043
 
1877
- const locales = fs$1.readdirSync(dir);
2044
+ const locales = fs$2.readdirSync(dir);
1878
2045
  // eslint-disable-next-line no-restricted-syntax
1879
2046
  for (const directory of locales) {
1880
2047
  const localeDir = path$1.join(dir, directory);
1881
- if (fs$1.lstatSync(localeDir).isDirectory()) {
2048
+ if (fs$2.lstatSync(localeDir).isDirectory()) {
1882
2049
  const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
1883
2050
  if (groups) {
1884
2051
  // eslint-disable-next-line no-restricted-syntax
@@ -2103,8 +2270,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
2103
2270
 
2104
2271
  const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
2105
2272
 
2106
- fs$1.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2107
- fs$1.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2273
+ fs$2.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2274
+ fs$2.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2108
2275
 
2109
2276
  logger.info(`Writing filters localizations: ${config.path}`);
2110
2277
  const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
@@ -2145,8 +2312,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
2145
2312
 
2146
2313
  const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
2147
2314
 
2148
- fs$1.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2149
- fs$1.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2315
+ fs$2.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2316
+ fs$2.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2150
2317
  }
2151
2318
 
2152
2319
  logger.info('Writing filters metadata done');
@@ -2206,12 +2373,12 @@ const writeLocalScriptRules = function (platformsPath) {
2206
2373
  // remove scriptlet rules in local_script_rules.json
2207
2374
  rulesJson.rules = removeScriptletRules(rulesJson.rules);
2208
2375
 
2209
- fs$1.writeFileSync(
2376
+ fs$2.writeFileSync(
2210
2377
  path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
2211
2378
  rulesTxt.join(RULES_SEPARATOR),
2212
2379
  'utf8',
2213
2380
  );
2214
- fs$1.writeFileSync(
2381
+ fs$2.writeFileSync(
2215
2382
  path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
2216
2383
  JSON.stringify(rulesJson, null, 4),
2217
2384
  'utf8',
@@ -2225,12 +2392,12 @@ const writeLocalScriptRules = function (platformsPath) {
2225
2392
  * Loads and processes filter metadata from the specified directory.
2226
2393
  *
2227
2394
  * @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.
2395
+ * @param {number[]} [includedFilterIds] - An optional array of filter IDs to include.
2396
+ * @param {number[]} [excludedFilterIds] - An optional array of filter IDs to exclude.
2230
2397
  * @returns {Object} The processed filter metadata.
2231
2398
  * @throws {Error} If the metadata or revision file cannot be read.
2232
2399
  */
2233
- const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2400
+ const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilterIds) {
2234
2401
  const metadataFilePath = path$1.join(filterDir, metadataFile);
2235
2402
  const metadataString = readFile$2(metadataFilePath);
2236
2403
  if (!metadataString) {
@@ -2253,8 +2420,8 @@ const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2253
2420
 
2254
2421
  const { filterId } = result;
2255
2422
  if (
2256
- (whitelist && whitelist.includes(filterId))
2257
- || (blacklist && !blacklist.includes(filterId))
2423
+ (includedFilterIds && includedFilterIds.includes(filterId))
2424
+ || (excludedFilterIds && !excludedFilterIds.includes(filterId))
2258
2425
  ) {
2259
2426
  checkFilterId(metadataFilterIdsPool, filterId);
2260
2427
  }
@@ -2298,7 +2465,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
2298
2465
  data = [adbHeader].concat(data);
2299
2466
  }
2300
2467
 
2301
- fs$1.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2468
+ fs$2.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2302
2469
  };
2303
2470
 
2304
2471
  /**
@@ -2378,10 +2545,10 @@ const removeRuleDuplicates = function (list) {
2378
2545
  *
2379
2546
  * @param filterDir - Path to filter directory
2380
2547
  * @param platformsPath - Path to platforms folder
2381
- * @param whitelist - Array of filter ids to whitelist
2382
- * @param blacklist - Array of filter ids to blacklist
2548
+ * @param includedFilterIds - Array of filter ids to include
2549
+ * @param excludedFilterIds - Array of filter ids to exclude
2383
2550
  */
2384
- const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) => {
2551
+ const buildFilter$1 = async (filterDir, platformsPath, includedFilterIds, excludedFilterIds) => {
2385
2552
  const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
2386
2553
 
2387
2554
  const metadataFilePath = path$1.join(filterDir, metadataFile);
@@ -2391,17 +2558,17 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
2391
2558
  const { filterId } = metadata;
2392
2559
  checkFilterId(filterIdsPool, filterId);
2393
2560
 
2394
- if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
2395
- logger.info(`Filter ${filterId} skipped with whitelist`);
2561
+ if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
2562
+ logger.info(`Filter ${filterId} skipped due to '--include' option`);
2396
2563
  return;
2397
2564
  }
2398
2565
 
2399
- if (blacklist && blacklist.length > 0 && blacklist.indexOf(filterId) >= 0) {
2400
- logger.info(`Filter ${filterId} skipped with blacklist`);
2566
+ if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
2567
+ logger.info(`Filter ${filterId} skipped due to '--skip' option`);
2401
2568
  return;
2402
2569
  }
2403
2570
 
2404
- const optimizationConfig = getFilterOptimizationConfig(filterId);
2571
+ const optimizationConfig = await getOptimizationStatistics(filterId);
2405
2572
 
2406
2573
  // eslint-disable-next-line guard-for-in,no-restricted-syntax
2407
2574
  for (const platform in platformPathsConfig) {
@@ -2495,30 +2662,30 @@ const isObsoleteFilter = (metadata) => metadata.tags && metadata.tags.some((tag)
2495
2662
  * @param filtersDir
2496
2663
  * @param filtersMetadata
2497
2664
  * @param platformsPath
2498
- * @param whitelist
2499
- * @param blacklist
2665
+ * @param includedFilterIds
2666
+ * @param excludedFilterIds
2500
2667
  * @param obsoleteFiltersMetadata
2501
2668
  */
2502
2669
  const parseDirectory$1 = async (
2503
2670
  filtersDir,
2504
2671
  filtersMetadata,
2505
2672
  platformsPath,
2506
- whitelist,
2507
- blacklist,
2673
+ includedFilterIds,
2674
+ excludedFilterIds,
2508
2675
  obsoleteFiltersMetadata,
2509
2676
  ) => {
2510
- const items = fs$1.readdirSync(filtersDir);
2677
+ const items = fs$2.readdirSync(filtersDir);
2511
2678
  // eslint-disable-next-line no-restricted-syntax
2512
2679
  for (const directory of items) {
2513
2680
  const filterDir = path$1.join(filtersDir, directory);
2514
- if (fs$1.lstatSync(filterDir).isDirectory()) {
2681
+ if (fs$2.lstatSync(filterDir).isDirectory()) {
2515
2682
  const metadataFilePath = path$1.join(filterDir, metadataFile);
2516
- if (fs$1.existsSync(metadataFilePath)) {
2683
+ if (fs$2.existsSync(metadataFilePath)) {
2517
2684
  logger.info(`Building filter platforms: ${directory}`);
2518
2685
  // eslint-disable-next-line no-await-in-loop
2519
- await buildFilter$1(filterDir, platformsPath, whitelist, blacklist);
2686
+ await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
2520
2687
  logger.info(`Building filter platforms: ${directory} done`);
2521
- const filterMetadata = loadFilterMetadata(filterDir, whitelist, blacklist);
2688
+ const filterMetadata = loadFilterMetadata(filterDir, includedFilterIds, excludedFilterIds);
2522
2689
  filtersMetadata.push(filterMetadata);
2523
2690
  if (isObsoleteFilter(filterMetadata)) {
2524
2691
  obsoleteFiltersMetadata.push(filterMetadata);
@@ -2529,8 +2696,8 @@ const parseDirectory$1 = async (
2529
2696
  filterDir,
2530
2697
  filtersMetadata,
2531
2698
  platformsPath,
2532
- whitelist,
2533
- blacklist,
2699
+ includedFilterIds,
2700
+ excludedFilterIds,
2534
2701
  obsoleteFiltersMetadata,
2535
2702
  );
2536
2703
  }
@@ -2543,14 +2710,12 @@ const parseDirectory$1 = async (
2543
2710
  *
2544
2711
  * @async
2545
2712
  * @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.
2713
+ * @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
2714
+ * @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include.
2715
+ * @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude.
2549
2716
  * @returns {Promise<void>} Resolves when the generation process is complete.
2550
- *
2551
- * @throws {Error} If `platformsPath` or `platformPathsConfig` is not specified.
2552
2717
  */
2553
- const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
2718
+ const generate = async (filtersDir, platformsPath, includedFilterIds, excludedFilterIds) => {
2554
2719
  if (!platformsPath) {
2555
2720
  logger.warn('Platforms build output path is not specified');
2556
2721
  return;
@@ -2566,7 +2731,14 @@ const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
2566
2731
  const filtersMetadata = [];
2567
2732
  const obsoleteFiltersMetadata = [];
2568
2733
 
2569
- await parseDirectory$1(filtersDir, filtersMetadata, platformsPath, whitelist, blacklist, obsoleteFiltersMetadata);
2734
+ await parseDirectory$1(
2735
+ filtersDir,
2736
+ filtersMetadata,
2737
+ platformsPath,
2738
+ includedFilterIds,
2739
+ excludedFilterIds,
2740
+ obsoleteFiltersMetadata,
2741
+ );
2570
2742
 
2571
2743
  writeFiltersMetadata(platformsPath, filtersDir, filtersMetadata, obsoleteFiltersMetadata);
2572
2744
  writeLocalScriptRules(platformsPath);
@@ -2632,7 +2804,7 @@ const skipFilter = (metadata) => {
2632
2804
  */
2633
2805
  const create = (reportPath) => {
2634
2806
  if (reportPath) {
2635
- fs$1.writeFileSync(reportPath, reportData, 'utf8');
2807
+ fs$2.writeFileSync(reportPath, reportData, 'utf8');
2636
2808
  return;
2637
2809
  }
2638
2810
  log(reportData);
@@ -2835,11 +3007,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
2835
3007
  * @returns {*}
2836
3008
  */
2837
3009
  const readFile$1 = function (path) {
2838
- if (!fs$1.existsSync(path)) {
3010
+ if (!fs$2.existsSync(path)) {
2839
3011
  return null;
2840
3012
  }
2841
3013
 
2842
- return fs$1.readFileSync(path, { encoding: 'utf-8' });
3014
+ return fs$2.readFileSync(path, { encoding: 'utf-8' });
2843
3015
  };
2844
3016
 
2845
3017
  /**
@@ -2849,7 +3021,7 @@ const readFile$1 = function (path) {
2849
3021
  * @param data
2850
3022
  */
2851
3023
  const writeFile = function (path, data) {
2852
- fs$1.writeFileSync(path, data, 'utf8');
3024
+ fs$2.writeFileSync(path, data, 'utf8');
2853
3025
  };
2854
3026
 
2855
3027
  /**
@@ -3146,7 +3318,7 @@ const include = async (filterDir, directiveLine, excluded) => {
3146
3318
  const externalInclude = url.includes(':');
3147
3319
 
3148
3320
  const included = externalInclude
3149
- ? downloadFile(url)
3321
+ ? await downloadFile(url)
3150
3322
  : readFile$1(path$1.join(filterDir, url));
3151
3323
 
3152
3324
  if (included) {
@@ -3413,11 +3585,11 @@ const makeRevision = function (path, hash) {
3413
3585
  * Builds filter txt file from directory contents
3414
3586
  *
3415
3587
  * @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.
3588
+ * @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
3589
+ * @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
3418
3590
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3419
3591
  */
3420
- const buildFilter = async function (filterDir, whitelist, blacklist) {
3592
+ const buildFilter = async function (filterDir, includedFilterIds, excludedFilterIds) {
3421
3593
  const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
3422
3594
  if (!templateContent) {
3423
3595
  throw new Error('Invalid template');
@@ -3434,12 +3606,12 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3434
3606
  return;
3435
3607
  }
3436
3608
 
3437
- if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
3609
+ if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
3438
3610
  logger.info(`Filter ${filterId} skipped due to '--include' option`);
3439
3611
  return;
3440
3612
  }
3441
3613
 
3442
- if (blacklist && blacklist.length > 0 && blacklist.indexOf(filterId) >= 0) {
3614
+ if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
3443
3615
  logger.info(`Filter ${filterId} skipped due to '--skip' option`);
3444
3616
  return;
3445
3617
  }
@@ -3479,30 +3651,30 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3479
3651
  };
3480
3652
 
3481
3653
  /**
3482
- * Asynchronously parses a directory and processes filters based on the provided whitelist and blacklist.
3654
+ * Asynchronously parses a directory and processes filters based on the provided included/excluded filter IDs.
3483
3655
  *
3484
3656
  * @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.
3657
+ * @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
3658
+ * @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
3487
3659
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3488
3660
  */
3489
- const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3490
- const items = fs$1.readdirSync(filtersDir)
3661
+ const parseDirectory = async function (filtersDir, includedFilterIds, excludedFilterIds) {
3662
+ const items = fs$2.readdirSync(filtersDir)
3491
3663
  .sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
3492
3664
 
3493
3665
  // eslint-disable-next-line no-restricted-syntax
3494
3666
  for (const directory of items) {
3495
3667
  const filterDir = path$1.join(filtersDir, directory);
3496
- if (fs$1.lstatSync(filterDir).isDirectory()) {
3668
+ if (fs$2.lstatSync(filterDir).isDirectory()) {
3497
3669
  const template = path$1.join(filterDir, TEMPLATE_FILE);
3498
- if (fs$1.existsSync(template)) {
3670
+ if (fs$2.existsSync(template)) {
3499
3671
  logger.info(`Building filter ${directory}...`);
3500
3672
  // eslint-disable-next-line no-await-in-loop
3501
- await buildFilter(filterDir, whitelist, blacklist);
3673
+ await buildFilter(filterDir, includedFilterIds, excludedFilterIds);
3502
3674
  logger.info(`Filter ${directory} ok`);
3503
3675
  } else {
3504
3676
  // eslint-disable-next-line no-await-in-loop
3505
- await parseDirectory(filterDir, whitelist, blacklist);
3677
+ await parseDirectory(filterDir, includedFilterIds, excludedFilterIds);
3506
3678
  }
3507
3679
  }
3508
3680
  }
@@ -3516,10 +3688,10 @@ const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3516
3688
  * @param {string} filtersDir - The directory containing filter files to be processed.
3517
3689
  * @param {string} logFile - The path to the log file where logs will be written.
3518
3690
  * @param {string} reportFile - The path to the report file to be created.
3519
- * @param {string} platformsPath - The path where platform data will be generated.
3691
+ * @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
3520
3692
  * @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.
3693
+ * @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include in processing.
3694
+ * @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude from processing.
3523
3695
  * @returns {Promise<void>} A promise that resolves when the build process is complete.
3524
3696
  */
3525
3697
  const build = async (
@@ -3528,16 +3700,16 @@ const build = async (
3528
3700
  reportFile,
3529
3701
  platformsPath,
3530
3702
  platformsConfig,
3531
- whitelist,
3532
- blacklist,
3703
+ includedFilterIds,
3704
+ excludedFilterIds,
3533
3705
  ) => {
3534
3706
  logger.initialize(logFile);
3535
3707
  init(FILTER_FILE, METADATA_FILE, REVISION_FILE, platformsConfig, ADGUARD_FILTERS_SERVER_URL);
3536
3708
 
3537
- await parseDirectory(filtersDir, whitelist, blacklist);
3709
+ await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
3538
3710
 
3539
3711
  logger.info('Generating platforms');
3540
- await generate(filtersDir, platformsPath, whitelist, blacklist);
3712
+ await generate(filtersDir, platformsPath, includedFilterIds, excludedFilterIds);
3541
3713
  logger.info('Generating platforms done');
3542
3714
  create(reportFile);
3543
3715
  };
@@ -3558,14 +3730,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
3558
3730
  const loadSchemas = (dir) => {
3559
3731
  const schemas = {};
3560
3732
 
3561
- const items = fs$1.readdirSync(dir);
3733
+ const items = fs$2.readdirSync(dir);
3562
3734
  // eslint-disable-next-line no-restricted-syntax
3563
3735
  for (const f of items) {
3564
3736
  if (f.endsWith(SCHEMA_EXTENSION)) {
3565
3737
  const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
3566
3738
 
3567
3739
  logger.info(`Loading schema for ${validationFileName}`);
3568
- schemas[validationFileName] = JSON.parse(fs$1.readFileSync(path$1.join(dir, f)));
3740
+ schemas[validationFileName] = JSON.parse(fs$2.readFileSync(path$1.join(dir, f)));
3569
3741
  }
3570
3742
  }
3571
3743
 
@@ -3585,7 +3757,7 @@ const loadSchemas = (dir) => {
3585
3757
  const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
3586
3758
  let items;
3587
3759
  try {
3588
- items = fs$1.readdirSync(dir);
3760
+ items = fs$2.readdirSync(dir);
3589
3761
  } catch (e) {
3590
3762
  logger.info(e.message);
3591
3763
  return false;
@@ -3593,7 +3765,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3593
3765
  // eslint-disable-next-line no-restricted-syntax
3594
3766
  for (const f of items) {
3595
3767
  const item = path$1.join(dir, f);
3596
- if (fs$1.lstatSync(item).isDirectory()) {
3768
+ if (fs$2.lstatSync(item).isDirectory()) {
3597
3769
  if (!validateDir(item, validator, schemas, oldSchemas)) {
3598
3770
  return false;
3599
3771
  }
@@ -3616,7 +3788,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3616
3788
  if (schema) {
3617
3789
  logger.info(`Validating ${item}`);
3618
3790
 
3619
- const json = JSON.parse(fs$1.readFileSync(item));
3791
+ const json = JSON.parse(fs$2.readFileSync(item));
3620
3792
 
3621
3793
  // Validate filters amount
3622
3794
  if (fileName === 'filters') {
@@ -3630,11 +3802,11 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3630
3802
  const valid = validate(json);
3631
3803
 
3632
3804
  // json can be updated with default values
3633
- fs$1.writeFileSync(item, JSON.stringify(json, null, '\t'));
3805
+ fs$2.writeFileSync(item, JSON.stringify(json, null, '\t'));
3634
3806
 
3635
3807
  // duplicate to .js file as well
3636
3808
  const jsFileName = `${fileName}.js`;
3637
- fs$1.writeFileSync(
3809
+ fs$2.writeFileSync(
3638
3810
  path$1.join(path$1.dirname(item), jsFileName),
3639
3811
  JSON.stringify(json, null, '\t'),
3640
3812
  );
@@ -3725,13 +3897,13 @@ const WARNING_TYPES = {
3725
3897
  * Sync reads file content
3726
3898
  * @param filePath - path to locales file
3727
3899
  */
3728
- const readFile = (filePath) => fs$1.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
3900
+ const readFile = (filePath) => fs$2.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
3729
3901
 
3730
3902
  /**
3731
3903
  * Sync reads directory content
3732
3904
  * @param dirPath - path to directory
3733
3905
  */
3734
- const readDir = (dirPath) => fs$1.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
3906
+ const readDir = (dirPath) => fs$2.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
3735
3907
 
3736
3908
  /**
3737
3909
  * Validates messages keys
@@ -4829,7 +5001,15 @@ process.on('unhandledRejection', (error) => {
4829
5001
  throw error;
4830
5002
  });
4831
5003
 
4832
- const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist, customPlatformsConfig) => {
5004
+ const compile = (
5005
+ path,
5006
+ logPath,
5007
+ reportFile,
5008
+ platformsPath,
5009
+ includedFilterIds,
5010
+ excludedFilterIds,
5011
+ customPlatformsConfig,
5012
+ ) => {
4833
5013
  if (customPlatformsConfig) {
4834
5014
  logger.info('Using custom platforms configuration');
4835
5015
  // eslint-disable-next-line no-restricted-syntax, guard-for-in
@@ -4845,8 +5025,8 @@ const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist,
4845
5025
  reportFile,
4846
5026
  platformsPath,
4847
5027
  platformsConfig,
4848
- whitelist,
4849
- blacklist,
5028
+ includedFilterIds,
5029
+ excludedFilterIds,
4850
5030
  );
4851
5031
  };
4852
5032
 
@@ -4858,4 +5038,4 @@ const validateLocales = (localesDirPath, requiredLocales) => {
4858
5038
  return localesValidator.validate(localesDirPath, requiredLocales);
4859
5039
  };
4860
5040
 
4861
- export { compile, validateJSONSchema, validateLocales };
5041
+ export { OptimizationStatsError, compile, localOptimizationStatistics, validateJSONSchema, validateLocales };