@adguard/filters-compiler 3.2.11 → 3.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -14
- package/dist/index.cjs +355 -166
- package/dist/index.d.ts +16 -6
- package/dist/index.js +354 -167
- package/dist/main/optimization.d.ts +114 -0
- package/dist/types/src/main/optimization.d.ts +114 -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 +5 -2
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,286 @@ const tryDownloadFile = function (url, retryNum = 0) {
|
|
|
1042
1027
|
return require$1('child_process')
|
|
1043
1028
|
.execFileSync('curl', args, options);
|
|
1044
1029
|
};
|
|
1045
|
-
|
|
1046
1030
|
/**
|
|
1047
|
-
*
|
|
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. Downloads into a staged
|
|
1191
|
+
* directory first and swaps it in only after all downloads succeed,
|
|
1192
|
+
* so a failed refresh leaves any previously cached stats intact.
|
|
1193
|
+
*
|
|
1194
|
+
* When both `includedFilterIds` and `excludedFilterIds` are non-empty,
|
|
1195
|
+
* a filter is processed only if it is in `includedFilterIds` and not in
|
|
1196
|
+
* `excludedFilterIds`.
|
|
1197
|
+
*
|
|
1198
|
+
* @param basePath - Directory to save `filters/<filterId>/stats.json` into.
|
|
1199
|
+
* @param includedFilterIds - Filter IDs to process; empty (default) processes all.
|
|
1200
|
+
* @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
|
|
1201
|
+
*/
|
|
1202
|
+
download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
|
|
1203
|
+
const percent = JSON.parse(await downloadOptimizationPercent());
|
|
1204
|
+
const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
|
|
1205
|
+
&& (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
|
|
1206
|
+
const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
|
|
1207
|
+
const STAGED_PREFIX = `${process.pid}_${Date.now()}`;
|
|
1208
|
+
const STAGED_FILTERS_PATH = path$1.join(basePath, `${STAGED_PREFIX}_${FILTERS_DIR_NAME}`);
|
|
1209
|
+
/**
|
|
1210
|
+
* Bounds concurrency so a large `percent.json` cannot fan out into unbounded
|
|
1211
|
+
* parallel requests if the underlying transport ever becomes truly async.
|
|
1212
|
+
*/
|
|
1213
|
+
const DOWNLOAD_CONCURRENCY = 8;
|
|
1214
|
+
try {
|
|
1215
|
+
await fs$1.mkdir(STAGED_FILTERS_PATH, { recursive: true });
|
|
1216
|
+
await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
|
|
1217
|
+
const dir = path$1.join(STAGED_FILTERS_PATH, String(filterId));
|
|
1218
|
+
const statsPath = path$1.join(dir, STATS_JSON);
|
|
1219
|
+
const content = await downloadOptimizationStats(filterId);
|
|
1220
|
+
await fs$1.mkdir(dir, { recursive: true });
|
|
1221
|
+
await fs$1.writeFile(statsPath, content, 'utf-8');
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
// Downloads incomplete: staged dir is garbage, safe to discard.
|
|
1226
|
+
await fs$1.rm(STAGED_FILTERS_PATH, { recursive: true, force: true });
|
|
1227
|
+
throw error;
|
|
1111
1228
|
}
|
|
1229
|
+
// Swap only after every fetch succeeded. From here on, staged dir is
|
|
1230
|
+
// a complete cache — never rm it on failure, that'd destroy the only
|
|
1231
|
+
// remaining valid copy once the old FILTERS_PATH is gone.
|
|
1232
|
+
await fs$1.rm(FILTERS_PATH, { recursive: true, force: true });
|
|
1233
|
+
await fs$1.rename(STAGED_FILTERS_PATH, FILTERS_PATH);
|
|
1234
|
+
},
|
|
1235
|
+
/**
|
|
1236
|
+
* Configures `getOptimizationStatistics` to read stats from local files under
|
|
1237
|
+
* `basePath` instead of fetching from the remote server.
|
|
1238
|
+
* Stats are loaded lazily on demand during compilation. `percent.json` is
|
|
1239
|
+
* still fetched remotely to determine which filters are optimizable.
|
|
1240
|
+
*
|
|
1241
|
+
* @param basePath - Directory containing `filters/<filterId>/stats.json`.
|
|
1242
|
+
*/
|
|
1243
|
+
use(basePath) {
|
|
1244
|
+
localStatsPath = basePath;
|
|
1245
|
+
optimizableFilterIdsPromise = null;
|
|
1246
|
+
},
|
|
1247
|
+
/**
|
|
1248
|
+
* Removes the cache directory and clears in-memory state.
|
|
1249
|
+
*
|
|
1250
|
+
* @param basePath - Directory to remove.
|
|
1251
|
+
*/
|
|
1252
|
+
async reset(basePath) {
|
|
1253
|
+
await fs$1.rm(basePath, { recursive: true, force: true });
|
|
1254
|
+
localStatsPath = null;
|
|
1255
|
+
optimizableFilterIdsPromise = null;
|
|
1256
|
+
},
|
|
1257
|
+
};
|
|
1258
|
+
/**
|
|
1259
|
+
* Returns the optimization stats for the given filter, or `null` when
|
|
1260
|
+
* optimization is disabled or the filter is not listed in `percent.json`.
|
|
1261
|
+
*
|
|
1262
|
+
* When `localOptimizationStatistics.use(path)` has been called, stats
|
|
1263
|
+
* are read lazily from local files. Otherwise stats are fetched from the
|
|
1264
|
+
* remote server.
|
|
1265
|
+
*
|
|
1266
|
+
* @param filterId - Numeric filter identifier.
|
|
1267
|
+
* @returns Parsed stats object, or `null` when the filter has no optimization stats.
|
|
1268
|
+
* @throws {Error} When the stats are missing or malformed.
|
|
1269
|
+
*/
|
|
1270
|
+
const getOptimizationStatistics = async (filterId) => {
|
|
1271
|
+
const ids = await getOptimizableFilterIds();
|
|
1272
|
+
if (!ids.has(filterId)) {
|
|
1273
|
+
return null;
|
|
1112
1274
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1275
|
+
let stats;
|
|
1276
|
+
try {
|
|
1277
|
+
const content = localStatsPath !== null
|
|
1278
|
+
? await fs$1.readFile(path$1.join(localStatsPath, FILTERS_DIR_NAME, String(filterId), STATS_JSON), 'utf-8')
|
|
1279
|
+
: await downloadOptimizationStats(filterId);
|
|
1280
|
+
stats = JSON.parse(content);
|
|
1281
|
+
}
|
|
1282
|
+
catch (originalError) {
|
|
1283
|
+
const statsPath = localStatsPath === null
|
|
1284
|
+
? getOptimizationStatsUrl(filterId)
|
|
1285
|
+
: `${localStatsPath}/filters/${filterId}/stats.json`;
|
|
1286
|
+
throw new OptimizationStatsError(filterId, statsPath, { cause: originalError });
|
|
1287
|
+
}
|
|
1288
|
+
assertValidStats(filterId, stats);
|
|
1289
|
+
return stats;
|
|
1115
1290
|
};
|
|
1116
|
-
|
|
1117
1291
|
/**
|
|
1118
|
-
* Checks if rule should be skipped
|
|
1119
|
-
* and
|
|
1120
|
-
*
|
|
1121
|
-
* @param
|
|
1292
|
+
* Checks if rule should be skipped because optimization is enabled for this filter
|
|
1293
|
+
* and the hit count for this rule is below the configured threshold.
|
|
1294
|
+
*
|
|
1295
|
+
* @param ruleText - Rule text to check.
|
|
1296
|
+
* @param optimizationStats - Optimization config for this filter.
|
|
1297
|
+
* @returns `true` if the rule should be skipped, `false` otherwise.
|
|
1122
1298
|
*/
|
|
1123
|
-
const skipRuleWithOptimization = (ruleText,
|
|
1124
|
-
if (!
|
|
1299
|
+
const skipRuleWithOptimization = (ruleText, optimizationStats) => {
|
|
1300
|
+
if (!optimizationStats) {
|
|
1125
1301
|
return false;
|
|
1126
1302
|
}
|
|
1127
|
-
|
|
1128
1303
|
// eslint-disable-next-line no-restricted-syntax
|
|
1129
|
-
for (const group of
|
|
1304
|
+
for (const group of optimizationStats.groups) {
|
|
1130
1305
|
const hits = group.rules[ruleText];
|
|
1131
1306
|
if (hits !== undefined && hits < group.config.hits) {
|
|
1132
1307
|
return true;
|
|
1133
1308
|
}
|
|
1134
1309
|
}
|
|
1135
|
-
|
|
1136
1310
|
return false;
|
|
1137
1311
|
};
|
|
1138
1312
|
|
|
@@ -1493,7 +1667,7 @@ let adguardFiltersServerUrl = null;
|
|
|
1493
1667
|
*/
|
|
1494
1668
|
const readFile$2 = function (path) {
|
|
1495
1669
|
try {
|
|
1496
|
-
return fs$
|
|
1670
|
+
return fs$2.readFileSync(path, { encoding: 'utf-8' });
|
|
1497
1671
|
} catch (e) {
|
|
1498
1672
|
return null;
|
|
1499
1673
|
}
|
|
@@ -1606,7 +1780,7 @@ const createDir = (dir) => {
|
|
|
1606
1780
|
return dir.split(sep).reduce((parentDir, childDir) => {
|
|
1607
1781
|
const curDir = path$1.resolve(baseDir, parentDir, childDir);
|
|
1608
1782
|
try {
|
|
1609
|
-
fs$
|
|
1783
|
+
fs$2.mkdirSync(curDir);
|
|
1610
1784
|
} catch (err) {
|
|
1611
1785
|
if (err.code === 'EEXIST') { // curDir already exists!
|
|
1612
1786
|
return curDir;
|
|
@@ -1877,11 +2051,11 @@ const loadLocales = function (dir) {
|
|
|
1877
2051
|
filters: {},
|
|
1878
2052
|
};
|
|
1879
2053
|
|
|
1880
|
-
const locales = fs$
|
|
2054
|
+
const locales = fs$2.readdirSync(dir);
|
|
1881
2055
|
// eslint-disable-next-line no-restricted-syntax
|
|
1882
2056
|
for (const directory of locales) {
|
|
1883
2057
|
const localeDir = path$1.join(dir, directory);
|
|
1884
|
-
if (fs$
|
|
2058
|
+
if (fs$2.lstatSync(localeDir).isDirectory()) {
|
|
1885
2059
|
const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
|
|
1886
2060
|
if (groups) {
|
|
1887
2061
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -2106,8 +2280,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
|
|
|
2106
2280
|
|
|
2107
2281
|
const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
|
|
2108
2282
|
|
|
2109
|
-
fs$
|
|
2110
|
-
fs$
|
|
2283
|
+
fs$2.writeFileSync(filtersFileJson, filtersContent, 'utf8');
|
|
2284
|
+
fs$2.writeFileSync(filtersFileJs, filtersContent, 'utf8');
|
|
2111
2285
|
|
|
2112
2286
|
logger.info(`Writing filters localizations: ${config.path}`);
|
|
2113
2287
|
const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
|
|
@@ -2148,8 +2322,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
|
|
|
2148
2322
|
|
|
2149
2323
|
const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
|
|
2150
2324
|
|
|
2151
|
-
fs$
|
|
2152
|
-
fs$
|
|
2325
|
+
fs$2.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
|
|
2326
|
+
fs$2.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
|
|
2153
2327
|
}
|
|
2154
2328
|
|
|
2155
2329
|
logger.info('Writing filters metadata done');
|
|
@@ -2209,12 +2383,12 @@ const writeLocalScriptRules = function (platformsPath) {
|
|
|
2209
2383
|
// remove scriptlet rules in local_script_rules.json
|
|
2210
2384
|
rulesJson.rules = removeScriptletRules(rulesJson.rules);
|
|
2211
2385
|
|
|
2212
|
-
fs$
|
|
2386
|
+
fs$2.writeFileSync(
|
|
2213
2387
|
path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
|
|
2214
2388
|
rulesTxt.join(RULES_SEPARATOR),
|
|
2215
2389
|
'utf8',
|
|
2216
2390
|
);
|
|
2217
|
-
fs$
|
|
2391
|
+
fs$2.writeFileSync(
|
|
2218
2392
|
path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
|
|
2219
2393
|
JSON.stringify(rulesJson, null, 4),
|
|
2220
2394
|
'utf8',
|
|
@@ -2228,12 +2402,12 @@ const writeLocalScriptRules = function (platformsPath) {
|
|
|
2228
2402
|
* Loads and processes filter metadata from the specified directory.
|
|
2229
2403
|
*
|
|
2230
2404
|
* @param {string} filterDir - The directory containing the filter metadata and revision files.
|
|
2231
|
-
* @param {number[]} [
|
|
2232
|
-
* @param {number[]} [
|
|
2405
|
+
* @param {number[]} [includedFilterIds] - An optional array of filter IDs to include.
|
|
2406
|
+
* @param {number[]} [excludedFilterIds] - An optional array of filter IDs to exclude.
|
|
2233
2407
|
* @returns {Object} The processed filter metadata.
|
|
2234
2408
|
* @throws {Error} If the metadata or revision file cannot be read.
|
|
2235
2409
|
*/
|
|
2236
|
-
const loadFilterMetadata = function (filterDir,
|
|
2410
|
+
const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilterIds) {
|
|
2237
2411
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2238
2412
|
const metadataString = readFile$2(metadataFilePath);
|
|
2239
2413
|
if (!metadataString) {
|
|
@@ -2256,8 +2430,8 @@ const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
|
|
|
2256
2430
|
|
|
2257
2431
|
const { filterId } = result;
|
|
2258
2432
|
if (
|
|
2259
|
-
(
|
|
2260
|
-
|| (
|
|
2433
|
+
(includedFilterIds && includedFilterIds.includes(filterId))
|
|
2434
|
+
|| (excludedFilterIds && !excludedFilterIds.includes(filterId))
|
|
2261
2435
|
) {
|
|
2262
2436
|
checkFilterId(metadataFilterIdsPool, filterId);
|
|
2263
2437
|
}
|
|
@@ -2301,7 +2475,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
|
|
|
2301
2475
|
data = [adbHeader].concat(data);
|
|
2302
2476
|
}
|
|
2303
2477
|
|
|
2304
|
-
fs$
|
|
2478
|
+
fs$2.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
|
|
2305
2479
|
};
|
|
2306
2480
|
|
|
2307
2481
|
/**
|
|
@@ -2381,10 +2555,10 @@ const removeRuleDuplicates = function (list) {
|
|
|
2381
2555
|
*
|
|
2382
2556
|
* @param filterDir - Path to filter directory
|
|
2383
2557
|
* @param platformsPath - Path to platforms folder
|
|
2384
|
-
* @param
|
|
2385
|
-
* @param
|
|
2558
|
+
* @param includedFilterIds - Array of filter ids to include
|
|
2559
|
+
* @param excludedFilterIds - Array of filter ids to exclude
|
|
2386
2560
|
*/
|
|
2387
|
-
const buildFilter$1 = async (filterDir, platformsPath,
|
|
2561
|
+
const buildFilter$1 = async (filterDir, platformsPath, includedFilterIds, excludedFilterIds) => {
|
|
2388
2562
|
const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
|
|
2389
2563
|
|
|
2390
2564
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
@@ -2394,17 +2568,17 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
|
|
|
2394
2568
|
const { filterId } = metadata;
|
|
2395
2569
|
checkFilterId(filterIdsPool, filterId);
|
|
2396
2570
|
|
|
2397
|
-
if (
|
|
2398
|
-
logger.info(`Filter ${filterId} skipped
|
|
2571
|
+
if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
|
|
2572
|
+
logger.info(`Filter ${filterId} skipped due to '--include' option`);
|
|
2399
2573
|
return;
|
|
2400
2574
|
}
|
|
2401
2575
|
|
|
2402
|
-
if (
|
|
2403
|
-
logger.info(`Filter ${filterId} skipped
|
|
2576
|
+
if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
|
|
2577
|
+
logger.info(`Filter ${filterId} skipped due to '--skip' option`);
|
|
2404
2578
|
return;
|
|
2405
2579
|
}
|
|
2406
2580
|
|
|
2407
|
-
const optimizationConfig =
|
|
2581
|
+
const optimizationConfig = await getOptimizationStatistics(filterId);
|
|
2408
2582
|
|
|
2409
2583
|
// eslint-disable-next-line guard-for-in,no-restricted-syntax
|
|
2410
2584
|
for (const platform in platformPathsConfig) {
|
|
@@ -2498,30 +2672,30 @@ const isObsoleteFilter = (metadata) => metadata.tags && metadata.tags.some((tag)
|
|
|
2498
2672
|
* @param filtersDir
|
|
2499
2673
|
* @param filtersMetadata
|
|
2500
2674
|
* @param platformsPath
|
|
2501
|
-
* @param
|
|
2502
|
-
* @param
|
|
2675
|
+
* @param includedFilterIds
|
|
2676
|
+
* @param excludedFilterIds
|
|
2503
2677
|
* @param obsoleteFiltersMetadata
|
|
2504
2678
|
*/
|
|
2505
2679
|
const parseDirectory$1 = async (
|
|
2506
2680
|
filtersDir,
|
|
2507
2681
|
filtersMetadata,
|
|
2508
2682
|
platformsPath,
|
|
2509
|
-
|
|
2510
|
-
|
|
2683
|
+
includedFilterIds,
|
|
2684
|
+
excludedFilterIds,
|
|
2511
2685
|
obsoleteFiltersMetadata,
|
|
2512
2686
|
) => {
|
|
2513
|
-
const items = fs$
|
|
2687
|
+
const items = fs$2.readdirSync(filtersDir);
|
|
2514
2688
|
// eslint-disable-next-line no-restricted-syntax
|
|
2515
2689
|
for (const directory of items) {
|
|
2516
2690
|
const filterDir = path$1.join(filtersDir, directory);
|
|
2517
|
-
if (fs$
|
|
2691
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
2518
2692
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2519
|
-
if (fs$
|
|
2693
|
+
if (fs$2.existsSync(metadataFilePath)) {
|
|
2520
2694
|
logger.info(`Building filter platforms: ${directory}`);
|
|
2521
2695
|
// eslint-disable-next-line no-await-in-loop
|
|
2522
|
-
await buildFilter$1(filterDir, platformsPath,
|
|
2696
|
+
await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
2523
2697
|
logger.info(`Building filter platforms: ${directory} done`);
|
|
2524
|
-
const filterMetadata = loadFilterMetadata(filterDir,
|
|
2698
|
+
const filterMetadata = loadFilterMetadata(filterDir, includedFilterIds, excludedFilterIds);
|
|
2525
2699
|
filtersMetadata.push(filterMetadata);
|
|
2526
2700
|
if (isObsoleteFilter(filterMetadata)) {
|
|
2527
2701
|
obsoleteFiltersMetadata.push(filterMetadata);
|
|
@@ -2532,8 +2706,8 @@ const parseDirectory$1 = async (
|
|
|
2532
2706
|
filterDir,
|
|
2533
2707
|
filtersMetadata,
|
|
2534
2708
|
platformsPath,
|
|
2535
|
-
|
|
2536
|
-
|
|
2709
|
+
includedFilterIds,
|
|
2710
|
+
excludedFilterIds,
|
|
2537
2711
|
obsoleteFiltersMetadata,
|
|
2538
2712
|
);
|
|
2539
2713
|
}
|
|
@@ -2546,14 +2720,12 @@ const parseDirectory$1 = async (
|
|
|
2546
2720
|
*
|
|
2547
2721
|
* @async
|
|
2548
2722
|
* @param {string} filtersDir - The directory containing filter files to be processed.
|
|
2549
|
-
* @param {string} platformsPath - The
|
|
2550
|
-
* @param {Array<number
|
|
2551
|
-
* @param {Array<number
|
|
2723
|
+
* @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
|
|
2724
|
+
* @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include.
|
|
2725
|
+
* @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude.
|
|
2552
2726
|
* @returns {Promise<void>} Resolves when the generation process is complete.
|
|
2553
|
-
*
|
|
2554
|
-
* @throws {Error} If `platformsPath` or `platformPathsConfig` is not specified.
|
|
2555
2727
|
*/
|
|
2556
|
-
const generate = async (filtersDir, platformsPath,
|
|
2728
|
+
const generate = async (filtersDir, platformsPath, includedFilterIds, excludedFilterIds) => {
|
|
2557
2729
|
if (!platformsPath) {
|
|
2558
2730
|
logger.warn('Platforms build output path is not specified');
|
|
2559
2731
|
return;
|
|
@@ -2569,7 +2741,14 @@ const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
|
|
|
2569
2741
|
const filtersMetadata = [];
|
|
2570
2742
|
const obsoleteFiltersMetadata = [];
|
|
2571
2743
|
|
|
2572
|
-
await parseDirectory$1(
|
|
2744
|
+
await parseDirectory$1(
|
|
2745
|
+
filtersDir,
|
|
2746
|
+
filtersMetadata,
|
|
2747
|
+
platformsPath,
|
|
2748
|
+
includedFilterIds,
|
|
2749
|
+
excludedFilterIds,
|
|
2750
|
+
obsoleteFiltersMetadata,
|
|
2751
|
+
);
|
|
2573
2752
|
|
|
2574
2753
|
writeFiltersMetadata(platformsPath, filtersDir, filtersMetadata, obsoleteFiltersMetadata);
|
|
2575
2754
|
writeLocalScriptRules(platformsPath);
|
|
@@ -2635,7 +2814,7 @@ const skipFilter = (metadata) => {
|
|
|
2635
2814
|
*/
|
|
2636
2815
|
const create = (reportPath) => {
|
|
2637
2816
|
if (reportPath) {
|
|
2638
|
-
fs$
|
|
2817
|
+
fs$2.writeFileSync(reportPath, reportData, 'utf8');
|
|
2639
2818
|
return;
|
|
2640
2819
|
}
|
|
2641
2820
|
log(reportData);
|
|
@@ -2838,11 +3017,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
|
|
|
2838
3017
|
* @returns {*}
|
|
2839
3018
|
*/
|
|
2840
3019
|
const readFile$1 = function (path) {
|
|
2841
|
-
if (!fs$
|
|
3020
|
+
if (!fs$2.existsSync(path)) {
|
|
2842
3021
|
return null;
|
|
2843
3022
|
}
|
|
2844
3023
|
|
|
2845
|
-
return fs$
|
|
3024
|
+
return fs$2.readFileSync(path, { encoding: 'utf-8' });
|
|
2846
3025
|
};
|
|
2847
3026
|
|
|
2848
3027
|
/**
|
|
@@ -2852,7 +3031,7 @@ const readFile$1 = function (path) {
|
|
|
2852
3031
|
* @param data
|
|
2853
3032
|
*/
|
|
2854
3033
|
const writeFile = function (path, data) {
|
|
2855
|
-
fs$
|
|
3034
|
+
fs$2.writeFileSync(path, data, 'utf8');
|
|
2856
3035
|
};
|
|
2857
3036
|
|
|
2858
3037
|
/**
|
|
@@ -3149,7 +3328,7 @@ const include = async (filterDir, directiveLine, excluded) => {
|
|
|
3149
3328
|
const externalInclude = url.includes(':');
|
|
3150
3329
|
|
|
3151
3330
|
const included = externalInclude
|
|
3152
|
-
? downloadFile(url)
|
|
3331
|
+
? await downloadFile(url)
|
|
3153
3332
|
: readFile$1(path$1.join(filterDir, url));
|
|
3154
3333
|
|
|
3155
3334
|
if (included) {
|
|
@@ -3416,11 +3595,11 @@ const makeRevision = function (path, hash) {
|
|
|
3416
3595
|
* Builds filter txt file from directory contents
|
|
3417
3596
|
*
|
|
3418
3597
|
* @param {string} filterDir - The path to the directory containing filters.
|
|
3419
|
-
* @param {Array<number
|
|
3420
|
-
* @param {Array<number
|
|
3598
|
+
* @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
|
|
3599
|
+
* @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
|
|
3421
3600
|
* @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
|
|
3422
3601
|
*/
|
|
3423
|
-
const buildFilter = async function (filterDir,
|
|
3602
|
+
const buildFilter = async function (filterDir, includedFilterIds, excludedFilterIds) {
|
|
3424
3603
|
const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
|
|
3425
3604
|
if (!templateContent) {
|
|
3426
3605
|
throw new Error('Invalid template');
|
|
@@ -3437,12 +3616,12 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
|
|
|
3437
3616
|
return;
|
|
3438
3617
|
}
|
|
3439
3618
|
|
|
3440
|
-
if (
|
|
3619
|
+
if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
|
|
3441
3620
|
logger.info(`Filter ${filterId} skipped due to '--include' option`);
|
|
3442
3621
|
return;
|
|
3443
3622
|
}
|
|
3444
3623
|
|
|
3445
|
-
if (
|
|
3624
|
+
if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
|
|
3446
3625
|
logger.info(`Filter ${filterId} skipped due to '--skip' option`);
|
|
3447
3626
|
return;
|
|
3448
3627
|
}
|
|
@@ -3482,30 +3661,30 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
|
|
|
3482
3661
|
};
|
|
3483
3662
|
|
|
3484
3663
|
/**
|
|
3485
|
-
* Asynchronously parses a directory and processes filters based on the provided
|
|
3664
|
+
* Asynchronously parses a directory and processes filters based on the provided included/excluded filter IDs.
|
|
3486
3665
|
*
|
|
3487
3666
|
* @param {string} filtersDir - The path to the directory containing filters.
|
|
3488
|
-
* @param {Array<number
|
|
3489
|
-
* @param {Array<number
|
|
3667
|
+
* @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
|
|
3668
|
+
* @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
|
|
3490
3669
|
* @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
|
|
3491
3670
|
*/
|
|
3492
|
-
const parseDirectory = async function (filtersDir,
|
|
3493
|
-
const items = fs$
|
|
3671
|
+
const parseDirectory = async function (filtersDir, includedFilterIds, excludedFilterIds) {
|
|
3672
|
+
const items = fs$2.readdirSync(filtersDir)
|
|
3494
3673
|
.sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
|
|
3495
3674
|
|
|
3496
3675
|
// eslint-disable-next-line no-restricted-syntax
|
|
3497
3676
|
for (const directory of items) {
|
|
3498
3677
|
const filterDir = path$1.join(filtersDir, directory);
|
|
3499
|
-
if (fs$
|
|
3678
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
3500
3679
|
const template = path$1.join(filterDir, TEMPLATE_FILE);
|
|
3501
|
-
if (fs$
|
|
3680
|
+
if (fs$2.existsSync(template)) {
|
|
3502
3681
|
logger.info(`Building filter ${directory}...`);
|
|
3503
3682
|
// eslint-disable-next-line no-await-in-loop
|
|
3504
|
-
await buildFilter(filterDir,
|
|
3683
|
+
await buildFilter(filterDir, includedFilterIds, excludedFilterIds);
|
|
3505
3684
|
logger.info(`Filter ${directory} ok`);
|
|
3506
3685
|
} else {
|
|
3507
3686
|
// eslint-disable-next-line no-await-in-loop
|
|
3508
|
-
await parseDirectory(filterDir,
|
|
3687
|
+
await parseDirectory(filterDir, includedFilterIds, excludedFilterIds);
|
|
3509
3688
|
}
|
|
3510
3689
|
}
|
|
3511
3690
|
}
|
|
@@ -3519,10 +3698,10 @@ const parseDirectory = async function (filtersDir, whitelist, blacklist) {
|
|
|
3519
3698
|
* @param {string} filtersDir - The directory containing filter files to be processed.
|
|
3520
3699
|
* @param {string} logFile - The path to the log file where logs will be written.
|
|
3521
3700
|
* @param {string} reportFile - The path to the report file to be created.
|
|
3522
|
-
* @param {string} platformsPath - The path where platform data will be generated.
|
|
3701
|
+
* @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
|
|
3523
3702
|
* @param {Object} platformsConfig - The configuration object for platforms.
|
|
3524
|
-
* @param {Array<number
|
|
3525
|
-
* @param {Array<number
|
|
3703
|
+
* @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include in processing.
|
|
3704
|
+
* @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude from processing.
|
|
3526
3705
|
* @returns {Promise<void>} A promise that resolves when the build process is complete.
|
|
3527
3706
|
*/
|
|
3528
3707
|
const build = async (
|
|
@@ -3531,16 +3710,16 @@ const build = async (
|
|
|
3531
3710
|
reportFile,
|
|
3532
3711
|
platformsPath,
|
|
3533
3712
|
platformsConfig,
|
|
3534
|
-
|
|
3535
|
-
|
|
3713
|
+
includedFilterIds,
|
|
3714
|
+
excludedFilterIds,
|
|
3536
3715
|
) => {
|
|
3537
3716
|
logger.initialize(logFile);
|
|
3538
3717
|
init(FILTER_FILE, METADATA_FILE, REVISION_FILE, platformsConfig, ADGUARD_FILTERS_SERVER_URL);
|
|
3539
3718
|
|
|
3540
|
-
await parseDirectory(filtersDir,
|
|
3719
|
+
await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
|
|
3541
3720
|
|
|
3542
3721
|
logger.info('Generating platforms');
|
|
3543
|
-
await generate(filtersDir, platformsPath,
|
|
3722
|
+
await generate(filtersDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
3544
3723
|
logger.info('Generating platforms done');
|
|
3545
3724
|
create(reportFile);
|
|
3546
3725
|
};
|
|
@@ -3561,14 +3740,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
|
|
|
3561
3740
|
const loadSchemas = (dir) => {
|
|
3562
3741
|
const schemas = {};
|
|
3563
3742
|
|
|
3564
|
-
const items = fs$
|
|
3743
|
+
const items = fs$2.readdirSync(dir);
|
|
3565
3744
|
// eslint-disable-next-line no-restricted-syntax
|
|
3566
3745
|
for (const f of items) {
|
|
3567
3746
|
if (f.endsWith(SCHEMA_EXTENSION)) {
|
|
3568
3747
|
const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
|
|
3569
3748
|
|
|
3570
3749
|
logger.info(`Loading schema for ${validationFileName}`);
|
|
3571
|
-
schemas[validationFileName] = JSON.parse(fs$
|
|
3750
|
+
schemas[validationFileName] = JSON.parse(fs$2.readFileSync(path$1.join(dir, f)));
|
|
3572
3751
|
}
|
|
3573
3752
|
}
|
|
3574
3753
|
|
|
@@ -3588,7 +3767,7 @@ const loadSchemas = (dir) => {
|
|
|
3588
3767
|
const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
|
|
3589
3768
|
let items;
|
|
3590
3769
|
try {
|
|
3591
|
-
items = fs$
|
|
3770
|
+
items = fs$2.readdirSync(dir);
|
|
3592
3771
|
} catch (e) {
|
|
3593
3772
|
logger.info(e.message);
|
|
3594
3773
|
return false;
|
|
@@ -3596,7 +3775,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3596
3775
|
// eslint-disable-next-line no-restricted-syntax
|
|
3597
3776
|
for (const f of items) {
|
|
3598
3777
|
const item = path$1.join(dir, f);
|
|
3599
|
-
if (fs$
|
|
3778
|
+
if (fs$2.lstatSync(item).isDirectory()) {
|
|
3600
3779
|
if (!validateDir(item, validator, schemas, oldSchemas)) {
|
|
3601
3780
|
return false;
|
|
3602
3781
|
}
|
|
@@ -3619,7 +3798,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3619
3798
|
if (schema) {
|
|
3620
3799
|
logger.info(`Validating ${item}`);
|
|
3621
3800
|
|
|
3622
|
-
const json = JSON.parse(fs$
|
|
3801
|
+
const json = JSON.parse(fs$2.readFileSync(item));
|
|
3623
3802
|
|
|
3624
3803
|
// Validate filters amount
|
|
3625
3804
|
if (fileName === 'filters') {
|
|
@@ -3633,11 +3812,11 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3633
3812
|
const valid = validate(json);
|
|
3634
3813
|
|
|
3635
3814
|
// json can be updated with default values
|
|
3636
|
-
fs$
|
|
3815
|
+
fs$2.writeFileSync(item, JSON.stringify(json, null, '\t'));
|
|
3637
3816
|
|
|
3638
3817
|
// duplicate to .js file as well
|
|
3639
3818
|
const jsFileName = `${fileName}.js`;
|
|
3640
|
-
fs$
|
|
3819
|
+
fs$2.writeFileSync(
|
|
3641
3820
|
path$1.join(path$1.dirname(item), jsFileName),
|
|
3642
3821
|
JSON.stringify(json, null, '\t'),
|
|
3643
3822
|
);
|
|
@@ -3728,13 +3907,13 @@ const WARNING_TYPES = {
|
|
|
3728
3907
|
* Sync reads file content
|
|
3729
3908
|
* @param filePath - path to locales file
|
|
3730
3909
|
*/
|
|
3731
|
-
const readFile = (filePath) => fs$
|
|
3910
|
+
const readFile = (filePath) => fs$2.readFileSync(path$1.resolve(__dirname$2, filePath), 'utf8');
|
|
3732
3911
|
|
|
3733
3912
|
/**
|
|
3734
3913
|
* Sync reads directory content
|
|
3735
3914
|
* @param dirPath - path to directory
|
|
3736
3915
|
*/
|
|
3737
|
-
const readDir = (dirPath) => fs$
|
|
3916
|
+
const readDir = (dirPath) => fs$2.readdirSync(path$1.resolve(__dirname$2, dirPath), 'utf8');
|
|
3738
3917
|
|
|
3739
3918
|
/**
|
|
3740
3919
|
* Validates messages keys
|
|
@@ -4832,7 +5011,15 @@ process.on('unhandledRejection', (error) => {
|
|
|
4832
5011
|
throw error;
|
|
4833
5012
|
});
|
|
4834
5013
|
|
|
4835
|
-
const compile = (
|
|
5014
|
+
const compile = (
|
|
5015
|
+
path,
|
|
5016
|
+
logPath,
|
|
5017
|
+
reportFile,
|
|
5018
|
+
platformsPath,
|
|
5019
|
+
includedFilterIds,
|
|
5020
|
+
excludedFilterIds,
|
|
5021
|
+
customPlatformsConfig,
|
|
5022
|
+
) => {
|
|
4836
5023
|
if (customPlatformsConfig) {
|
|
4837
5024
|
logger.info('Using custom platforms configuration');
|
|
4838
5025
|
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
@@ -4848,8 +5035,8 @@ const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist,
|
|
|
4848
5035
|
reportFile,
|
|
4849
5036
|
platformsPath,
|
|
4850
5037
|
platformsConfig,
|
|
4851
|
-
|
|
4852
|
-
|
|
5038
|
+
includedFilterIds,
|
|
5039
|
+
excludedFilterIds,
|
|
4853
5040
|
);
|
|
4854
5041
|
};
|
|
4855
5042
|
|
|
@@ -4861,6 +5048,8 @@ const validateLocales = (localesDirPath, requiredLocales) => {
|
|
|
4861
5048
|
return localesValidator.validate(localesDirPath, requiredLocales);
|
|
4862
5049
|
};
|
|
4863
5050
|
|
|
5051
|
+
exports.OptimizationStatsError = OptimizationStatsError;
|
|
4864
5052
|
exports.compile = compile;
|
|
5053
|
+
exports.localOptimizationStatistics = localOptimizationStatistics;
|
|
4865
5054
|
exports.validateJSONSchema = validateJSONSchema;
|
|
4866
5055
|
exports.validateLocales = validateLocales;
|