@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/README.md +77 -14
- package/dist/index.cjs +348 -166
- package/dist/index.d.ts +16 -6
- package/dist/index.js +347 -167
- package/dist/main/optimization.d.ts +112 -0
- package/dist/types/src/main/optimization.d.ts +112 -0
- package/dist/types/src/main/utils/concurrent.d.ts +8 -0
- package/dist/types/src/main/utils/log.d.ts +31 -0
- package/dist/types/src/main/utils/webutils.d.ts +11 -0
- package/dist/types/test/optimization.test.d.ts +1 -0
- package/dist/types/test/webutils.test.d.ts +1 -0
- package/package.json +6 -3
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$
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
*
|
|
1010
|
+
* Downloads file from url
|
|
1026
1011
|
*
|
|
1027
1012
|
* @param url
|
|
1028
|
-
* @param
|
|
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
|
-
*
|
|
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
|
-
}
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
|
|
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
|
|
1119
|
-
* and
|
|
1120
|
-
*
|
|
1121
|
-
* @param
|
|
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,
|
|
1124
|
-
if (!
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2110
|
-
fs$
|
|
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$
|
|
2152
|
-
fs$
|
|
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$
|
|
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$
|
|
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[]} [
|
|
2232
|
-
* @param {number[]} [
|
|
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,
|
|
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
|
-
(
|
|
2260
|
-
|| (
|
|
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$
|
|
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
|
|
2385
|
-
* @param
|
|
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,
|
|
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 (
|
|
2398
|
-
logger.info(`Filter ${filterId} skipped
|
|
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 (
|
|
2403
|
-
logger.info(`Filter ${filterId} skipped
|
|
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 =
|
|
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
|
|
2502
|
-
* @param
|
|
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
|
-
|
|
2510
|
-
|
|
2676
|
+
includedFilterIds,
|
|
2677
|
+
excludedFilterIds,
|
|
2511
2678
|
obsoleteFiltersMetadata,
|
|
2512
2679
|
) => {
|
|
2513
|
-
const items = fs$
|
|
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$
|
|
2684
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
2518
2685
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2519
|
-
if (fs$
|
|
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,
|
|
2689
|
+
await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
2523
2690
|
logger.info(`Building filter platforms: ${directory} done`);
|
|
2524
|
-
const filterMetadata = loadFilterMetadata(filterDir,
|
|
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
|
-
|
|
2536
|
-
|
|
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
|
|
2550
|
-
* @param {Array<number
|
|
2551
|
-
* @param {Array<number
|
|
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,
|
|
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(
|
|
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$
|
|
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$
|
|
3013
|
+
if (!fs$2.existsSync(path)) {
|
|
2842
3014
|
return null;
|
|
2843
3015
|
}
|
|
2844
3016
|
|
|
2845
|
-
return fs$
|
|
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$
|
|
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
|
|
3420
|
-
* @param {Array<number
|
|
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,
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
3489
|
-
* @param {Array<number
|
|
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,
|
|
3493
|
-
const items = fs$
|
|
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$
|
|
3671
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
3500
3672
|
const template = path$1.join(filterDir, TEMPLATE_FILE);
|
|
3501
|
-
if (fs$
|
|
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,
|
|
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,
|
|
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
|
|
3525
|
-
* @param {Array<number
|
|
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
|
-
|
|
3535
|
-
|
|
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,
|
|
3712
|
+
await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
|
|
3541
3713
|
|
|
3542
3714
|
logger.info('Generating platforms');
|
|
3543
|
-
await generate(filtersDir, platformsPath,
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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 = (
|
|
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
|
-
|
|
4852
|
-
|
|
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;
|