@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.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path$1 from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { RuleFactory, CosmeticRule, NetworkRule, setConfiguration, CompatibilityTypes } from '@adguard/tsurlfilter';
|
|
4
|
-
import fs$
|
|
4
|
+
import fs$2 from 'fs';
|
|
5
5
|
import md5 from 'md5';
|
|
6
6
|
import { FiltersDownloader } from '@adguard/filters-downloader';
|
|
7
7
|
import { ADG_SCRIPTLET_MASK, CosmeticRuleType, AdblockSyntax, RuleParser, RuleConverter, RuleGenerator, CommentParser, RuleCategory, RegExpUtils } from '@adguard/agtree';
|
|
@@ -16,6 +16,7 @@ import { createRequire } from 'module';
|
|
|
16
16
|
import { JSDOM } from 'jsdom';
|
|
17
17
|
import crypto from 'crypto';
|
|
18
18
|
import moment from 'moment';
|
|
19
|
+
import fs$1 from 'fs/promises';
|
|
19
20
|
import { parse as parse$1 } from 'tldts';
|
|
20
21
|
import Ajv from 'ajv';
|
|
21
22
|
|
|
@@ -69,53 +70,43 @@ class CompilerLogger extends Logger {
|
|
|
69
70
|
/**
|
|
70
71
|
* File descriptor
|
|
71
72
|
*
|
|
72
|
-
* @type {number | null}
|
|
73
|
-
*
|
|
74
73
|
* @private
|
|
75
74
|
*/
|
|
76
75
|
#fd = null;
|
|
77
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Log file path, set after successful initialization.
|
|
78
|
+
*/
|
|
79
|
+
logFile;
|
|
78
80
|
/**
|
|
79
81
|
* Helper to append message to log file
|
|
80
|
-
*
|
|
81
|
-
* @param {string} message
|
|
82
|
-
* @param {'INFO'|'WARN'|'ERROR'} level
|
|
83
|
-
*
|
|
84
|
-
* @private
|
|
85
82
|
*/
|
|
86
83
|
#append(message, level) {
|
|
87
84
|
if (this.#fd == null) {
|
|
88
85
|
return;
|
|
89
86
|
}
|
|
90
|
-
|
|
91
87
|
const line = `[${new Date().toLocaleTimeString()}] [${level}]: ${message}${os.EOL}`;
|
|
92
|
-
|
|
93
88
|
// Using appendFileSync with an fd ensures atomic append semantics.
|
|
94
89
|
fs.appendFileSync(this.#fd, line, 'utf8');
|
|
95
90
|
}
|
|
96
|
-
|
|
97
91
|
/** @inheritdoc */
|
|
98
92
|
info(message) {
|
|
99
93
|
super.info(message);
|
|
100
94
|
this.#append(message, 'INFO');
|
|
101
95
|
}
|
|
102
|
-
|
|
103
96
|
/** @inheritdoc */
|
|
104
97
|
error(message) {
|
|
105
98
|
super.error(message);
|
|
106
99
|
this.#append(message, 'ERROR');
|
|
107
100
|
}
|
|
108
|
-
|
|
109
101
|
/** @inheritdoc */
|
|
110
102
|
warn(message) {
|
|
111
103
|
super.warn(message);
|
|
112
104
|
this.#append(message, 'WARN');
|
|
113
105
|
}
|
|
114
|
-
|
|
115
106
|
/**
|
|
116
107
|
* Initializes logger
|
|
117
108
|
*
|
|
118
|
-
* @param
|
|
109
|
+
* @param logFilePath - log file path
|
|
119
110
|
*
|
|
120
111
|
* The log file is opened with 'w' (truncate/create). Subsequent writes are appended.
|
|
121
112
|
*/
|
|
@@ -125,26 +116,23 @@ class CompilerLogger extends Logger {
|
|
|
125
116
|
console.warn('Log file is not specified');
|
|
126
117
|
return;
|
|
127
118
|
}
|
|
128
|
-
|
|
129
119
|
// Ensure the directory exists before creating the log file
|
|
130
120
|
const dir = path.dirname(logFilePath);
|
|
131
121
|
fs.mkdirSync(dir, { recursive: true });
|
|
132
|
-
|
|
133
122
|
// Close any previous descriptor to avoid leaks
|
|
134
123
|
if (this.#fd != null) {
|
|
135
124
|
try {
|
|
136
125
|
fs.closeSync(this.#fd);
|
|
137
|
-
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
138
128
|
/* noop */
|
|
139
129
|
}
|
|
140
130
|
this.#fd = null;
|
|
141
131
|
}
|
|
142
|
-
|
|
143
|
-
// Open (truncate) now; we’ll append to the same fd later.
|
|
132
|
+
// Open (truncate) now; we'll append to the same fd later.
|
|
144
133
|
this.#fd = fs.openSync(logFilePath, 'w');
|
|
145
134
|
this.logFile = logFilePath;
|
|
146
135
|
}
|
|
147
|
-
|
|
148
136
|
/**
|
|
149
137
|
* Optional: call to close the file descriptor when done (e.g., on shutdown)
|
|
150
138
|
*/
|
|
@@ -152,13 +140,13 @@ class CompilerLogger extends Logger {
|
|
|
152
140
|
if (this.#fd != null) {
|
|
153
141
|
try {
|
|
154
142
|
fs.closeSync(this.#fd);
|
|
155
|
-
}
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
156
145
|
this.#fd = null;
|
|
157
146
|
}
|
|
158
147
|
}
|
|
159
148
|
}
|
|
160
149
|
}
|
|
161
|
-
|
|
162
150
|
const logger = new CompilerLogger();
|
|
163
151
|
|
|
164
152
|
// TODO: a lot of these masks can be imported from @adguard/agtree
|
|
@@ -1009,27 +997,24 @@ const checkAffinityDirectives = (lines) => {
|
|
|
1009
997
|
};
|
|
1010
998
|
|
|
1011
999
|
/* eslint-disable global-require */
|
|
1012
|
-
|
|
1013
1000
|
const require = createRequire(import.meta.url);
|
|
1014
|
-
|
|
1015
1001
|
/**
|
|
1016
1002
|
* Some sources require proper user-agents and forbid downloading without.
|
|
1017
1003
|
*/
|
|
1018
1004
|
const USER_AGENT = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko)'
|
|
1019
1005
|
+ 'Chrome/63.0.3239.132 Mobile Safari/537.36';
|
|
1020
|
-
|
|
1021
1006
|
/**
|
|
1022
|
-
*
|
|
1007
|
+
* Downloads file from url
|
|
1023
1008
|
*
|
|
1024
1009
|
* @param url
|
|
1025
|
-
* @param
|
|
1026
|
-
* @returns
|
|
1010
|
+
* @param retryNum number of times to retry downloading, defaults to 0
|
|
1011
|
+
* @returns raw content of the file
|
|
1027
1012
|
*/
|
|
1028
|
-
const tryDownloadFile = function (url, retryNum = 0) {
|
|
1013
|
+
const tryDownloadFile = async function (url, retryNum = 0) {
|
|
1029
1014
|
let args = ['--fail', '--silent', '--user-agent', USER_AGENT, '-L', url];
|
|
1030
1015
|
if (retryNum) {
|
|
1031
1016
|
args.push('--retry');
|
|
1032
|
-
args.push(retryNum);
|
|
1017
|
+
args.push(String(retryNum));
|
|
1033
1018
|
}
|
|
1034
1019
|
const options = { encoding: 'utf8', maxBuffer: Infinity };
|
|
1035
1020
|
const tlsCheck = process.env.TLS;
|
|
@@ -1039,97 +1024,279 @@ const tryDownloadFile = function (url, retryNum = 0) {
|
|
|
1039
1024
|
return require('child_process')
|
|
1040
1025
|
.execFileSync('curl', args, options);
|
|
1041
1026
|
};
|
|
1042
|
-
|
|
1043
1027
|
/**
|
|
1044
|
-
*
|
|
1028
|
+
* Number of times to retry downloading after the first failed attempt for `downloadFile` function.
|
|
1029
|
+
*/
|
|
1030
|
+
const RETRY_NUM = 5;
|
|
1031
|
+
/**
|
|
1032
|
+
* Downloads file from url with two attempts
|
|
1045
1033
|
*
|
|
1046
1034
|
* @param url
|
|
1047
|
-
* @returns
|
|
1035
|
+
* @returns raw content of the file
|
|
1048
1036
|
*/
|
|
1049
|
-
const downloadFile = (url) => {
|
|
1037
|
+
const downloadFile = async (url) => {
|
|
1050
1038
|
logger.info(`Downloading: ${url}`);
|
|
1051
|
-
|
|
1052
1039
|
// 5 times to retry after first fail attempt:
|
|
1053
1040
|
// 1 sec for first time, double for every forthcoming attempts
|
|
1054
1041
|
// so it will take: 1 + 2 + 4 + 8 + 16 = 31 seconds
|
|
1055
1042
|
// https://curl.se/docs/manpage.html#--retry
|
|
1056
|
-
const RETRY_NUM = 5;
|
|
1057
|
-
|
|
1058
1043
|
try {
|
|
1059
|
-
return tryDownloadFile(url);
|
|
1060
|
-
}
|
|
1044
|
+
return await tryDownloadFile(url);
|
|
1045
|
+
}
|
|
1046
|
+
catch (e) {
|
|
1061
1047
|
logger.warn(e);
|
|
1062
1048
|
logger.warn(`Retry downloading: ${url}`);
|
|
1063
1049
|
return tryDownloadFile(url, RETRY_NUM);
|
|
1064
1050
|
}
|
|
1065
1051
|
};
|
|
1066
1052
|
|
|
1067
|
-
/* eslint-disable global-require */
|
|
1068
|
-
|
|
1069
|
-
// Here we can access optimizable filters and its optimization percentages
|
|
1070
|
-
// eslint-disable-next-line max-len
|
|
1071
|
-
const OPTIMIZATION_PERCENT_URL = 'https://chrome.adtidy.org/optimization_config/percent.json?key=4DDBE80A3DA94D819A00523252FB6380';
|
|
1072
|
-
// eslint-disable-next-line max-len
|
|
1073
|
-
const OPTIMIZATION_STATS_URL = 'https://chrome.adtidy.org/filters/{0}/stats.json?key=4DDBE80A3DA94D819A00523252FB6380';
|
|
1074
|
-
|
|
1075
|
-
let filtersOptimizationPercent = null;
|
|
1076
|
-
|
|
1077
1053
|
/**
|
|
1078
|
-
*
|
|
1054
|
+
* Runs `fn` over `items` with at most `concurrency` calls in flight at once.
|
|
1055
|
+
*
|
|
1056
|
+
* @param items - Items to process.
|
|
1057
|
+
* @param max - Max number of concurrent `fn` calls.
|
|
1058
|
+
* @param fn - Async worker invoked for each item.
|
|
1079
1059
|
*/
|
|
1080
|
-
const
|
|
1060
|
+
const mapWithConcurrency = async (items, max, fn) => {
|
|
1061
|
+
const workerCount = Math.min(max, items.length);
|
|
1062
|
+
// Each worker owns a fixed, disjoint slice of indices (workerIndex, workerIndex + workerCount, ...)
|
|
1063
|
+
// decided up front, so there's no shared mutable state for workers to race over.
|
|
1064
|
+
const worker = async (startIndex) => {
|
|
1065
|
+
for (let i = startIndex; i < items.length; i += workerCount) {
|
|
1066
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1067
|
+
await fn(items[i]);
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
await Promise.all(Array.from({ length: workerCount }, (_, startIndex) => worker(startIndex)));
|
|
1071
|
+
};
|
|
1081
1072
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1073
|
+
/**
|
|
1074
|
+
* Optimizes filters by removing low-hit rules using each filter's hit-counts
|
|
1075
|
+
* collected from AdGuard users who opted into filter rules statistics.
|
|
1076
|
+
* Statistics are fetched remotely or read from a local cache.
|
|
1077
|
+
*
|
|
1078
|
+
* @see {@link https://github.com/AdguardTeam/FiltersRegistry#optimization} for more information.
|
|
1079
|
+
*
|
|
1080
|
+
* @see localOptimizationStatistics for managing a local cache of optimization stats files.
|
|
1081
|
+
* @see getOptimizationStatistics for retrieving optimization stats for a filter.
|
|
1082
|
+
* @see skipRuleWithOptimization for checking if a rule should be skipped based on optimization stats.
|
|
1083
|
+
*/
|
|
1084
|
+
// Here we can access optimizable filters and its optimization percentages
|
|
1085
|
+
const OPTIMIZATION_KEY = '4DDBE80A3DA94D819A00523252FB6380';
|
|
1086
|
+
const OPTIMIZATION_PERCENT_URL = `https://chrome.adtidy.org/optimization_config/percent.json?key=${OPTIMIZATION_KEY}`;
|
|
1087
|
+
// Path segment constants for the local config directory layout:
|
|
1088
|
+
// <basePath>/filters/<filterId>/stats.json
|
|
1089
|
+
const STATS_JSON = 'stats.json';
|
|
1090
|
+
const FILTERS_DIR_NAME = 'filters';
|
|
1091
|
+
/**
|
|
1092
|
+
* Thrown by `getOptimizationStatistics` when a filter's stats cannot be
|
|
1093
|
+
* retrieved, from either a local file or the remote server.
|
|
1094
|
+
*
|
|
1095
|
+
* Carries `filterId` and `sourcePath` as structured fields so callers can
|
|
1096
|
+
* build their own actionable message instead of matching on `error.message`.
|
|
1097
|
+
*/
|
|
1098
|
+
class OptimizationStatsError extends Error {
|
|
1099
|
+
filterId;
|
|
1100
|
+
sourcePath;
|
|
1101
|
+
code = 'OPTIMIZATION_STATS_UNAVAILABLE';
|
|
1102
|
+
constructor(filterId, sourcePath, options) {
|
|
1103
|
+
super(`Unable to retrieve optimization stats for ${filterId}, at ${sourcePath}. `
|
|
1104
|
+
+ 'Please ensure the stats file exists and is accessible.', options);
|
|
1105
|
+
this.filterId = filterId;
|
|
1106
|
+
this.sourcePath = sourcePath;
|
|
1107
|
+
this.name = 'OptimizationStatsError';
|
|
1084
1108
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1109
|
+
}
|
|
1110
|
+
const downloadOptimizationPercent = async () => downloadFile(OPTIMIZATION_PERCENT_URL);
|
|
1111
|
+
const getOptimizationStatsUrl = (filterId) => `https://chrome.adtidy.org/filters/${filterId}/stats.json?key=${OPTIMIZATION_KEY}`;
|
|
1112
|
+
/**
|
|
1113
|
+
* Downloads optimization stats for a single filter from the remote server.
|
|
1114
|
+
*
|
|
1115
|
+
* @param filterId - Numeric filter identifier.
|
|
1116
|
+
* @returns Raw JSON string of the stats file.
|
|
1117
|
+
*/
|
|
1118
|
+
const downloadOptimizationStats = async (filterId) => {
|
|
1119
|
+
const optimizationStatsUrl = getOptimizationStatsUrl(filterId);
|
|
1120
|
+
return downloadFile(optimizationStatsUrl);
|
|
1121
|
+
};
|
|
1122
|
+
/**
|
|
1123
|
+
* When set, `getOptimizationStatistics` reads stats from local files under this
|
|
1124
|
+
* directory instead of fetching from the remote server.
|
|
1125
|
+
*/
|
|
1126
|
+
let localStatsPath = null;
|
|
1127
|
+
/**
|
|
1128
|
+
* Cached set of filter IDs that have optimization stats available.
|
|
1129
|
+
* Populated lazily on the first `getOptimizableFilterIds` call.
|
|
1130
|
+
* Shared across concurrent callers to avoid redundant percent.json fetches.
|
|
1131
|
+
*/
|
|
1132
|
+
let optimizableFilterIdsPromise = null;
|
|
1133
|
+
/**
|
|
1134
|
+
* Returns the set of optimizable filter IDs.
|
|
1135
|
+
*
|
|
1136
|
+
* Lazily initializes a shared singleton on first call so that concurrent
|
|
1137
|
+
* callers share one in-flight fetch of `percent.json`.
|
|
1138
|
+
* `percent.json` is always fetched remotely, even when `use` has
|
|
1139
|
+
* been called — only the per-filter stats content is read from local files.
|
|
1140
|
+
*
|
|
1141
|
+
* @returns Set of filter IDs that have optimization stats available.
|
|
1142
|
+
*/
|
|
1143
|
+
const getOptimizableFilterIds = async () => {
|
|
1144
|
+
if (optimizableFilterIdsPromise === null) {
|
|
1145
|
+
optimizableFilterIdsPromise = (async () => {
|
|
1146
|
+
const percent = JSON.parse(await downloadOptimizationPercent());
|
|
1147
|
+
return new Set(percent.config.map(({ filterId: id }) => id));
|
|
1148
|
+
})();
|
|
1149
|
+
// Clear the singleton on rejection so a transient network error
|
|
1150
|
+
// doesn't poison the cache for all subsequent callers.
|
|
1151
|
+
optimizableFilterIdsPromise.catch(() => {
|
|
1152
|
+
optimizableFilterIdsPromise = null;
|
|
1153
|
+
});
|
|
1089
1154
|
}
|
|
1090
|
-
|
|
1091
|
-
return filtersOptimizationPercent;
|
|
1155
|
+
return optimizableFilterIdsPromise;
|
|
1092
1156
|
};
|
|
1093
|
-
|
|
1094
1157
|
/**
|
|
1095
|
-
*
|
|
1158
|
+
* Validates that stats have non-empty groups.
|
|
1159
|
+
*
|
|
1160
|
+
* @param filterId - Numeric filter identifier.
|
|
1161
|
+
* @param stats - Parsed optimization stats object.
|
|
1162
|
+
* @throws {Error} if stats is not an object, or if stats.groups is missing or empty.
|
|
1096
1163
|
*/
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1164
|
+
function assertValidStats(filterId, stats) {
|
|
1165
|
+
if (stats === null || typeof stats !== 'object') {
|
|
1166
|
+
throw new Error(`Invalid optimization stats for ${filterId}: expected an object`);
|
|
1167
|
+
}
|
|
1168
|
+
if (!('groups' in stats) || !Array.isArray(stats.groups) || stats.groups.length === 0) {
|
|
1169
|
+
throw new Error(`Invalid optimization stats for ${filterId}: missing or empty groups`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Manages a local on-disk cache of optimization stats files.
|
|
1174
|
+
*
|
|
1175
|
+
* Typical usage for generating the cache:
|
|
1176
|
+
* 1. `download(basePath, includedFilterIds, excludedFilterIds)` — save
|
|
1177
|
+
* `stats.json` for filters listed in the remote `percent.json`.
|
|
1178
|
+
*
|
|
1179
|
+
* Typical usage for using the cache:
|
|
1180
|
+
* 1. `use(basePath)` — tells `getOptimizationStatistics` to read stats
|
|
1181
|
+
* from local files lazily during compilation instead of fetching remotely.
|
|
1182
|
+
* 2. `reset(basePath)` — remove the cache directory and clear in-memory state.
|
|
1183
|
+
*/
|
|
1184
|
+
const localOptimizationStatistics = {
|
|
1185
|
+
/**
|
|
1186
|
+
* Downloads `stats.json` files for filters listed in the remote
|
|
1187
|
+
* `percent.json` and saves them to disk.
|
|
1188
|
+
* Existing `stats.json` files will be overwritten.
|
|
1189
|
+
*
|
|
1190
|
+
* `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
|
|
1191
|
+
*
|
|
1192
|
+
* @param basePath - Directory to save `filters/<filterId>/stats.json` into.
|
|
1193
|
+
* @param includedFilterIds - Filter IDs to process; empty (default) processes all.
|
|
1194
|
+
* @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
|
|
1195
|
+
* @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
|
|
1196
|
+
*/
|
|
1197
|
+
download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
|
|
1198
|
+
if (includedFilterIds.length > 0 && excludedFilterIds.length > 0) {
|
|
1199
|
+
throw new Error('includedFilterIds and excludedFilterIds cannot both be non-empty');
|
|
1108
1200
|
}
|
|
1201
|
+
const percent = JSON.parse(await downloadOptimizationPercent());
|
|
1202
|
+
const configs = percent.config.filter(({ filterId }) => {
|
|
1203
|
+
if (includedFilterIds.length > 0) {
|
|
1204
|
+
return includedFilterIds.includes(filterId);
|
|
1205
|
+
}
|
|
1206
|
+
if (excludedFilterIds.length > 0) {
|
|
1207
|
+
return !excludedFilterIds.includes(filterId);
|
|
1208
|
+
}
|
|
1209
|
+
return true;
|
|
1210
|
+
});
|
|
1211
|
+
const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
|
|
1212
|
+
/**
|
|
1213
|
+
* Bounds concurrency so a large `percent.json` cannot fan out into unbounded
|
|
1214
|
+
* parallel requests if the underlying transport ever becomes truly async.
|
|
1215
|
+
*/
|
|
1216
|
+
const DOWNLOAD_CONCURRENCY = 8;
|
|
1217
|
+
await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
|
|
1218
|
+
const dir = path$1.join(FILTERS_PATH, String(filterId));
|
|
1219
|
+
const statsPath = path$1.join(dir, STATS_JSON);
|
|
1220
|
+
const content = await downloadOptimizationStats(filterId);
|
|
1221
|
+
await fs$1.mkdir(dir, { recursive: true });
|
|
1222
|
+
await fs$1.writeFile(statsPath, content, 'utf-8');
|
|
1223
|
+
});
|
|
1224
|
+
},
|
|
1225
|
+
/**
|
|
1226
|
+
* Configures `getOptimizationStatistics` to read stats from local files under
|
|
1227
|
+
* `basePath` instead of fetching from the remote server.
|
|
1228
|
+
* Stats are loaded lazily on demand during compilation. `percent.json` is
|
|
1229
|
+
* still fetched remotely to determine which filters are optimizable.
|
|
1230
|
+
*
|
|
1231
|
+
* @param basePath - Directory containing `filters/<filterId>/stats.json`.
|
|
1232
|
+
*/
|
|
1233
|
+
use(basePath) {
|
|
1234
|
+
localStatsPath = basePath;
|
|
1235
|
+
optimizableFilterIdsPromise = null;
|
|
1236
|
+
},
|
|
1237
|
+
/**
|
|
1238
|
+
* Removes the cache directory and clears in-memory state.
|
|
1239
|
+
*
|
|
1240
|
+
* @param basePath - Directory to remove.
|
|
1241
|
+
*/
|
|
1242
|
+
async reset(basePath) {
|
|
1243
|
+
await fs$1.rm(basePath, { recursive: true, force: true });
|
|
1244
|
+
localStatsPath = null;
|
|
1245
|
+
optimizableFilterIdsPromise = null;
|
|
1246
|
+
},
|
|
1247
|
+
};
|
|
1248
|
+
/**
|
|
1249
|
+
* Returns the optimization stats for the given filter, or `null` when
|
|
1250
|
+
* optimization is disabled or the filter is not listed in `percent.json`.
|
|
1251
|
+
*
|
|
1252
|
+
* When `localOptimizationStatistics.use(path)` has been called, stats
|
|
1253
|
+
* are read lazily from local files. Otherwise stats are fetched from the
|
|
1254
|
+
* remote server.
|
|
1255
|
+
*
|
|
1256
|
+
* @param filterId - Numeric filter identifier.
|
|
1257
|
+
* @returns Parsed stats object, or `null` when the filter has no optimization stats.
|
|
1258
|
+
* @throws {Error} When the stats are missing or malformed.
|
|
1259
|
+
*/
|
|
1260
|
+
const getOptimizationStatistics = async (filterId) => {
|
|
1261
|
+
const ids = await getOptimizableFilterIds();
|
|
1262
|
+
if (!ids.has(filterId)) {
|
|
1263
|
+
return null;
|
|
1109
1264
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1265
|
+
let stats;
|
|
1266
|
+
try {
|
|
1267
|
+
const content = localStatsPath !== null
|
|
1268
|
+
? await fs$1.readFile(path$1.join(localStatsPath, FILTERS_DIR_NAME, String(filterId), STATS_JSON), 'utf-8')
|
|
1269
|
+
: await downloadOptimizationStats(filterId);
|
|
1270
|
+
stats = JSON.parse(content);
|
|
1271
|
+
}
|
|
1272
|
+
catch (originalError) {
|
|
1273
|
+
const statsPath = localStatsPath === null
|
|
1274
|
+
? getOptimizationStatsUrl(filterId)
|
|
1275
|
+
: `${localStatsPath}/filters/${filterId}/stats.json`;
|
|
1276
|
+
throw new OptimizationStatsError(filterId, statsPath, { cause: originalError });
|
|
1277
|
+
}
|
|
1278
|
+
assertValidStats(filterId, stats);
|
|
1279
|
+
return stats;
|
|
1112
1280
|
};
|
|
1113
|
-
|
|
1114
1281
|
/**
|
|
1115
|
-
* Checks if rule should be skipped
|
|
1116
|
-
* and
|
|
1117
|
-
*
|
|
1118
|
-
* @param
|
|
1282
|
+
* Checks if rule should be skipped because optimization is enabled for this filter
|
|
1283
|
+
* and the hit count for this rule is below the configured threshold.
|
|
1284
|
+
*
|
|
1285
|
+
* @param ruleText - Rule text to check.
|
|
1286
|
+
* @param optimizationStats - Optimization config for this filter.
|
|
1287
|
+
* @returns `true` if the rule should be skipped, `false` otherwise.
|
|
1119
1288
|
*/
|
|
1120
|
-
const skipRuleWithOptimization = (ruleText,
|
|
1121
|
-
if (!
|
|
1289
|
+
const skipRuleWithOptimization = (ruleText, optimizationStats) => {
|
|
1290
|
+
if (!optimizationStats) {
|
|
1122
1291
|
return false;
|
|
1123
1292
|
}
|
|
1124
|
-
|
|
1125
1293
|
// eslint-disable-next-line no-restricted-syntax
|
|
1126
|
-
for (const group of
|
|
1294
|
+
for (const group of optimizationStats.groups) {
|
|
1127
1295
|
const hits = group.rules[ruleText];
|
|
1128
1296
|
if (hits !== undefined && hits < group.config.hits) {
|
|
1129
1297
|
return true;
|
|
1130
1298
|
}
|
|
1131
1299
|
}
|
|
1132
|
-
|
|
1133
1300
|
return false;
|
|
1134
1301
|
};
|
|
1135
1302
|
|
|
@@ -1490,7 +1657,7 @@ let adguardFiltersServerUrl = null;
|
|
|
1490
1657
|
*/
|
|
1491
1658
|
const readFile$2 = function (path) {
|
|
1492
1659
|
try {
|
|
1493
|
-
return fs$
|
|
1660
|
+
return fs$2.readFileSync(path, { encoding: 'utf-8' });
|
|
1494
1661
|
} catch (e) {
|
|
1495
1662
|
return null;
|
|
1496
1663
|
}
|
|
@@ -1603,7 +1770,7 @@ const createDir = (dir) => {
|
|
|
1603
1770
|
return dir.split(sep).reduce((parentDir, childDir) => {
|
|
1604
1771
|
const curDir = path$1.resolve(baseDir, parentDir, childDir);
|
|
1605
1772
|
try {
|
|
1606
|
-
fs$
|
|
1773
|
+
fs$2.mkdirSync(curDir);
|
|
1607
1774
|
} catch (err) {
|
|
1608
1775
|
if (err.code === 'EEXIST') { // curDir already exists!
|
|
1609
1776
|
return curDir;
|
|
@@ -1874,11 +2041,11 @@ const loadLocales = function (dir) {
|
|
|
1874
2041
|
filters: {},
|
|
1875
2042
|
};
|
|
1876
2043
|
|
|
1877
|
-
const locales = fs$
|
|
2044
|
+
const locales = fs$2.readdirSync(dir);
|
|
1878
2045
|
// eslint-disable-next-line no-restricted-syntax
|
|
1879
2046
|
for (const directory of locales) {
|
|
1880
2047
|
const localeDir = path$1.join(dir, directory);
|
|
1881
|
-
if (fs$
|
|
2048
|
+
if (fs$2.lstatSync(localeDir).isDirectory()) {
|
|
1882
2049
|
const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
|
|
1883
2050
|
if (groups) {
|
|
1884
2051
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -2103,8 +2270,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
|
|
|
2103
2270
|
|
|
2104
2271
|
const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
|
|
2105
2272
|
|
|
2106
|
-
fs$
|
|
2107
|
-
fs$
|
|
2273
|
+
fs$2.writeFileSync(filtersFileJson, filtersContent, 'utf8');
|
|
2274
|
+
fs$2.writeFileSync(filtersFileJs, filtersContent, 'utf8');
|
|
2108
2275
|
|
|
2109
2276
|
logger.info(`Writing filters localizations: ${config.path}`);
|
|
2110
2277
|
const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
|
|
@@ -2145,8 +2312,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
|
|
|
2145
2312
|
|
|
2146
2313
|
const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
|
|
2147
2314
|
|
|
2148
|
-
fs$
|
|
2149
|
-
fs$
|
|
2315
|
+
fs$2.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
|
|
2316
|
+
fs$2.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
|
|
2150
2317
|
}
|
|
2151
2318
|
|
|
2152
2319
|
logger.info('Writing filters metadata done');
|
|
@@ -2206,12 +2373,12 @@ const writeLocalScriptRules = function (platformsPath) {
|
|
|
2206
2373
|
// remove scriptlet rules in local_script_rules.json
|
|
2207
2374
|
rulesJson.rules = removeScriptletRules(rulesJson.rules);
|
|
2208
2375
|
|
|
2209
|
-
fs$
|
|
2376
|
+
fs$2.writeFileSync(
|
|
2210
2377
|
path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
|
|
2211
2378
|
rulesTxt.join(RULES_SEPARATOR),
|
|
2212
2379
|
'utf8',
|
|
2213
2380
|
);
|
|
2214
|
-
fs$
|
|
2381
|
+
fs$2.writeFileSync(
|
|
2215
2382
|
path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
|
|
2216
2383
|
JSON.stringify(rulesJson, null, 4),
|
|
2217
2384
|
'utf8',
|
|
@@ -2225,12 +2392,12 @@ const writeLocalScriptRules = function (platformsPath) {
|
|
|
2225
2392
|
* Loads and processes filter metadata from the specified directory.
|
|
2226
2393
|
*
|
|
2227
2394
|
* @param {string} filterDir - The directory containing the filter metadata and revision files.
|
|
2228
|
-
* @param {number[]} [
|
|
2229
|
-
* @param {number[]} [
|
|
2395
|
+
* @param {number[]} [includedFilterIds] - An optional array of filter IDs to include.
|
|
2396
|
+
* @param {number[]} [excludedFilterIds] - An optional array of filter IDs to exclude.
|
|
2230
2397
|
* @returns {Object} The processed filter metadata.
|
|
2231
2398
|
* @throws {Error} If the metadata or revision file cannot be read.
|
|
2232
2399
|
*/
|
|
2233
|
-
const loadFilterMetadata = function (filterDir,
|
|
2400
|
+
const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilterIds) {
|
|
2234
2401
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2235
2402
|
const metadataString = readFile$2(metadataFilePath);
|
|
2236
2403
|
if (!metadataString) {
|
|
@@ -2253,8 +2420,8 @@ const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
|
|
|
2253
2420
|
|
|
2254
2421
|
const { filterId } = result;
|
|
2255
2422
|
if (
|
|
2256
|
-
(
|
|
2257
|
-
|| (
|
|
2423
|
+
(includedFilterIds && includedFilterIds.includes(filterId))
|
|
2424
|
+
|| (excludedFilterIds && !excludedFilterIds.includes(filterId))
|
|
2258
2425
|
) {
|
|
2259
2426
|
checkFilterId(metadataFilterIdsPool, filterId);
|
|
2260
2427
|
}
|
|
@@ -2298,7 +2465,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
|
|
|
2298
2465
|
data = [adbHeader].concat(data);
|
|
2299
2466
|
}
|
|
2300
2467
|
|
|
2301
|
-
fs$
|
|
2468
|
+
fs$2.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
|
|
2302
2469
|
};
|
|
2303
2470
|
|
|
2304
2471
|
/**
|
|
@@ -2378,10 +2545,10 @@ const removeRuleDuplicates = function (list) {
|
|
|
2378
2545
|
*
|
|
2379
2546
|
* @param filterDir - Path to filter directory
|
|
2380
2547
|
* @param platformsPath - Path to platforms folder
|
|
2381
|
-
* @param
|
|
2382
|
-
* @param
|
|
2548
|
+
* @param includedFilterIds - Array of filter ids to include
|
|
2549
|
+
* @param excludedFilterIds - Array of filter ids to exclude
|
|
2383
2550
|
*/
|
|
2384
|
-
const buildFilter$1 = async (filterDir, platformsPath,
|
|
2551
|
+
const buildFilter$1 = async (filterDir, platformsPath, includedFilterIds, excludedFilterIds) => {
|
|
2385
2552
|
const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
|
|
2386
2553
|
|
|
2387
2554
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
@@ -2391,17 +2558,17 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
|
|
|
2391
2558
|
const { filterId } = metadata;
|
|
2392
2559
|
checkFilterId(filterIdsPool, filterId);
|
|
2393
2560
|
|
|
2394
|
-
if (
|
|
2395
|
-
logger.info(`Filter ${filterId} skipped
|
|
2561
|
+
if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
|
|
2562
|
+
logger.info(`Filter ${filterId} skipped due to '--include' option`);
|
|
2396
2563
|
return;
|
|
2397
2564
|
}
|
|
2398
2565
|
|
|
2399
|
-
if (
|
|
2400
|
-
logger.info(`Filter ${filterId} skipped
|
|
2566
|
+
if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
|
|
2567
|
+
logger.info(`Filter ${filterId} skipped due to '--skip' option`);
|
|
2401
2568
|
return;
|
|
2402
2569
|
}
|
|
2403
2570
|
|
|
2404
|
-
const optimizationConfig =
|
|
2571
|
+
const optimizationConfig = await getOptimizationStatistics(filterId);
|
|
2405
2572
|
|
|
2406
2573
|
// eslint-disable-next-line guard-for-in,no-restricted-syntax
|
|
2407
2574
|
for (const platform in platformPathsConfig) {
|
|
@@ -2495,30 +2662,30 @@ const isObsoleteFilter = (metadata) => metadata.tags && metadata.tags.some((tag)
|
|
|
2495
2662
|
* @param filtersDir
|
|
2496
2663
|
* @param filtersMetadata
|
|
2497
2664
|
* @param platformsPath
|
|
2498
|
-
* @param
|
|
2499
|
-
* @param
|
|
2665
|
+
* @param includedFilterIds
|
|
2666
|
+
* @param excludedFilterIds
|
|
2500
2667
|
* @param obsoleteFiltersMetadata
|
|
2501
2668
|
*/
|
|
2502
2669
|
const parseDirectory$1 = async (
|
|
2503
2670
|
filtersDir,
|
|
2504
2671
|
filtersMetadata,
|
|
2505
2672
|
platformsPath,
|
|
2506
|
-
|
|
2507
|
-
|
|
2673
|
+
includedFilterIds,
|
|
2674
|
+
excludedFilterIds,
|
|
2508
2675
|
obsoleteFiltersMetadata,
|
|
2509
2676
|
) => {
|
|
2510
|
-
const items = fs$
|
|
2677
|
+
const items = fs$2.readdirSync(filtersDir);
|
|
2511
2678
|
// eslint-disable-next-line no-restricted-syntax
|
|
2512
2679
|
for (const directory of items) {
|
|
2513
2680
|
const filterDir = path$1.join(filtersDir, directory);
|
|
2514
|
-
if (fs$
|
|
2681
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
2515
2682
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2516
|
-
if (fs$
|
|
2683
|
+
if (fs$2.existsSync(metadataFilePath)) {
|
|
2517
2684
|
logger.info(`Building filter platforms: ${directory}`);
|
|
2518
2685
|
// eslint-disable-next-line no-await-in-loop
|
|
2519
|
-
await buildFilter$1(filterDir, platformsPath,
|
|
2686
|
+
await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
2520
2687
|
logger.info(`Building filter platforms: ${directory} done`);
|
|
2521
|
-
const filterMetadata = loadFilterMetadata(filterDir,
|
|
2688
|
+
const filterMetadata = loadFilterMetadata(filterDir, includedFilterIds, excludedFilterIds);
|
|
2522
2689
|
filtersMetadata.push(filterMetadata);
|
|
2523
2690
|
if (isObsoleteFilter(filterMetadata)) {
|
|
2524
2691
|
obsoleteFiltersMetadata.push(filterMetadata);
|
|
@@ -2529,8 +2696,8 @@ const parseDirectory$1 = async (
|
|
|
2529
2696
|
filterDir,
|
|
2530
2697
|
filtersMetadata,
|
|
2531
2698
|
platformsPath,
|
|
2532
|
-
|
|
2533
|
-
|
|
2699
|
+
includedFilterIds,
|
|
2700
|
+
excludedFilterIds,
|
|
2534
2701
|
obsoleteFiltersMetadata,
|
|
2535
2702
|
);
|
|
2536
2703
|
}
|
|
@@ -2543,14 +2710,12 @@ const parseDirectory$1 = async (
|
|
|
2543
2710
|
*
|
|
2544
2711
|
* @async
|
|
2545
2712
|
* @param {string} filtersDir - The directory containing filter files to be processed.
|
|
2546
|
-
* @param {string} platformsPath - The
|
|
2547
|
-
* @param {Array<number
|
|
2548
|
-
* @param {Array<number
|
|
2713
|
+
* @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
|
|
2714
|
+
* @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include.
|
|
2715
|
+
* @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude.
|
|
2549
2716
|
* @returns {Promise<void>} Resolves when the generation process is complete.
|
|
2550
|
-
*
|
|
2551
|
-
* @throws {Error} If `platformsPath` or `platformPathsConfig` is not specified.
|
|
2552
2717
|
*/
|
|
2553
|
-
const generate = async (filtersDir, platformsPath,
|
|
2718
|
+
const generate = async (filtersDir, platformsPath, includedFilterIds, excludedFilterIds) => {
|
|
2554
2719
|
if (!platformsPath) {
|
|
2555
2720
|
logger.warn('Platforms build output path is not specified');
|
|
2556
2721
|
return;
|
|
@@ -2566,7 +2731,14 @@ const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
|
|
|
2566
2731
|
const filtersMetadata = [];
|
|
2567
2732
|
const obsoleteFiltersMetadata = [];
|
|
2568
2733
|
|
|
2569
|
-
await parseDirectory$1(
|
|
2734
|
+
await parseDirectory$1(
|
|
2735
|
+
filtersDir,
|
|
2736
|
+
filtersMetadata,
|
|
2737
|
+
platformsPath,
|
|
2738
|
+
includedFilterIds,
|
|
2739
|
+
excludedFilterIds,
|
|
2740
|
+
obsoleteFiltersMetadata,
|
|
2741
|
+
);
|
|
2570
2742
|
|
|
2571
2743
|
writeFiltersMetadata(platformsPath, filtersDir, filtersMetadata, obsoleteFiltersMetadata);
|
|
2572
2744
|
writeLocalScriptRules(platformsPath);
|
|
@@ -2632,7 +2804,7 @@ const skipFilter = (metadata) => {
|
|
|
2632
2804
|
*/
|
|
2633
2805
|
const create = (reportPath) => {
|
|
2634
2806
|
if (reportPath) {
|
|
2635
|
-
fs$
|
|
2807
|
+
fs$2.writeFileSync(reportPath, reportData, 'utf8');
|
|
2636
2808
|
return;
|
|
2637
2809
|
}
|
|
2638
2810
|
log(reportData);
|
|
@@ -2835,11 +3007,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
|
|
|
2835
3007
|
* @returns {*}
|
|
2836
3008
|
*/
|
|
2837
3009
|
const readFile$1 = function (path) {
|
|
2838
|
-
if (!fs$
|
|
3010
|
+
if (!fs$2.existsSync(path)) {
|
|
2839
3011
|
return null;
|
|
2840
3012
|
}
|
|
2841
3013
|
|
|
2842
|
-
return fs$
|
|
3014
|
+
return fs$2.readFileSync(path, { encoding: 'utf-8' });
|
|
2843
3015
|
};
|
|
2844
3016
|
|
|
2845
3017
|
/**
|
|
@@ -2849,7 +3021,7 @@ const readFile$1 = function (path) {
|
|
|
2849
3021
|
* @param data
|
|
2850
3022
|
*/
|
|
2851
3023
|
const writeFile = function (path, data) {
|
|
2852
|
-
fs$
|
|
3024
|
+
fs$2.writeFileSync(path, data, 'utf8');
|
|
2853
3025
|
};
|
|
2854
3026
|
|
|
2855
3027
|
/**
|
|
@@ -3146,7 +3318,7 @@ const include = async (filterDir, directiveLine, excluded) => {
|
|
|
3146
3318
|
const externalInclude = url.includes(':');
|
|
3147
3319
|
|
|
3148
3320
|
const included = externalInclude
|
|
3149
|
-
? downloadFile(url)
|
|
3321
|
+
? await downloadFile(url)
|
|
3150
3322
|
: readFile$1(path$1.join(filterDir, url));
|
|
3151
3323
|
|
|
3152
3324
|
if (included) {
|
|
@@ -3413,11 +3585,11 @@ const makeRevision = function (path, hash) {
|
|
|
3413
3585
|
* Builds filter txt file from directory contents
|
|
3414
3586
|
*
|
|
3415
3587
|
* @param {string} filterDir - The path to the directory containing filters.
|
|
3416
|
-
* @param {Array<number
|
|
3417
|
-
* @param {Array<number
|
|
3588
|
+
* @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
|
|
3589
|
+
* @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
|
|
3418
3590
|
* @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
|
|
3419
3591
|
*/
|
|
3420
|
-
const buildFilter = async function (filterDir,
|
|
3592
|
+
const buildFilter = async function (filterDir, includedFilterIds, excludedFilterIds) {
|
|
3421
3593
|
const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
|
|
3422
3594
|
if (!templateContent) {
|
|
3423
3595
|
throw new Error('Invalid template');
|
|
@@ -3434,12 +3606,12 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
|
|
|
3434
3606
|
return;
|
|
3435
3607
|
}
|
|
3436
3608
|
|
|
3437
|
-
if (
|
|
3609
|
+
if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
|
|
3438
3610
|
logger.info(`Filter ${filterId} skipped due to '--include' option`);
|
|
3439
3611
|
return;
|
|
3440
3612
|
}
|
|
3441
3613
|
|
|
3442
|
-
if (
|
|
3614
|
+
if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
|
|
3443
3615
|
logger.info(`Filter ${filterId} skipped due to '--skip' option`);
|
|
3444
3616
|
return;
|
|
3445
3617
|
}
|
|
@@ -3479,30 +3651,30 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
|
|
|
3479
3651
|
};
|
|
3480
3652
|
|
|
3481
3653
|
/**
|
|
3482
|
-
* Asynchronously parses a directory and processes filters based on the provided
|
|
3654
|
+
* Asynchronously parses a directory and processes filters based on the provided included/excluded filter IDs.
|
|
3483
3655
|
*
|
|
3484
3656
|
* @param {string} filtersDir - The path to the directory containing filters.
|
|
3485
|
-
* @param {Array<number
|
|
3486
|
-
* @param {Array<number
|
|
3657
|
+
* @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
|
|
3658
|
+
* @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
|
|
3487
3659
|
* @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
|
|
3488
3660
|
*/
|
|
3489
|
-
const parseDirectory = async function (filtersDir,
|
|
3490
|
-
const items = fs$
|
|
3661
|
+
const parseDirectory = async function (filtersDir, includedFilterIds, excludedFilterIds) {
|
|
3662
|
+
const items = fs$2.readdirSync(filtersDir)
|
|
3491
3663
|
.sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
|
|
3492
3664
|
|
|
3493
3665
|
// eslint-disable-next-line no-restricted-syntax
|
|
3494
3666
|
for (const directory of items) {
|
|
3495
3667
|
const filterDir = path$1.join(filtersDir, directory);
|
|
3496
|
-
if (fs$
|
|
3668
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
3497
3669
|
const template = path$1.join(filterDir, TEMPLATE_FILE);
|
|
3498
|
-
if (fs$
|
|
3670
|
+
if (fs$2.existsSync(template)) {
|
|
3499
3671
|
logger.info(`Building filter ${directory}...`);
|
|
3500
3672
|
// eslint-disable-next-line no-await-in-loop
|
|
3501
|
-
await buildFilter(filterDir,
|
|
3673
|
+
await buildFilter(filterDir, includedFilterIds, excludedFilterIds);
|
|
3502
3674
|
logger.info(`Filter ${directory} ok`);
|
|
3503
3675
|
} else {
|
|
3504
3676
|
// eslint-disable-next-line no-await-in-loop
|
|
3505
|
-
await parseDirectory(filterDir,
|
|
3677
|
+
await parseDirectory(filterDir, includedFilterIds, excludedFilterIds);
|
|
3506
3678
|
}
|
|
3507
3679
|
}
|
|
3508
3680
|
}
|
|
@@ -3516,10 +3688,10 @@ const parseDirectory = async function (filtersDir, whitelist, blacklist) {
|
|
|
3516
3688
|
* @param {string} filtersDir - The directory containing filter files to be processed.
|
|
3517
3689
|
* @param {string} logFile - The path to the log file where logs will be written.
|
|
3518
3690
|
* @param {string} reportFile - The path to the report file to be created.
|
|
3519
|
-
* @param {string} platformsPath - The path where platform data will be generated.
|
|
3691
|
+
* @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
|
|
3520
3692
|
* @param {Object} platformsConfig - The configuration object for platforms.
|
|
3521
|
-
* @param {Array<number
|
|
3522
|
-
* @param {Array<number
|
|
3693
|
+
* @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include in processing.
|
|
3694
|
+
* @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude from processing.
|
|
3523
3695
|
* @returns {Promise<void>} A promise that resolves when the build process is complete.
|
|
3524
3696
|
*/
|
|
3525
3697
|
const build = async (
|
|
@@ -3528,16 +3700,16 @@ const build = async (
|
|
|
3528
3700
|
reportFile,
|
|
3529
3701
|
platformsPath,
|
|
3530
3702
|
platformsConfig,
|
|
3531
|
-
|
|
3532
|
-
|
|
3703
|
+
includedFilterIds,
|
|
3704
|
+
excludedFilterIds,
|
|
3533
3705
|
) => {
|
|
3534
3706
|
logger.initialize(logFile);
|
|
3535
3707
|
init(FILTER_FILE, METADATA_FILE, REVISION_FILE, platformsConfig, ADGUARD_FILTERS_SERVER_URL);
|
|
3536
3708
|
|
|
3537
|
-
await parseDirectory(filtersDir,
|
|
3709
|
+
await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
|
|
3538
3710
|
|
|
3539
3711
|
logger.info('Generating platforms');
|
|
3540
|
-
await generate(filtersDir, platformsPath,
|
|
3712
|
+
await generate(filtersDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
3541
3713
|
logger.info('Generating platforms done');
|
|
3542
3714
|
create(reportFile);
|
|
3543
3715
|
};
|
|
@@ -3558,14 +3730,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
|
|
|
3558
3730
|
const loadSchemas = (dir) => {
|
|
3559
3731
|
const schemas = {};
|
|
3560
3732
|
|
|
3561
|
-
const items = fs$
|
|
3733
|
+
const items = fs$2.readdirSync(dir);
|
|
3562
3734
|
// eslint-disable-next-line no-restricted-syntax
|
|
3563
3735
|
for (const f of items) {
|
|
3564
3736
|
if (f.endsWith(SCHEMA_EXTENSION)) {
|
|
3565
3737
|
const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
|
|
3566
3738
|
|
|
3567
3739
|
logger.info(`Loading schema for ${validationFileName}`);
|
|
3568
|
-
schemas[validationFileName] = JSON.parse(fs$
|
|
3740
|
+
schemas[validationFileName] = JSON.parse(fs$2.readFileSync(path$1.join(dir, f)));
|
|
3569
3741
|
}
|
|
3570
3742
|
}
|
|
3571
3743
|
|
|
@@ -3585,7 +3757,7 @@ const loadSchemas = (dir) => {
|
|
|
3585
3757
|
const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
|
|
3586
3758
|
let items;
|
|
3587
3759
|
try {
|
|
3588
|
-
items = fs$
|
|
3760
|
+
items = fs$2.readdirSync(dir);
|
|
3589
3761
|
} catch (e) {
|
|
3590
3762
|
logger.info(e.message);
|
|
3591
3763
|
return false;
|
|
@@ -3593,7 +3765,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3593
3765
|
// eslint-disable-next-line no-restricted-syntax
|
|
3594
3766
|
for (const f of items) {
|
|
3595
3767
|
const item = path$1.join(dir, f);
|
|
3596
|
-
if (fs$
|
|
3768
|
+
if (fs$2.lstatSync(item).isDirectory()) {
|
|
3597
3769
|
if (!validateDir(item, validator, schemas, oldSchemas)) {
|
|
3598
3770
|
return false;
|
|
3599
3771
|
}
|
|
@@ -3616,7 +3788,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3616
3788
|
if (schema) {
|
|
3617
3789
|
logger.info(`Validating ${item}`);
|
|
3618
3790
|
|
|
3619
|
-
const json = JSON.parse(fs$
|
|
3791
|
+
const json = JSON.parse(fs$2.readFileSync(item));
|
|
3620
3792
|
|
|
3621
3793
|
// Validate filters amount
|
|
3622
3794
|
if (fileName === 'filters') {
|
|
@@ -3630,11 +3802,11 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3630
3802
|
const valid = validate(json);
|
|
3631
3803
|
|
|
3632
3804
|
// json can be updated with default values
|
|
3633
|
-
fs$
|
|
3805
|
+
fs$2.writeFileSync(item, JSON.stringify(json, null, '\t'));
|
|
3634
3806
|
|
|
3635
3807
|
// duplicate to .js file as well
|
|
3636
3808
|
const jsFileName = `${fileName}.js`;
|
|
3637
|
-
fs$
|
|
3809
|
+
fs$2.writeFileSync(
|
|
3638
3810
|
path$1.join(path$1.dirname(item), jsFileName),
|
|
3639
3811
|
JSON.stringify(json, null, '\t'),
|
|
3640
3812
|
);
|
|
@@ -3725,13 +3897,13 @@ const WARNING_TYPES = {
|
|
|
3725
3897
|
* Sync reads file content
|
|
3726
3898
|
* @param filePath - path to locales file
|
|
3727
3899
|
*/
|
|
3728
|
-
const readFile = (filePath) => fs$
|
|
3900
|
+
const readFile = (filePath) => fs$2.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
|
|
3729
3901
|
|
|
3730
3902
|
/**
|
|
3731
3903
|
* Sync reads directory content
|
|
3732
3904
|
* @param dirPath - path to directory
|
|
3733
3905
|
*/
|
|
3734
|
-
const readDir = (dirPath) => fs$
|
|
3906
|
+
const readDir = (dirPath) => fs$2.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
|
|
3735
3907
|
|
|
3736
3908
|
/**
|
|
3737
3909
|
* Validates messages keys
|
|
@@ -4829,7 +5001,15 @@ process.on('unhandledRejection', (error) => {
|
|
|
4829
5001
|
throw error;
|
|
4830
5002
|
});
|
|
4831
5003
|
|
|
4832
|
-
const compile = (
|
|
5004
|
+
const compile = (
|
|
5005
|
+
path,
|
|
5006
|
+
logPath,
|
|
5007
|
+
reportFile,
|
|
5008
|
+
platformsPath,
|
|
5009
|
+
includedFilterIds,
|
|
5010
|
+
excludedFilterIds,
|
|
5011
|
+
customPlatformsConfig,
|
|
5012
|
+
) => {
|
|
4833
5013
|
if (customPlatformsConfig) {
|
|
4834
5014
|
logger.info('Using custom platforms configuration');
|
|
4835
5015
|
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
@@ -4845,8 +5025,8 @@ const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist,
|
|
|
4845
5025
|
reportFile,
|
|
4846
5026
|
platformsPath,
|
|
4847
5027
|
platformsConfig,
|
|
4848
|
-
|
|
4849
|
-
|
|
5028
|
+
includedFilterIds,
|
|
5029
|
+
excludedFilterIds,
|
|
4850
5030
|
);
|
|
4851
5031
|
};
|
|
4852
5032
|
|
|
@@ -4858,4 +5038,4 @@ const validateLocales = (localesDirPath, requiredLocales) => {
|
|
|
4858
5038
|
return localesValidator.validate(localesDirPath, requiredLocales);
|
|
4859
5039
|
};
|
|
4860
5040
|
|
|
4861
|
-
export { compile, validateJSONSchema, validateLocales };
|
|
5041
|
+
export { OptimizationStatsError, compile, localOptimizationStatistics, validateJSONSchema, validateLocales };
|