@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.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,286 @@ 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. Downloads into a staged
|
|
1188
|
+
* directory first and swaps it in only after all downloads succeed,
|
|
1189
|
+
* so a failed refresh leaves any previously cached stats intact.
|
|
1190
|
+
*
|
|
1191
|
+
* When both `includedFilterIds` and `excludedFilterIds` are non-empty,
|
|
1192
|
+
* a filter is processed only if it is in `includedFilterIds` and not in
|
|
1193
|
+
* `excludedFilterIds`.
|
|
1194
|
+
*
|
|
1195
|
+
* @param basePath - Directory to save `filters/<filterId>/stats.json` into.
|
|
1196
|
+
* @param includedFilterIds - Filter IDs to process; empty (default) processes all.
|
|
1197
|
+
* @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
|
|
1198
|
+
*/
|
|
1199
|
+
download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
|
|
1200
|
+
const percent = JSON.parse(await downloadOptimizationPercent());
|
|
1201
|
+
const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
|
|
1202
|
+
&& (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
|
|
1203
|
+
const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
|
|
1204
|
+
const STAGED_PREFIX = `${process.pid}_${Date.now()}`;
|
|
1205
|
+
const STAGED_FILTERS_PATH = path$1.join(basePath, `${STAGED_PREFIX}_${FILTERS_DIR_NAME}`);
|
|
1206
|
+
/**
|
|
1207
|
+
* Bounds concurrency so a large `percent.json` cannot fan out into unbounded
|
|
1208
|
+
* parallel requests if the underlying transport ever becomes truly async.
|
|
1209
|
+
*/
|
|
1210
|
+
const DOWNLOAD_CONCURRENCY = 8;
|
|
1211
|
+
try {
|
|
1212
|
+
await fs$1.mkdir(STAGED_FILTERS_PATH, { recursive: true });
|
|
1213
|
+
await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
|
|
1214
|
+
const dir = path$1.join(STAGED_FILTERS_PATH, String(filterId));
|
|
1215
|
+
const statsPath = path$1.join(dir, STATS_JSON);
|
|
1216
|
+
const content = await downloadOptimizationStats(filterId);
|
|
1217
|
+
await fs$1.mkdir(dir, { recursive: true });
|
|
1218
|
+
await fs$1.writeFile(statsPath, content, 'utf-8');
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
catch (error) {
|
|
1222
|
+
// Downloads incomplete: staged dir is garbage, safe to discard.
|
|
1223
|
+
await fs$1.rm(STAGED_FILTERS_PATH, { recursive: true, force: true });
|
|
1224
|
+
throw error;
|
|
1108
1225
|
}
|
|
1226
|
+
// Swap only after every fetch succeeded. From here on, staged dir is
|
|
1227
|
+
// a complete cache — never rm it on failure, that'd destroy the only
|
|
1228
|
+
// remaining valid copy once the old FILTERS_PATH is gone.
|
|
1229
|
+
await fs$1.rm(FILTERS_PATH, { recursive: true, force: true });
|
|
1230
|
+
await fs$1.rename(STAGED_FILTERS_PATH, FILTERS_PATH);
|
|
1231
|
+
},
|
|
1232
|
+
/**
|
|
1233
|
+
* Configures `getOptimizationStatistics` to read stats from local files under
|
|
1234
|
+
* `basePath` instead of fetching from the remote server.
|
|
1235
|
+
* Stats are loaded lazily on demand during compilation. `percent.json` is
|
|
1236
|
+
* still fetched remotely to determine which filters are optimizable.
|
|
1237
|
+
*
|
|
1238
|
+
* @param basePath - Directory containing `filters/<filterId>/stats.json`.
|
|
1239
|
+
*/
|
|
1240
|
+
use(basePath) {
|
|
1241
|
+
localStatsPath = basePath;
|
|
1242
|
+
optimizableFilterIdsPromise = null;
|
|
1243
|
+
},
|
|
1244
|
+
/**
|
|
1245
|
+
* Removes the cache directory and clears in-memory state.
|
|
1246
|
+
*
|
|
1247
|
+
* @param basePath - Directory to remove.
|
|
1248
|
+
*/
|
|
1249
|
+
async reset(basePath) {
|
|
1250
|
+
await fs$1.rm(basePath, { recursive: true, force: true });
|
|
1251
|
+
localStatsPath = null;
|
|
1252
|
+
optimizableFilterIdsPromise = null;
|
|
1253
|
+
},
|
|
1254
|
+
};
|
|
1255
|
+
/**
|
|
1256
|
+
* Returns the optimization stats for the given filter, or `null` when
|
|
1257
|
+
* optimization is disabled or the filter is not listed in `percent.json`.
|
|
1258
|
+
*
|
|
1259
|
+
* When `localOptimizationStatistics.use(path)` has been called, stats
|
|
1260
|
+
* are read lazily from local files. Otherwise stats are fetched from the
|
|
1261
|
+
* remote server.
|
|
1262
|
+
*
|
|
1263
|
+
* @param filterId - Numeric filter identifier.
|
|
1264
|
+
* @returns Parsed stats object, or `null` when the filter has no optimization stats.
|
|
1265
|
+
* @throws {Error} When the stats are missing or malformed.
|
|
1266
|
+
*/
|
|
1267
|
+
const getOptimizationStatistics = async (filterId) => {
|
|
1268
|
+
const ids = await getOptimizableFilterIds();
|
|
1269
|
+
if (!ids.has(filterId)) {
|
|
1270
|
+
return null;
|
|
1109
1271
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1272
|
+
let stats;
|
|
1273
|
+
try {
|
|
1274
|
+
const content = localStatsPath !== null
|
|
1275
|
+
? await fs$1.readFile(path$1.join(localStatsPath, FILTERS_DIR_NAME, String(filterId), STATS_JSON), 'utf-8')
|
|
1276
|
+
: await downloadOptimizationStats(filterId);
|
|
1277
|
+
stats = JSON.parse(content);
|
|
1278
|
+
}
|
|
1279
|
+
catch (originalError) {
|
|
1280
|
+
const statsPath = localStatsPath === null
|
|
1281
|
+
? getOptimizationStatsUrl(filterId)
|
|
1282
|
+
: `${localStatsPath}/filters/${filterId}/stats.json`;
|
|
1283
|
+
throw new OptimizationStatsError(filterId, statsPath, { cause: originalError });
|
|
1284
|
+
}
|
|
1285
|
+
assertValidStats(filterId, stats);
|
|
1286
|
+
return stats;
|
|
1112
1287
|
};
|
|
1113
|
-
|
|
1114
1288
|
/**
|
|
1115
|
-
* Checks if rule should be skipped
|
|
1116
|
-
* and
|
|
1117
|
-
*
|
|
1118
|
-
* @param
|
|
1289
|
+
* Checks if rule should be skipped because optimization is enabled for this filter
|
|
1290
|
+
* and the hit count for this rule is below the configured threshold.
|
|
1291
|
+
*
|
|
1292
|
+
* @param ruleText - Rule text to check.
|
|
1293
|
+
* @param optimizationStats - Optimization config for this filter.
|
|
1294
|
+
* @returns `true` if the rule should be skipped, `false` otherwise.
|
|
1119
1295
|
*/
|
|
1120
|
-
const skipRuleWithOptimization = (ruleText,
|
|
1121
|
-
if (!
|
|
1296
|
+
const skipRuleWithOptimization = (ruleText, optimizationStats) => {
|
|
1297
|
+
if (!optimizationStats) {
|
|
1122
1298
|
return false;
|
|
1123
1299
|
}
|
|
1124
|
-
|
|
1125
1300
|
// eslint-disable-next-line no-restricted-syntax
|
|
1126
|
-
for (const group of
|
|
1301
|
+
for (const group of optimizationStats.groups) {
|
|
1127
1302
|
const hits = group.rules[ruleText];
|
|
1128
1303
|
if (hits !== undefined && hits < group.config.hits) {
|
|
1129
1304
|
return true;
|
|
1130
1305
|
}
|
|
1131
1306
|
}
|
|
1132
|
-
|
|
1133
1307
|
return false;
|
|
1134
1308
|
};
|
|
1135
1309
|
|
|
@@ -1490,7 +1664,7 @@ let adguardFiltersServerUrl = null;
|
|
|
1490
1664
|
*/
|
|
1491
1665
|
const readFile$2 = function (path) {
|
|
1492
1666
|
try {
|
|
1493
|
-
return fs$
|
|
1667
|
+
return fs$2.readFileSync(path, { encoding: 'utf-8' });
|
|
1494
1668
|
} catch (e) {
|
|
1495
1669
|
return null;
|
|
1496
1670
|
}
|
|
@@ -1603,7 +1777,7 @@ const createDir = (dir) => {
|
|
|
1603
1777
|
return dir.split(sep).reduce((parentDir, childDir) => {
|
|
1604
1778
|
const curDir = path$1.resolve(baseDir, parentDir, childDir);
|
|
1605
1779
|
try {
|
|
1606
|
-
fs$
|
|
1780
|
+
fs$2.mkdirSync(curDir);
|
|
1607
1781
|
} catch (err) {
|
|
1608
1782
|
if (err.code === 'EEXIST') { // curDir already exists!
|
|
1609
1783
|
return curDir;
|
|
@@ -1874,11 +2048,11 @@ const loadLocales = function (dir) {
|
|
|
1874
2048
|
filters: {},
|
|
1875
2049
|
};
|
|
1876
2050
|
|
|
1877
|
-
const locales = fs$
|
|
2051
|
+
const locales = fs$2.readdirSync(dir);
|
|
1878
2052
|
// eslint-disable-next-line no-restricted-syntax
|
|
1879
2053
|
for (const directory of locales) {
|
|
1880
2054
|
const localeDir = path$1.join(dir, directory);
|
|
1881
|
-
if (fs$
|
|
2055
|
+
if (fs$2.lstatSync(localeDir).isDirectory()) {
|
|
1882
2056
|
const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
|
|
1883
2057
|
if (groups) {
|
|
1884
2058
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -2103,8 +2277,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
|
|
|
2103
2277
|
|
|
2104
2278
|
const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
|
|
2105
2279
|
|
|
2106
|
-
fs$
|
|
2107
|
-
fs$
|
|
2280
|
+
fs$2.writeFileSync(filtersFileJson, filtersContent, 'utf8');
|
|
2281
|
+
fs$2.writeFileSync(filtersFileJs, filtersContent, 'utf8');
|
|
2108
2282
|
|
|
2109
2283
|
logger.info(`Writing filters localizations: ${config.path}`);
|
|
2110
2284
|
const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
|
|
@@ -2145,8 +2319,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
|
|
|
2145
2319
|
|
|
2146
2320
|
const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
|
|
2147
2321
|
|
|
2148
|
-
fs$
|
|
2149
|
-
fs$
|
|
2322
|
+
fs$2.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
|
|
2323
|
+
fs$2.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
|
|
2150
2324
|
}
|
|
2151
2325
|
|
|
2152
2326
|
logger.info('Writing filters metadata done');
|
|
@@ -2206,12 +2380,12 @@ const writeLocalScriptRules = function (platformsPath) {
|
|
|
2206
2380
|
// remove scriptlet rules in local_script_rules.json
|
|
2207
2381
|
rulesJson.rules = removeScriptletRules(rulesJson.rules);
|
|
2208
2382
|
|
|
2209
|
-
fs$
|
|
2383
|
+
fs$2.writeFileSync(
|
|
2210
2384
|
path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
|
|
2211
2385
|
rulesTxt.join(RULES_SEPARATOR),
|
|
2212
2386
|
'utf8',
|
|
2213
2387
|
);
|
|
2214
|
-
fs$
|
|
2388
|
+
fs$2.writeFileSync(
|
|
2215
2389
|
path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
|
|
2216
2390
|
JSON.stringify(rulesJson, null, 4),
|
|
2217
2391
|
'utf8',
|
|
@@ -2225,12 +2399,12 @@ const writeLocalScriptRules = function (platformsPath) {
|
|
|
2225
2399
|
* Loads and processes filter metadata from the specified directory.
|
|
2226
2400
|
*
|
|
2227
2401
|
* @param {string} filterDir - The directory containing the filter metadata and revision files.
|
|
2228
|
-
* @param {number[]} [
|
|
2229
|
-
* @param {number[]} [
|
|
2402
|
+
* @param {number[]} [includedFilterIds] - An optional array of filter IDs to include.
|
|
2403
|
+
* @param {number[]} [excludedFilterIds] - An optional array of filter IDs to exclude.
|
|
2230
2404
|
* @returns {Object} The processed filter metadata.
|
|
2231
2405
|
* @throws {Error} If the metadata or revision file cannot be read.
|
|
2232
2406
|
*/
|
|
2233
|
-
const loadFilterMetadata = function (filterDir,
|
|
2407
|
+
const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilterIds) {
|
|
2234
2408
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2235
2409
|
const metadataString = readFile$2(metadataFilePath);
|
|
2236
2410
|
if (!metadataString) {
|
|
@@ -2253,8 +2427,8 @@ const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
|
|
|
2253
2427
|
|
|
2254
2428
|
const { filterId } = result;
|
|
2255
2429
|
if (
|
|
2256
|
-
(
|
|
2257
|
-
|| (
|
|
2430
|
+
(includedFilterIds && includedFilterIds.includes(filterId))
|
|
2431
|
+
|| (excludedFilterIds && !excludedFilterIds.includes(filterId))
|
|
2258
2432
|
) {
|
|
2259
2433
|
checkFilterId(metadataFilterIdsPool, filterId);
|
|
2260
2434
|
}
|
|
@@ -2298,7 +2472,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
|
|
|
2298
2472
|
data = [adbHeader].concat(data);
|
|
2299
2473
|
}
|
|
2300
2474
|
|
|
2301
|
-
fs$
|
|
2475
|
+
fs$2.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
|
|
2302
2476
|
};
|
|
2303
2477
|
|
|
2304
2478
|
/**
|
|
@@ -2378,10 +2552,10 @@ const removeRuleDuplicates = function (list) {
|
|
|
2378
2552
|
*
|
|
2379
2553
|
* @param filterDir - Path to filter directory
|
|
2380
2554
|
* @param platformsPath - Path to platforms folder
|
|
2381
|
-
* @param
|
|
2382
|
-
* @param
|
|
2555
|
+
* @param includedFilterIds - Array of filter ids to include
|
|
2556
|
+
* @param excludedFilterIds - Array of filter ids to exclude
|
|
2383
2557
|
*/
|
|
2384
|
-
const buildFilter$1 = async (filterDir, platformsPath,
|
|
2558
|
+
const buildFilter$1 = async (filterDir, platformsPath, includedFilterIds, excludedFilterIds) => {
|
|
2385
2559
|
const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
|
|
2386
2560
|
|
|
2387
2561
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
@@ -2391,17 +2565,17 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
|
|
|
2391
2565
|
const { filterId } = metadata;
|
|
2392
2566
|
checkFilterId(filterIdsPool, filterId);
|
|
2393
2567
|
|
|
2394
|
-
if (
|
|
2395
|
-
logger.info(`Filter ${filterId} skipped
|
|
2568
|
+
if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
|
|
2569
|
+
logger.info(`Filter ${filterId} skipped due to '--include' option`);
|
|
2396
2570
|
return;
|
|
2397
2571
|
}
|
|
2398
2572
|
|
|
2399
|
-
if (
|
|
2400
|
-
logger.info(`Filter ${filterId} skipped
|
|
2573
|
+
if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
|
|
2574
|
+
logger.info(`Filter ${filterId} skipped due to '--skip' option`);
|
|
2401
2575
|
return;
|
|
2402
2576
|
}
|
|
2403
2577
|
|
|
2404
|
-
const optimizationConfig =
|
|
2578
|
+
const optimizationConfig = await getOptimizationStatistics(filterId);
|
|
2405
2579
|
|
|
2406
2580
|
// eslint-disable-next-line guard-for-in,no-restricted-syntax
|
|
2407
2581
|
for (const platform in platformPathsConfig) {
|
|
@@ -2495,30 +2669,30 @@ const isObsoleteFilter = (metadata) => metadata.tags && metadata.tags.some((tag)
|
|
|
2495
2669
|
* @param filtersDir
|
|
2496
2670
|
* @param filtersMetadata
|
|
2497
2671
|
* @param platformsPath
|
|
2498
|
-
* @param
|
|
2499
|
-
* @param
|
|
2672
|
+
* @param includedFilterIds
|
|
2673
|
+
* @param excludedFilterIds
|
|
2500
2674
|
* @param obsoleteFiltersMetadata
|
|
2501
2675
|
*/
|
|
2502
2676
|
const parseDirectory$1 = async (
|
|
2503
2677
|
filtersDir,
|
|
2504
2678
|
filtersMetadata,
|
|
2505
2679
|
platformsPath,
|
|
2506
|
-
|
|
2507
|
-
|
|
2680
|
+
includedFilterIds,
|
|
2681
|
+
excludedFilterIds,
|
|
2508
2682
|
obsoleteFiltersMetadata,
|
|
2509
2683
|
) => {
|
|
2510
|
-
const items = fs$
|
|
2684
|
+
const items = fs$2.readdirSync(filtersDir);
|
|
2511
2685
|
// eslint-disable-next-line no-restricted-syntax
|
|
2512
2686
|
for (const directory of items) {
|
|
2513
2687
|
const filterDir = path$1.join(filtersDir, directory);
|
|
2514
|
-
if (fs$
|
|
2688
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
2515
2689
|
const metadataFilePath = path$1.join(filterDir, metadataFile);
|
|
2516
|
-
if (fs$
|
|
2690
|
+
if (fs$2.existsSync(metadataFilePath)) {
|
|
2517
2691
|
logger.info(`Building filter platforms: ${directory}`);
|
|
2518
2692
|
// eslint-disable-next-line no-await-in-loop
|
|
2519
|
-
await buildFilter$1(filterDir, platformsPath,
|
|
2693
|
+
await buildFilter$1(filterDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
2520
2694
|
logger.info(`Building filter platforms: ${directory} done`);
|
|
2521
|
-
const filterMetadata = loadFilterMetadata(filterDir,
|
|
2695
|
+
const filterMetadata = loadFilterMetadata(filterDir, includedFilterIds, excludedFilterIds);
|
|
2522
2696
|
filtersMetadata.push(filterMetadata);
|
|
2523
2697
|
if (isObsoleteFilter(filterMetadata)) {
|
|
2524
2698
|
obsoleteFiltersMetadata.push(filterMetadata);
|
|
@@ -2529,8 +2703,8 @@ const parseDirectory$1 = async (
|
|
|
2529
2703
|
filterDir,
|
|
2530
2704
|
filtersMetadata,
|
|
2531
2705
|
platformsPath,
|
|
2532
|
-
|
|
2533
|
-
|
|
2706
|
+
includedFilterIds,
|
|
2707
|
+
excludedFilterIds,
|
|
2534
2708
|
obsoleteFiltersMetadata,
|
|
2535
2709
|
);
|
|
2536
2710
|
}
|
|
@@ -2543,14 +2717,12 @@ const parseDirectory$1 = async (
|
|
|
2543
2717
|
*
|
|
2544
2718
|
* @async
|
|
2545
2719
|
* @param {string} filtersDir - The directory containing filter files to be processed.
|
|
2546
|
-
* @param {string} platformsPath - The
|
|
2547
|
-
* @param {Array<number
|
|
2548
|
-
* @param {Array<number
|
|
2720
|
+
* @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
|
|
2721
|
+
* @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include.
|
|
2722
|
+
* @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude.
|
|
2549
2723
|
* @returns {Promise<void>} Resolves when the generation process is complete.
|
|
2550
|
-
*
|
|
2551
|
-
* @throws {Error} If `platformsPath` or `platformPathsConfig` is not specified.
|
|
2552
2724
|
*/
|
|
2553
|
-
const generate = async (filtersDir, platformsPath,
|
|
2725
|
+
const generate = async (filtersDir, platformsPath, includedFilterIds, excludedFilterIds) => {
|
|
2554
2726
|
if (!platformsPath) {
|
|
2555
2727
|
logger.warn('Platforms build output path is not specified');
|
|
2556
2728
|
return;
|
|
@@ -2566,7 +2738,14 @@ const generate = async (filtersDir, platformsPath, whitelist, blacklist) => {
|
|
|
2566
2738
|
const filtersMetadata = [];
|
|
2567
2739
|
const obsoleteFiltersMetadata = [];
|
|
2568
2740
|
|
|
2569
|
-
await parseDirectory$1(
|
|
2741
|
+
await parseDirectory$1(
|
|
2742
|
+
filtersDir,
|
|
2743
|
+
filtersMetadata,
|
|
2744
|
+
platformsPath,
|
|
2745
|
+
includedFilterIds,
|
|
2746
|
+
excludedFilterIds,
|
|
2747
|
+
obsoleteFiltersMetadata,
|
|
2748
|
+
);
|
|
2570
2749
|
|
|
2571
2750
|
writeFiltersMetadata(platformsPath, filtersDir, filtersMetadata, obsoleteFiltersMetadata);
|
|
2572
2751
|
writeLocalScriptRules(platformsPath);
|
|
@@ -2632,7 +2811,7 @@ const skipFilter = (metadata) => {
|
|
|
2632
2811
|
*/
|
|
2633
2812
|
const create = (reportPath) => {
|
|
2634
2813
|
if (reportPath) {
|
|
2635
|
-
fs$
|
|
2814
|
+
fs$2.writeFileSync(reportPath, reportData, 'utf8');
|
|
2636
2815
|
return;
|
|
2637
2816
|
}
|
|
2638
2817
|
log(reportData);
|
|
@@ -2835,11 +3014,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
|
|
|
2835
3014
|
* @returns {*}
|
|
2836
3015
|
*/
|
|
2837
3016
|
const readFile$1 = function (path) {
|
|
2838
|
-
if (!fs$
|
|
3017
|
+
if (!fs$2.existsSync(path)) {
|
|
2839
3018
|
return null;
|
|
2840
3019
|
}
|
|
2841
3020
|
|
|
2842
|
-
return fs$
|
|
3021
|
+
return fs$2.readFileSync(path, { encoding: 'utf-8' });
|
|
2843
3022
|
};
|
|
2844
3023
|
|
|
2845
3024
|
/**
|
|
@@ -2849,7 +3028,7 @@ const readFile$1 = function (path) {
|
|
|
2849
3028
|
* @param data
|
|
2850
3029
|
*/
|
|
2851
3030
|
const writeFile = function (path, data) {
|
|
2852
|
-
fs$
|
|
3031
|
+
fs$2.writeFileSync(path, data, 'utf8');
|
|
2853
3032
|
};
|
|
2854
3033
|
|
|
2855
3034
|
/**
|
|
@@ -3146,7 +3325,7 @@ const include = async (filterDir, directiveLine, excluded) => {
|
|
|
3146
3325
|
const externalInclude = url.includes(':');
|
|
3147
3326
|
|
|
3148
3327
|
const included = externalInclude
|
|
3149
|
-
? downloadFile(url)
|
|
3328
|
+
? await downloadFile(url)
|
|
3150
3329
|
: readFile$1(path$1.join(filterDir, url));
|
|
3151
3330
|
|
|
3152
3331
|
if (included) {
|
|
@@ -3413,11 +3592,11 @@ const makeRevision = function (path, hash) {
|
|
|
3413
3592
|
* Builds filter txt file from directory contents
|
|
3414
3593
|
*
|
|
3415
3594
|
* @param {string} filterDir - The path to the directory containing filters.
|
|
3416
|
-
* @param {Array<number
|
|
3417
|
-
* @param {Array<number
|
|
3595
|
+
* @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
|
|
3596
|
+
* @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
|
|
3418
3597
|
* @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
|
|
3419
3598
|
*/
|
|
3420
|
-
const buildFilter = async function (filterDir,
|
|
3599
|
+
const buildFilter = async function (filterDir, includedFilterIds, excludedFilterIds) {
|
|
3421
3600
|
const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
|
|
3422
3601
|
if (!templateContent) {
|
|
3423
3602
|
throw new Error('Invalid template');
|
|
@@ -3434,12 +3613,12 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
|
|
|
3434
3613
|
return;
|
|
3435
3614
|
}
|
|
3436
3615
|
|
|
3437
|
-
if (
|
|
3616
|
+
if (includedFilterIds && includedFilterIds.length > 0 && includedFilterIds.indexOf(filterId) < 0) {
|
|
3438
3617
|
logger.info(`Filter ${filterId} skipped due to '--include' option`);
|
|
3439
3618
|
return;
|
|
3440
3619
|
}
|
|
3441
3620
|
|
|
3442
|
-
if (
|
|
3621
|
+
if (excludedFilterIds && excludedFilterIds.length > 0 && excludedFilterIds.indexOf(filterId) >= 0) {
|
|
3443
3622
|
logger.info(`Filter ${filterId} skipped due to '--skip' option`);
|
|
3444
3623
|
return;
|
|
3445
3624
|
}
|
|
@@ -3479,30 +3658,30 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
|
|
|
3479
3658
|
};
|
|
3480
3659
|
|
|
3481
3660
|
/**
|
|
3482
|
-
* Asynchronously parses a directory and processes filters based on the provided
|
|
3661
|
+
* Asynchronously parses a directory and processes filters based on the provided included/excluded filter IDs.
|
|
3483
3662
|
*
|
|
3484
3663
|
* @param {string} filtersDir - The path to the directory containing filters.
|
|
3485
|
-
* @param {Array<number
|
|
3486
|
-
* @param {Array<number
|
|
3664
|
+
* @param {Array<number>|null} [includedFilterIds] - An array of filter IDs to include.
|
|
3665
|
+
* @param {Array<number>|null} [excludedFilterIds] - An array of filter IDs to exclude.
|
|
3487
3666
|
* @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
|
|
3488
3667
|
*/
|
|
3489
|
-
const parseDirectory = async function (filtersDir,
|
|
3490
|
-
const items = fs$
|
|
3668
|
+
const parseDirectory = async function (filtersDir, includedFilterIds, excludedFilterIds) {
|
|
3669
|
+
const items = fs$2.readdirSync(filtersDir)
|
|
3491
3670
|
.sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
|
|
3492
3671
|
|
|
3493
3672
|
// eslint-disable-next-line no-restricted-syntax
|
|
3494
3673
|
for (const directory of items) {
|
|
3495
3674
|
const filterDir = path$1.join(filtersDir, directory);
|
|
3496
|
-
if (fs$
|
|
3675
|
+
if (fs$2.lstatSync(filterDir).isDirectory()) {
|
|
3497
3676
|
const template = path$1.join(filterDir, TEMPLATE_FILE);
|
|
3498
|
-
if (fs$
|
|
3677
|
+
if (fs$2.existsSync(template)) {
|
|
3499
3678
|
logger.info(`Building filter ${directory}...`);
|
|
3500
3679
|
// eslint-disable-next-line no-await-in-loop
|
|
3501
|
-
await buildFilter(filterDir,
|
|
3680
|
+
await buildFilter(filterDir, includedFilterIds, excludedFilterIds);
|
|
3502
3681
|
logger.info(`Filter ${directory} ok`);
|
|
3503
3682
|
} else {
|
|
3504
3683
|
// eslint-disable-next-line no-await-in-loop
|
|
3505
|
-
await parseDirectory(filterDir,
|
|
3684
|
+
await parseDirectory(filterDir, includedFilterIds, excludedFilterIds);
|
|
3506
3685
|
}
|
|
3507
3686
|
}
|
|
3508
3687
|
}
|
|
@@ -3516,10 +3695,10 @@ const parseDirectory = async function (filtersDir, whitelist, blacklist) {
|
|
|
3516
3695
|
* @param {string} filtersDir - The directory containing filter files to be processed.
|
|
3517
3696
|
* @param {string} logFile - The path to the log file where logs will be written.
|
|
3518
3697
|
* @param {string} reportFile - The path to the report file to be created.
|
|
3519
|
-
* @param {string} platformsPath - The path where platform data will be generated.
|
|
3698
|
+
* @param {string|null} platformsPath - The path where platform data will be generated; `null` skips platform output.
|
|
3520
3699
|
* @param {Object} platformsConfig - The configuration object for platforms.
|
|
3521
|
-
* @param {Array<number
|
|
3522
|
-
* @param {Array<number
|
|
3700
|
+
* @param {Array<number>|null} [includedFilterIds] - A list of filter IDs to include in processing.
|
|
3701
|
+
* @param {Array<number>|null} [excludedFilterIds] - A list of filter IDs to exclude from processing.
|
|
3523
3702
|
* @returns {Promise<void>} A promise that resolves when the build process is complete.
|
|
3524
3703
|
*/
|
|
3525
3704
|
const build = async (
|
|
@@ -3528,16 +3707,16 @@ const build = async (
|
|
|
3528
3707
|
reportFile,
|
|
3529
3708
|
platformsPath,
|
|
3530
3709
|
platformsConfig,
|
|
3531
|
-
|
|
3532
|
-
|
|
3710
|
+
includedFilterIds,
|
|
3711
|
+
excludedFilterIds,
|
|
3533
3712
|
) => {
|
|
3534
3713
|
logger.initialize(logFile);
|
|
3535
3714
|
init(FILTER_FILE, METADATA_FILE, REVISION_FILE, platformsConfig, ADGUARD_FILTERS_SERVER_URL);
|
|
3536
3715
|
|
|
3537
|
-
await parseDirectory(filtersDir,
|
|
3716
|
+
await parseDirectory(filtersDir, includedFilterIds, excludedFilterIds);
|
|
3538
3717
|
|
|
3539
3718
|
logger.info('Generating platforms');
|
|
3540
|
-
await generate(filtersDir, platformsPath,
|
|
3719
|
+
await generate(filtersDir, platformsPath, includedFilterIds, excludedFilterIds);
|
|
3541
3720
|
logger.info('Generating platforms done');
|
|
3542
3721
|
create(reportFile);
|
|
3543
3722
|
};
|
|
@@ -3558,14 +3737,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
|
|
|
3558
3737
|
const loadSchemas = (dir) => {
|
|
3559
3738
|
const schemas = {};
|
|
3560
3739
|
|
|
3561
|
-
const items = fs$
|
|
3740
|
+
const items = fs$2.readdirSync(dir);
|
|
3562
3741
|
// eslint-disable-next-line no-restricted-syntax
|
|
3563
3742
|
for (const f of items) {
|
|
3564
3743
|
if (f.endsWith(SCHEMA_EXTENSION)) {
|
|
3565
3744
|
const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
|
|
3566
3745
|
|
|
3567
3746
|
logger.info(`Loading schema for ${validationFileName}`);
|
|
3568
|
-
schemas[validationFileName] = JSON.parse(fs$
|
|
3747
|
+
schemas[validationFileName] = JSON.parse(fs$2.readFileSync(path$1.join(dir, f)));
|
|
3569
3748
|
}
|
|
3570
3749
|
}
|
|
3571
3750
|
|
|
@@ -3585,7 +3764,7 @@ const loadSchemas = (dir) => {
|
|
|
3585
3764
|
const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
|
|
3586
3765
|
let items;
|
|
3587
3766
|
try {
|
|
3588
|
-
items = fs$
|
|
3767
|
+
items = fs$2.readdirSync(dir);
|
|
3589
3768
|
} catch (e) {
|
|
3590
3769
|
logger.info(e.message);
|
|
3591
3770
|
return false;
|
|
@@ -3593,7 +3772,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3593
3772
|
// eslint-disable-next-line no-restricted-syntax
|
|
3594
3773
|
for (const f of items) {
|
|
3595
3774
|
const item = path$1.join(dir, f);
|
|
3596
|
-
if (fs$
|
|
3775
|
+
if (fs$2.lstatSync(item).isDirectory()) {
|
|
3597
3776
|
if (!validateDir(item, validator, schemas, oldSchemas)) {
|
|
3598
3777
|
return false;
|
|
3599
3778
|
}
|
|
@@ -3616,7 +3795,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3616
3795
|
if (schema) {
|
|
3617
3796
|
logger.info(`Validating ${item}`);
|
|
3618
3797
|
|
|
3619
|
-
const json = JSON.parse(fs$
|
|
3798
|
+
const json = JSON.parse(fs$2.readFileSync(item));
|
|
3620
3799
|
|
|
3621
3800
|
// Validate filters amount
|
|
3622
3801
|
if (fileName === 'filters') {
|
|
@@ -3630,11 +3809,11 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
|
|
|
3630
3809
|
const valid = validate(json);
|
|
3631
3810
|
|
|
3632
3811
|
// json can be updated with default values
|
|
3633
|
-
fs$
|
|
3812
|
+
fs$2.writeFileSync(item, JSON.stringify(json, null, '\t'));
|
|
3634
3813
|
|
|
3635
3814
|
// duplicate to .js file as well
|
|
3636
3815
|
const jsFileName = `${fileName}.js`;
|
|
3637
|
-
fs$
|
|
3816
|
+
fs$2.writeFileSync(
|
|
3638
3817
|
path$1.join(path$1.dirname(item), jsFileName),
|
|
3639
3818
|
JSON.stringify(json, null, '\t'),
|
|
3640
3819
|
);
|
|
@@ -3725,13 +3904,13 @@ const WARNING_TYPES = {
|
|
|
3725
3904
|
* Sync reads file content
|
|
3726
3905
|
* @param filePath - path to locales file
|
|
3727
3906
|
*/
|
|
3728
|
-
const readFile = (filePath) => fs$
|
|
3907
|
+
const readFile = (filePath) => fs$2.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
|
|
3729
3908
|
|
|
3730
3909
|
/**
|
|
3731
3910
|
* Sync reads directory content
|
|
3732
3911
|
* @param dirPath - path to directory
|
|
3733
3912
|
*/
|
|
3734
|
-
const readDir = (dirPath) => fs$
|
|
3913
|
+
const readDir = (dirPath) => fs$2.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
|
|
3735
3914
|
|
|
3736
3915
|
/**
|
|
3737
3916
|
* Validates messages keys
|
|
@@ -4829,7 +5008,15 @@ process.on('unhandledRejection', (error) => {
|
|
|
4829
5008
|
throw error;
|
|
4830
5009
|
});
|
|
4831
5010
|
|
|
4832
|
-
const compile = (
|
|
5011
|
+
const compile = (
|
|
5012
|
+
path,
|
|
5013
|
+
logPath,
|
|
5014
|
+
reportFile,
|
|
5015
|
+
platformsPath,
|
|
5016
|
+
includedFilterIds,
|
|
5017
|
+
excludedFilterIds,
|
|
5018
|
+
customPlatformsConfig,
|
|
5019
|
+
) => {
|
|
4833
5020
|
if (customPlatformsConfig) {
|
|
4834
5021
|
logger.info('Using custom platforms configuration');
|
|
4835
5022
|
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
@@ -4845,8 +5032,8 @@ const compile = (path, logPath, reportFile, platformsPath, whitelist, blacklist,
|
|
|
4845
5032
|
reportFile,
|
|
4846
5033
|
platformsPath,
|
|
4847
5034
|
platformsConfig,
|
|
4848
|
-
|
|
4849
|
-
|
|
5035
|
+
includedFilterIds,
|
|
5036
|
+
excludedFilterIds,
|
|
4850
5037
|
);
|
|
4851
5038
|
};
|
|
4852
5039
|
|
|
@@ -4858,4 +5045,4 @@ const validateLocales = (localesDirPath, requiredLocales) => {
|
|
|
4858
5045
|
return localesValidator.validate(localesDirPath, requiredLocales);
|
|
4859
5046
|
};
|
|
4860
5047
|
|
|
4861
|
-
export { compile, validateJSONSchema, validateLocales };
|
|
5048
|
+
export { OptimizationStatsError, compile, localOptimizationStatistics, validateJSONSchema, validateLocales };
|