@adguard/filters-compiler 3.0.2 → 3.1.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/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
- import path from 'path';
1
+ import path$1 from 'path';
2
2
  import { RuleFactory, CosmeticRule, NetworkRule, setConfiguration, CompatibilityTypes } from '@adguard/tsurlfilter';
3
- import fs from 'fs';
3
+ import fs$1 from 'fs';
4
4
  import md5 from 'md5';
5
5
  import { FiltersDownloader } from '@adguard/filters-downloader';
6
6
  import { RuleParser, RuleConverter, RuleGenerator, CosmeticRuleType, CommentParser, RuleCategory, RegExpUtils, ADG_SCRIPTLET_MASK } from '@adguard/agtree';
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
7
10
  import { Logger } from '@adguard/logger';
8
11
  import { defaultParserOptions } from '@adguard/agtree/parser';
9
12
  import { parse } from '@adguard/ecss-tree';
@@ -69,24 +72,103 @@ const version = {
69
72
  };
70
73
 
71
74
  /**
72
- * Export logger implementation.
75
+ * Extend logger implementation
73
76
  */
74
- const logger = new Logger(console);
77
+ class CompilerLogger extends Logger {
78
+ /**
79
+ * File descriptor
80
+ *
81
+ * @type {number | null}
82
+ *
83
+ * @private
84
+ */
85
+ #fd = null;
75
86
 
76
- /**
77
- * Initializes logger
78
- *
79
- * @param path log file
80
- * @param level log lvl
81
- */
82
- logger.initialize = (path) => {
83
- if (!path) {
84
- /* eslint-disable-next-line no-console */
85
- console.warn('Log file is not specified');
86
- return;
87
+ /**
88
+ * Helper to append message to log file
89
+ *
90
+ * @param {string} message
91
+ * @param {'INFO'|'WARN'|'ERROR'} level
92
+ *
93
+ * @private
94
+ */
95
+ #append(message, level) {
96
+ if (this.#fd == null) {
97
+ return;
98
+ }
99
+
100
+ const line = `[${new Date().toLocaleTimeString()}] [${level}]: ${message}${os.EOL}`;
101
+
102
+ // Using appendFileSync with an fd ensures atomic append semantics.
103
+ fs.appendFileSync(this.#fd, line, 'utf8');
87
104
  }
88
- fs.openSync(path, 'w');
89
- };
105
+
106
+ /** @inheritdoc */
107
+ info(message) {
108
+ super.info(message);
109
+ this.#append(message, 'INFO');
110
+ }
111
+
112
+ /** @inheritdoc */
113
+ error(message) {
114
+ super.error(message);
115
+ this.#append(message, 'ERROR');
116
+ }
117
+
118
+ /** @inheritdoc */
119
+ warn(message) {
120
+ super.warn(message);
121
+ this.#append(message, 'WARN');
122
+ }
123
+
124
+ /**
125
+ * Initializes logger
126
+ *
127
+ * @param {string} logFilePath - log file path
128
+ *
129
+ * The log file is opened with 'w' (truncate/create). Subsequent writes are appended.
130
+ */
131
+ initialize(logFilePath) {
132
+ if (!logFilePath) {
133
+ /* eslint-disable no-console */
134
+ console.warn('Log file is not specified');
135
+ return;
136
+ }
137
+
138
+ // Ensure the directory exists before creating the log file
139
+ const dir = path.dirname(logFilePath);
140
+ fs.mkdirSync(dir, { recursive: true });
141
+
142
+ // Close any previous descriptor to avoid leaks
143
+ if (this.#fd != null) {
144
+ try {
145
+ fs.closeSync(this.#fd);
146
+ } catch {
147
+ /* noop */
148
+ }
149
+ this.#fd = null;
150
+ }
151
+
152
+ // Open (truncate) now; we’ll append to the same fd later.
153
+ this.#fd = fs.openSync(logFilePath, 'w');
154
+ this.logFile = logFilePath;
155
+ }
156
+
157
+ /**
158
+ * Optional: call to close the file descriptor when done (e.g., on shutdown)
159
+ */
160
+ close() {
161
+ if (this.#fd != null) {
162
+ try {
163
+ fs.closeSync(this.#fd);
164
+ } finally {
165
+ this.#fd = null;
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ const logger = new CompilerLogger();
90
172
 
91
173
  // TODO: a lot of these masks can be imported from @adguard/agtree
92
174
  /**
@@ -154,7 +236,7 @@ const convertRulesToAdgSyntax = (rulesList, excluded) => {
154
236
  }
155
237
  } catch (e) {
156
238
  const message = `Unable to convert rule to AdGuard syntax: "${rule}" due to error: ${e.message}`;
157
- logger.info(message);
239
+ logger.error(message);
158
240
  excludeRule$1(rule, excluded, message);
159
241
  }
160
242
  }
@@ -218,7 +300,7 @@ const convertToUbo = (rules) => {
218
300
  // https://github.com/AdguardTeam/Scriptlets#trusted-scriptlets-restriction
219
301
  // does not work in other blockers
220
302
  const message = `Trusted scriptlets should not be converted to uBO syntax. Rule: "${rule}"`;
221
- logger.info(message);
303
+ logger.warn(message);
222
304
  modified.push('');
223
305
  return;
224
306
  }
@@ -228,7 +310,7 @@ const convertToUbo = (rules) => {
228
310
  modified.push(...convertedRules);
229
311
  } catch (e) {
230
312
  const message = `Unable to convert rule to Ubo syntax: "${rule}" due to error: ${e.message}`;
231
- logger.info(message);
313
+ logger.error(message);
232
314
  }
233
315
  } else {
234
316
  modified.push('');
@@ -1254,7 +1336,7 @@ const cleanupAndOptimizeRules = function (rules, config, optimizationConfig, fil
1254
1336
 
1255
1337
  /* eslint-disable global-require */
1256
1338
 
1257
- const __dirname$3 = path.dirname(new URL(import.meta.url).pathname);
1339
+ const __dirname$3 = path$1.dirname(new URL(import.meta.url).pathname);
1258
1340
 
1259
1341
  const RULES_SEPARATOR = '\r\n';
1260
1342
  let filterIdsPool = [];
@@ -1323,7 +1405,7 @@ let adguardFiltersServerUrl = null;
1323
1405
  */
1324
1406
  const readFile$2 = function (path) {
1325
1407
  try {
1326
- return fs.readFileSync(path, { encoding: 'utf-8' });
1408
+ return fs$1.readFileSync(path, { encoding: 'utf-8' });
1327
1409
  } catch (e) {
1328
1410
  return null;
1329
1411
  }
@@ -1428,15 +1510,15 @@ const calculateChecksum = function (header, rules) {
1428
1510
  * @throws {Error} Throws an error if there is a permission issue or if the directory cannot be created.
1429
1511
  */
1430
1512
  const createDir = (dir) => {
1431
- const { sep } = path;
1432
- const initDir = path.isAbsolute(dir) ? sep : '';
1513
+ const { sep } = path$1;
1514
+ const initDir = path$1.isAbsolute(dir) ? sep : '';
1433
1515
  // eslint-disable-next-line no-undef
1434
1516
  const baseDir = __dirname$3;
1435
1517
 
1436
1518
  return dir.split(sep).reduce((parentDir, childDir) => {
1437
- const curDir = path.resolve(baseDir, parentDir, childDir);
1519
+ const curDir = path$1.resolve(baseDir, parentDir, childDir);
1438
1520
  try {
1439
- fs.mkdirSync(curDir);
1521
+ fs$1.mkdirSync(curDir);
1440
1522
  } catch (err) {
1441
1523
  if (err.code === 'EEXIST') { // curDir already exists!
1442
1524
  return curDir;
@@ -1448,7 +1530,7 @@ const createDir = (dir) => {
1448
1530
  }
1449
1531
 
1450
1532
  const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
1451
- if ((!caughtErr || caughtErr) && (curDir === path.resolve(dir))) {
1533
+ if ((!caughtErr || caughtErr) && (curDir === path$1.resolve(dir))) {
1452
1534
  throw err; // Throw if it's just the last created dir.
1453
1535
  }
1454
1536
  }
@@ -1707,12 +1789,12 @@ const loadLocales = function (dir) {
1707
1789
  filters: {},
1708
1790
  };
1709
1791
 
1710
- const locales = fs.readdirSync(dir);
1792
+ const locales = fs$1.readdirSync(dir);
1711
1793
  // eslint-disable-next-line no-restricted-syntax
1712
1794
  for (const directory of locales) {
1713
- const localeDir = path.join(dir, directory);
1714
- if (fs.lstatSync(localeDir).isDirectory()) {
1715
- const groups = JSON.parse(readFile$2(path.join(localeDir, 'groups.json')));
1795
+ const localeDir = path$1.join(dir, directory);
1796
+ if (fs$1.lstatSync(localeDir).isDirectory()) {
1797
+ const groups = JSON.parse(readFile$2(path$1.join(localeDir, 'groups.json')));
1716
1798
  if (groups) {
1717
1799
  // eslint-disable-next-line no-restricted-syntax
1718
1800
  for (const group of groups) {
@@ -1731,7 +1813,7 @@ const loadLocales = function (dir) {
1731
1813
  }
1732
1814
  }
1733
1815
 
1734
- const tags = JSON.parse(readFile$2(path.join(localeDir, 'tags.json')));
1816
+ const tags = JSON.parse(readFile$2(path$1.join(localeDir, 'tags.json')));
1735
1817
  if (tags) {
1736
1818
  // eslint-disable-next-line no-restricted-syntax
1737
1819
  for (const tag of tags) {
@@ -1750,7 +1832,7 @@ const loadLocales = function (dir) {
1750
1832
  }
1751
1833
  }
1752
1834
 
1753
- const filters = JSON.parse(readFile$2(path.join(localeDir, 'filters.json')));
1835
+ const filters = JSON.parse(readFile$2(path$1.join(localeDir, 'filters.json')));
1754
1836
  if (filters) {
1755
1837
  // eslint-disable-next-line no-restricted-syntax
1756
1838
  for (const filter of filters) {
@@ -1886,13 +1968,13 @@ const removeGroupDescriptions = (inputGroups) => {
1886
1968
  const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadata, obsoleteFilters) {
1887
1969
  logger.info('Writing filters metadata');
1888
1970
 
1889
- const groups = JSON.parse(readFile$2(path.join(filtersDir, '../groups', 'metadata.json')));
1971
+ const groups = JSON.parse(readFile$2(path$1.join(filtersDir, '../groups', 'metadata.json')));
1890
1972
  if (!groups) {
1891
1973
  logger.error('Error reading groups metadata');
1892
1974
  return;
1893
1975
  }
1894
1976
 
1895
- const tags = JSON.parse(readFile$2(path.join(filtersDir, '../tags', 'metadata.json')));
1977
+ const tags = JSON.parse(readFile$2(path$1.join(filtersDir, '../tags', 'metadata.json')));
1896
1978
  if (!tags) {
1897
1979
  logger.error('Error reading tags metadata');
1898
1980
  return;
@@ -1902,17 +1984,17 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
1902
1984
  const parsedLangTagsFiltersMetadata = processFiltersFromMetadata(filtersMetadata);
1903
1985
  const replacedTagKeywordsFiltersMetadata = replaceTagKeywords(parsedLangTagsFiltersMetadata, tags);
1904
1986
 
1905
- const localizations = loadLocales(path.join(filtersDir, '../locales'));
1987
+ const localizations = loadLocales(path$1.join(filtersDir, '../locales'));
1906
1988
 
1907
1989
  // eslint-disable-next-line guard-for-in,no-restricted-syntax
1908
1990
  for (const platform in platformPathsConfig) {
1909
1991
  const config = platformPathsConfig[platform];
1910
- const platformDir = path.join(platformsPath, config.path);
1992
+ const platformDir = path$1.join(platformsPath, config.path);
1911
1993
  createDir(platformDir);
1912
1994
 
1913
1995
  logger.info(`Writing filters metadata: ${config.path}`);
1914
- const filtersFileJson = path.join(platformDir, FILTERS_METADATA_FILE_JSON);
1915
- const filtersFileJs = path.join(platformDir, FILTERS_METADATA_FILE_JS);
1996
+ const filtersFileJson = path$1.join(platformDir, FILTERS_METADATA_FILE_JSON);
1997
+ const filtersFileJs = path$1.join(platformDir, FILTERS_METADATA_FILE_JS);
1916
1998
 
1917
1999
  const replacedExpiresFiltersMetadata = replaceExpires(replacedTagKeywordsFiltersMetadata, config.expires);
1918
2000
 
@@ -1936,12 +2018,12 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
1936
2018
 
1937
2019
  const filtersContent = JSON.stringify(sortMetadataFilters(metadata), null, '\t');
1938
2020
 
1939
- fs.writeFileSync(filtersFileJson, filtersContent, 'utf8');
1940
- fs.writeFileSync(filtersFileJs, filtersContent, 'utf8');
2021
+ fs$1.writeFileSync(filtersFileJson, filtersContent, 'utf8');
2022
+ fs$1.writeFileSync(filtersFileJs, filtersContent, 'utf8');
1941
2023
 
1942
2024
  logger.info(`Writing filters localizations: ${config.path}`);
1943
- const filtersI18nFileJson = path.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
1944
- const filtersI18nFileJs = path.join(platformDir, FILTERS_I18N_METADATA_FILE_JS);
2025
+ const filtersI18nFileJson = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JSON);
2026
+ const filtersI18nFileJs = path$1.join(platformDir, FILTERS_I18N_METADATA_FILE_JS);
1945
2027
 
1946
2028
  let localizedFilters = { ...localizations.filters };
1947
2029
 
@@ -1978,8 +2060,8 @@ const writeFiltersMetadata = function (platformsPath, filtersDir, filtersMetadat
1978
2060
 
1979
2061
  const i18nContent = JSON.stringify(i18nMetadata, null, '\t');
1980
2062
 
1981
- fs.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
1982
- fs.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
2063
+ fs$1.writeFileSync(filtersI18nFileJson, i18nContent, 'utf8');
2064
+ fs$1.writeFileSync(filtersI18nFileJs, i18nContent, 'utf8');
1983
2065
  }
1984
2066
 
1985
2067
  logger.info('Writing filters metadata done');
@@ -1996,7 +2078,7 @@ const writeLocalScriptRules = function (platformsPath) {
1996
2078
  // eslint-disable-next-line guard-for-in,no-restricted-syntax
1997
2079
  for (const platform in platformPathsConfig) {
1998
2080
  const config = platformPathsConfig[platform];
1999
- const platformDir = path.join(platformsPath, config.path);
2081
+ const platformDir = path$1.join(platformsPath, config.path);
2000
2082
 
2001
2083
  const rulesTxt = [];
2002
2084
  const rulesJson = {
@@ -2008,7 +2090,7 @@ const writeLocalScriptRules = function (platformsPath) {
2008
2090
  // because AdGuard Chinese filter has id 224
2009
2091
  // https://github.com/AdguardTeam/FiltersRegistry/blob/master/filters/filter_224_Chinese/metadata.json
2010
2092
  for (let i = 1; i <= LAST_ADGUARD_FILTER_ID; i += 1) {
2011
- const filterRules = readFile$2(path.join(platformDir, PLATFORM_FILTERS_DIR, `${i}.txt`));
2093
+ const filterRules = readFile$2(path$1.join(platformDir, PLATFORM_FILTERS_DIR, `${i}.txt`));
2012
2094
  if (!filterRules) {
2013
2095
  continue;
2014
2096
  }
@@ -2039,13 +2121,13 @@ const writeLocalScriptRules = function (platformsPath) {
2039
2121
  // remove scriptlet rules in local_script_rules.json
2040
2122
  rulesJson.rules = removeScriptletRules(rulesJson.rules);
2041
2123
 
2042
- fs.writeFileSync(
2043
- path.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
2124
+ fs$1.writeFileSync(
2125
+ path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE),
2044
2126
  rulesTxt.join(RULES_SEPARATOR),
2045
2127
  'utf8',
2046
2128
  );
2047
- fs.writeFileSync(
2048
- path.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
2129
+ fs$1.writeFileSync(
2130
+ path$1.join(platformDir, LOCAL_SCRIPT_RULES_FILE_JSON),
2049
2131
  JSON.stringify(rulesJson, null, 4),
2050
2132
  'utf8',
2051
2133
  );
@@ -2064,13 +2146,13 @@ const writeLocalScriptRules = function (platformsPath) {
2064
2146
  * @throws {Error} If the metadata or revision file cannot be read.
2065
2147
  */
2066
2148
  const loadFilterMetadata = function (filterDir, whitelist, blacklist) {
2067
- const metadataFilePath = path.join(filterDir, metadataFile);
2149
+ const metadataFilePath = path$1.join(filterDir, metadataFile);
2068
2150
  const metadataString = readFile$2(metadataFilePath);
2069
2151
  if (!metadataString) {
2070
2152
  throw new Error(`Error reading filter metadata:${filterDir}`);
2071
2153
  }
2072
2154
 
2073
- const revisionFilePath = path.join(filterDir, revisionFile);
2155
+ const revisionFilePath = path$1.join(filterDir, revisionFile);
2074
2156
  const revisionString = readFile$2(revisionFilePath);
2075
2157
  if (!revisionString) {
2076
2158
  throw new Error(`Error reading filter revision:${filterDir}`);
@@ -2131,7 +2213,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
2131
2213
  data = [adbHeader].concat(data);
2132
2214
  }
2133
2215
 
2134
- fs.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2216
+ fs$1.writeFileSync(filterFile, data.join(RULES_SEPARATOR), 'utf8');
2135
2217
  };
2136
2218
 
2137
2219
  /**
@@ -2140,7 +2222,7 @@ const writeFilterFile = function (filterFile, adbHeader, rulesHeader, rules) {
2140
2222
  const writeFilterRules = function (filterId, dir, config, rulesHeader, rules, optimized) {
2141
2223
  createDir(dir);
2142
2224
 
2143
- const filterFile = path.join(dir, `${filterId}${optimized ? '_optimized' : ''}.txt`);
2225
+ const filterFile = path$1.join(dir, `${filterId}${optimized ? '_optimized' : ''}.txt`);
2144
2226
  let rulesList = rules;
2145
2227
 
2146
2228
  // Convert Adguard scriptlets and redirect rules to UBlock syntax.
@@ -2162,7 +2244,7 @@ const writeFilterRules = function (filterId, dir, config, rulesHeader, rules, op
2162
2244
  const correctedHeader = rewriteHeader(rulesHeader);
2163
2245
  const correctedRules = rewriteRules(rulesList);
2164
2246
 
2165
- const correctedFile = path.join(dir, `${filterId}_without_easylist.txt`);
2247
+ const correctedFile = path$1.join(dir, `${filterId}_without_easylist.txt`);
2166
2248
  writeFilterFile(correctedFile, config.configuration.adbHeader, correctedHeader, correctedRules);
2167
2249
  }
2168
2250
  };
@@ -2215,10 +2297,10 @@ const removeRuleDuplicates = function (list) {
2215
2297
  * @param blacklist - Array of filter ids to blacklist
2216
2298
  */
2217
2299
  const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) => {
2218
- const originalRules = readFile$2(path.join(filterDir, filterFile)).split('\r\n');
2300
+ const originalRules = readFile$2(path$1.join(filterDir, filterFile)).split('\r\n');
2219
2301
 
2220
- const metadataFilePath = path.join(filterDir, metadataFile);
2221
- const revisionFilePath = path.join(filterDir, revisionFile);
2302
+ const metadataFilePath = path$1.join(filterDir, metadataFile);
2303
+ const revisionFilePath = path$1.join(filterDir, revisionFile);
2222
2304
 
2223
2305
  const metadata = JSON.parse(readFile$2(metadataFilePath));
2224
2306
  const { filterId } = metadata;
@@ -2274,7 +2356,7 @@ const buildFilter$1 = async (filterDir, platformsPath, whitelist, blacklist) =>
2274
2356
 
2275
2357
  const header = makeHeader(metadataFilePath, revisionFilePath, config.expires);
2276
2358
 
2277
- const platformDir = path.join(platformsPath, config.path, PLATFORM_FILTERS_DIR);
2359
+ const platformDir = path$1.join(platformsPath, config.path, PLATFORM_FILTERS_DIR);
2278
2360
  writeFilterRules(filterId, platformDir, config, header, rules, false);
2279
2361
 
2280
2362
  // add '(Optimized)' to the '! Title:' for optimized filters
@@ -2340,13 +2422,13 @@ const parseDirectory$1 = async (
2340
2422
  blacklist,
2341
2423
  obsoleteFiltersMetadata,
2342
2424
  ) => {
2343
- const items = fs.readdirSync(filtersDir);
2425
+ const items = fs$1.readdirSync(filtersDir);
2344
2426
  // eslint-disable-next-line no-restricted-syntax
2345
2427
  for (const directory of items) {
2346
- const filterDir = path.join(filtersDir, directory);
2347
- if (fs.lstatSync(filterDir).isDirectory()) {
2348
- const metadataFilePath = path.join(filterDir, metadataFile);
2349
- if (fs.existsSync(metadataFilePath)) {
2428
+ const filterDir = path$1.join(filtersDir, directory);
2429
+ if (fs$1.lstatSync(filterDir).isDirectory()) {
2430
+ const metadataFilePath = path$1.join(filterDir, metadataFile);
2431
+ if (fs$1.existsSync(metadataFilePath)) {
2350
2432
  logger.info(`Building filter platforms: ${directory}`);
2351
2433
  // eslint-disable-next-line no-await-in-loop
2352
2434
  await buildFilter$1(filterDir, platformsPath, whitelist, blacklist);
@@ -2464,7 +2546,7 @@ const skipFilter = (metadata) => {
2464
2546
  */
2465
2547
  const create = (reportPath) => {
2466
2548
  if (reportPath) {
2467
- fs.writeFileSync(reportPath, reportData, 'utf8');
2549
+ fs$1.writeFileSync(reportPath, reportData, 'utf8');
2468
2550
  return;
2469
2551
  }
2470
2552
  log(reportData);
@@ -2626,7 +2708,7 @@ const optimizeDomainBlockingRules = async (lines) => {
2626
2708
  /* eslint-disable global-require */
2627
2709
 
2628
2710
 
2629
- const __dirname$2 = path.dirname(new URL(import.meta.url).pathname);
2711
+ const __dirname$2 = path$1.dirname(new URL(import.meta.url).pathname);
2630
2712
 
2631
2713
  const TEMPLATE_FILE = 'template.txt';
2632
2714
  const FILTER_FILE = 'filter.txt';
@@ -2667,11 +2749,11 @@ const DIFF_PATH_TAG = 'Diff-Path:';
2667
2749
  * @returns {*}
2668
2750
  */
2669
2751
  const readFile$1 = function (path) {
2670
- if (!fs.existsSync(path)) {
2752
+ if (!fs$1.existsSync(path)) {
2671
2753
  return null;
2672
2754
  }
2673
2755
 
2674
- return fs.readFileSync(path, { encoding: 'utf-8' });
2756
+ return fs$1.readFileSync(path, { encoding: 'utf-8' });
2675
2757
  };
2676
2758
 
2677
2759
  /**
@@ -2681,7 +2763,7 @@ const readFile$1 = function (path) {
2681
2763
  * @param data
2682
2764
  */
2683
2765
  const writeFile = function (path, data) {
2684
- fs.writeFileSync(path, data, 'utf8');
2766
+ fs$1.writeFileSync(path, data, 'utf8');
2685
2767
  };
2686
2768
 
2687
2769
  /**
@@ -2836,7 +2918,7 @@ const exclude = function (lines, exclusionsFile, excluded) {
2836
2918
 
2837
2919
  exclusions = splitLines(exclusions);
2838
2920
 
2839
- const exclusionsFileName = path.parse(exclusionsFile).base;
2921
+ const exclusionsFileName = path$1.parse(exclusionsFile).base;
2840
2922
  const result = [];
2841
2923
 
2842
2924
  lines.forEach((line, pos) => {
@@ -2979,7 +3061,7 @@ const include = async (filterDir, directiveLine, excluded) => {
2979
3061
 
2980
3062
  const included = externalInclude
2981
3063
  ? downloadFile(url)
2982
- : readFile$1(path.join(filterDir, url));
3064
+ : readFile$1(path$1.join(filterDir, url));
2983
3065
 
2984
3066
  if (included) {
2985
3067
  includedLines = splitLines(included);
@@ -3000,7 +3082,7 @@ const include = async (filterDir, directiveLine, excluded) => {
3000
3082
  let optionsExcludePath;
3001
3083
  switch (name) {
3002
3084
  case EXCLUDE_OPTION:
3003
- optionsExcludePath = path.join(filterDir, value);
3085
+ optionsExcludePath = path$1.join(filterDir, value);
3004
3086
  includedLines = exclude(includedLines, optionsExcludePath, excluded);
3005
3087
  break;
3006
3088
  case STRIP_COMMENTS_OPTION:
@@ -3059,7 +3141,7 @@ const getResolvedPreprocessorIncludes = async function (filterDir, filterName, l
3059
3141
  try {
3060
3142
  rules = await FiltersDownloader.resolveIncludes(lines, filterDir);
3061
3143
  } catch (e) {
3062
- logger.warn(`Error resolving includes in ${filterName}: ${e.message}`);
3144
+ logger.error(`Error resolving includes in ${filterName}: ${e.message}`);
3063
3145
  }
3064
3146
  return rules;
3065
3147
  };
@@ -3175,7 +3257,7 @@ const compile$1 = async function (filterDir, filterName, templateContent, trustL
3175
3257
 
3176
3258
  result = result.filter((line) => !isDiffPathHeaderTag(line));
3177
3259
 
3178
- const excludeFilePath = path.join(filterDir, EXCLUDE_FILE);
3260
+ const excludeFilePath = path$1.join(filterDir, EXCLUDE_FILE);
3179
3261
  result = exclude(result, excludeFilePath, excluded);
3180
3262
 
3181
3263
  result = validateAndFilterRules(result, excluded, invalid, filterName);
@@ -3231,15 +3313,22 @@ const makeRevision = function (path, hash) {
3231
3313
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3232
3314
  */
3233
3315
  const buildFilter = async function (filterDir, whitelist, blacklist) {
3234
- const templateContent = readFile$1(path.join(filterDir, TEMPLATE_FILE));
3316
+ const templateContent = readFile$1(path$1.join(filterDir, TEMPLATE_FILE));
3235
3317
  if (!templateContent) {
3236
3318
  throw new Error('Invalid template');
3237
3319
  }
3238
3320
 
3239
- const metadata = JSON.parse(readFile$1(path.join(filterDir, METADATA_FILE)));
3321
+ const metadata = JSON.parse(readFile$1(path$1.join(filterDir, METADATA_FILE)));
3240
3322
 
3241
3323
  const { filterId } = metadata;
3242
3324
 
3325
+ // check whether the filter is disabled after other checks to avoid unnecessary logging
3326
+ if (metadata.disabled) {
3327
+ logger.warn(`Filter ${filterId} skipped as disabled`);
3328
+ skipFilter(metadata);
3329
+ return;
3330
+ }
3331
+
3243
3332
  if (whitelist && whitelist.length > 0 && whitelist.indexOf(filterId) < 0) {
3244
3333
  logger.info(`Filter ${filterId} skipped due to '--include' option`);
3245
3334
  return;
@@ -3250,16 +3339,9 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3250
3339
  return;
3251
3340
  }
3252
3341
 
3253
- // check whether the filter is disabled after other checks to avoid unnecessary logging
3254
- if (metadata.disabled) {
3255
- logger.warn(`Filter ${filterId} skipped as disabled`);
3256
- skipFilter(metadata);
3257
- return;
3258
- }
3259
-
3260
3342
  const trustLevel = metadata.trustLevel ? metadata.trustLevel : DEFAULT_TRUST_LEVEL;
3261
3343
  // eslint-disable-next-line no-undef
3262
- const trustLevelSettings = path.resolve(__dirname$2, TRUST_LEVEL_DIR, `exclusions-${trustLevel}.txt`);
3344
+ const trustLevelSettings = path$1.resolve(__dirname$2, TRUST_LEVEL_DIR, `exclusions-${trustLevel}.txt`);
3263
3345
 
3264
3346
  const { name: filterName } = metadata;
3265
3347
  logger.info(`Compiling ${filterName}`);
@@ -3279,14 +3361,14 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3279
3361
  const compiledData = compiled.join('\r\n');
3280
3362
 
3281
3363
  logger.info(`Writing filter file, lines:${compiled.length}`);
3282
- writeFile(path.join(filterDir, FILTER_FILE), compiledData);
3364
+ writeFile(path$1.join(filterDir, FILTER_FILE), compiledData);
3283
3365
  logger.info(`Writing excluded file, lines:${excluded.length}`);
3284
- writeFile(path.join(filterDir, EXCLUDED_LINES_FILE), excluded.join('\r\n'));
3366
+ writeFile(path$1.join(filterDir, EXCLUDED_LINES_FILE), excluded.join('\r\n'));
3285
3367
  logger.info('Writing revision file..');
3286
3368
 
3287
3369
  // eslint-disable-next-line no-buffer-constructor
3288
3370
  const hash = Buffer.from(md5(compiledData, { asString: true })).toString('base64').trim();
3289
- const revisionFile = path.join(filterDir, REVISION_FILE);
3371
+ const revisionFile = path$1.join(filterDir, REVISION_FILE);
3290
3372
  const revision = makeRevision(revisionFile, hash);
3291
3373
  writeFile(revisionFile, JSON.stringify(revision, null, '\t'));
3292
3374
  };
@@ -3300,15 +3382,15 @@ const buildFilter = async function (filterDir, whitelist, blacklist) {
3300
3382
  * @returns {Promise<void>} A promise that resolves when all filters and its subdirectories have been processed.
3301
3383
  */
3302
3384
  const parseDirectory = async function (filtersDir, whitelist, blacklist) {
3303
- const items = fs.readdirSync(filtersDir)
3385
+ const items = fs$1.readdirSync(filtersDir)
3304
3386
  .sort((a, b) => getFilterIdFromDirName(a) - getFilterIdFromDirName(b));
3305
3387
 
3306
3388
  // eslint-disable-next-line no-restricted-syntax
3307
3389
  for (const directory of items) {
3308
- const filterDir = path.join(filtersDir, directory);
3309
- if (fs.lstatSync(filterDir).isDirectory()) {
3310
- const template = path.join(filterDir, TEMPLATE_FILE);
3311
- if (fs.existsSync(template)) {
3390
+ const filterDir = path$1.join(filtersDir, directory);
3391
+ if (fs$1.lstatSync(filterDir).isDirectory()) {
3392
+ const template = path$1.join(filterDir, TEMPLATE_FILE);
3393
+ if (fs$1.existsSync(template)) {
3312
3394
  logger.info(`Building filter ${directory}...`);
3313
3395
  // eslint-disable-next-line no-await-in-loop
3314
3396
  await buildFilter(filterDir, whitelist, blacklist);
@@ -3371,14 +3453,14 @@ const OLD_MAC_V2_PLATFORM = 'mac_v2';
3371
3453
  const loadSchemas = (dir) => {
3372
3454
  const schemas = {};
3373
3455
 
3374
- const items = fs.readdirSync(dir);
3456
+ const items = fs$1.readdirSync(dir);
3375
3457
  // eslint-disable-next-line no-restricted-syntax
3376
3458
  for (const f of items) {
3377
3459
  if (f.endsWith(SCHEMA_EXTENSION)) {
3378
3460
  const validationFileName = f.substr(0, f.indexOf(SCHEMA_EXTENSION));
3379
3461
 
3380
3462
  logger.info(`Loading schema for ${validationFileName}`);
3381
- schemas[validationFileName] = JSON.parse(fs.readFileSync(path.join(dir, f)));
3463
+ schemas[validationFileName] = JSON.parse(fs$1.readFileSync(path$1.join(dir, f)));
3382
3464
  }
3383
3465
  }
3384
3466
 
@@ -3398,30 +3480,30 @@ const loadSchemas = (dir) => {
3398
3480
  const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount) => {
3399
3481
  let items;
3400
3482
  try {
3401
- items = fs.readdirSync(dir);
3483
+ items = fs$1.readdirSync(dir);
3402
3484
  } catch (e) {
3403
3485
  logger.info(e.message);
3404
3486
  return false;
3405
3487
  }
3406
3488
  // eslint-disable-next-line no-restricted-syntax
3407
3489
  for (const f of items) {
3408
- const item = path.join(dir, f);
3409
- if (fs.lstatSync(item).isDirectory()) {
3490
+ const item = path$1.join(dir, f);
3491
+ if (fs$1.lstatSync(item).isDirectory()) {
3410
3492
  if (!validateDir(item, validator, schemas, oldSchemas)) {
3411
3493
  return false;
3412
3494
  }
3413
3495
  } else {
3414
- const fileName = path.basename(item, '.json');
3496
+ const fileName = path$1.basename(item, '.json');
3415
3497
  let schema = schemas[fileName];
3416
3498
 
3417
3499
  // Validate `mac` (mac v1) dir with old schemas
3418
- if (path.basename(path.dirname(item)) === OLD_MAC_V1_PLATFORM) {
3500
+ if (path$1.basename(path$1.dirname(item)) === OLD_MAC_V1_PLATFORM) {
3419
3501
  logger.info('Look up old schemas for mac directory');
3420
3502
  schema = oldSchemas[OLD_MAC_V1_PLATFORM][fileName];
3421
3503
  }
3422
3504
 
3423
3505
  // Validate `mac_v2` dir with old schemas
3424
- if (path.basename(path.dirname(item)) === OLD_MAC_V2_PLATFORM) {
3506
+ if (path$1.basename(path$1.dirname(item)) === OLD_MAC_V2_PLATFORM) {
3425
3507
  logger.info('Look up old schemas for mac_v2 directory');
3426
3508
  schema = oldSchemas[OLD_MAC_V2_PLATFORM][fileName];
3427
3509
  }
@@ -3429,7 +3511,7 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3429
3511
  if (schema) {
3430
3512
  logger.info(`Validating ${item}`);
3431
3513
 
3432
- const json = JSON.parse(fs.readFileSync(item));
3514
+ const json = JSON.parse(fs$1.readFileSync(item));
3433
3515
 
3434
3516
  // Validate filters amount
3435
3517
  if (fileName === 'filters') {
@@ -3443,12 +3525,12 @@ const validateDir = (dir, validator, schemas, oldSchemas, filtersRequiredAmount)
3443
3525
  const valid = validate(json);
3444
3526
 
3445
3527
  // json can be updated with default values
3446
- fs.writeFileSync(item, JSON.stringify(json, null, '\t'));
3528
+ fs$1.writeFileSync(item, JSON.stringify(json, null, '\t'));
3447
3529
 
3448
3530
  // duplicate to .js file as well
3449
3531
  const jsFileName = `${fileName}.js`;
3450
- fs.writeFileSync(
3451
- path.join(path.dirname(item), jsFileName),
3532
+ fs$1.writeFileSync(
3533
+ path$1.join(path$1.dirname(item), jsFileName),
3452
3534
  JSON.stringify(json, null, '\t'),
3453
3535
  );
3454
3536
 
@@ -3476,8 +3558,8 @@ const validate$1 = (platformsPath, jsonSchemasConfigDir, filtersRequiredAmount)
3476
3558
 
3477
3559
  const schemas = loadSchemas(jsonSchemasConfigDir);
3478
3560
 
3479
- const oldSchemasMacV1 = loadSchemas(path.join(jsonSchemasConfigDir, OLD_MAC_V1_PLATFORM));
3480
- const oldSchemasMacV2 = loadSchemas(path.join(jsonSchemasConfigDir, OLD_MAC_V2_PLATFORM));
3561
+ const oldSchemasMacV1 = loadSchemas(path$1.join(jsonSchemasConfigDir, OLD_MAC_V1_PLATFORM));
3562
+ const oldSchemasMacV2 = loadSchemas(path$1.join(jsonSchemasConfigDir, OLD_MAC_V2_PLATFORM));
3481
3563
  const oldSchemas = {
3482
3564
  [OLD_MAC_V1_PLATFORM]: oldSchemasMacV1,
3483
3565
  [OLD_MAC_V2_PLATFORM]: oldSchemasMacV2,
@@ -3500,7 +3582,7 @@ const schemaValidator = { validate: validate$1 };
3500
3582
 
3501
3583
  /* eslint-disable global-require */
3502
3584
 
3503
- const __dirname$1 = path.dirname(new URL(import.meta.url).pathname);
3585
+ const __dirname$1 = path$1.dirname(new URL(import.meta.url).pathname);
3504
3586
 
3505
3587
  /**
3506
3588
  * Each filter, group, tag should have two keys.
@@ -3538,13 +3620,13 @@ const WARNING_TYPES = {
3538
3620
  * Sync reads file content
3539
3621
  * @param filePath - path to locales file
3540
3622
  */
3541
- const readFile = (filePath) => fs.readFileSync(path.resolve(__dirname$1, filePath), 'utf8');
3623
+ const readFile = (filePath) => fs$1.readFileSync(path$1.resolve(__dirname$1, filePath), 'utf8');
3542
3624
 
3543
3625
  /**
3544
3626
  * Sync reads directory content
3545
3627
  * @param dirPath - path to directory
3546
3628
  */
3547
- const readDir = (dirPath) => fs.readdirSync(path.resolve(__dirname$1, dirPath), 'utf8');
3629
+ const readDir = (dirPath) => fs$1.readdirSync(path$1.resolve(__dirname$1, dirPath), 'utf8');
3548
3630
 
3549
3631
  /**
3550
3632
  * Validates messages keys
@@ -3589,11 +3671,11 @@ const prepareWarningDetails = (obj) => Object.entries(obj).map(([key, value]) =>
3589
3671
  const getBaseLocaleKeys = (dirPath) => {
3590
3672
  const baseLocaleKeys = {};
3591
3673
 
3592
- const baseLocalePath = path.join(dirPath, BASE_LOCALE);
3674
+ const baseLocalePath = path$1.join(dirPath, BASE_LOCALE);
3593
3675
  const baseLocaleFiles = readDir(baseLocalePath);
3594
3676
 
3595
3677
  baseLocaleFiles.forEach((fileName) => {
3596
- const baseLocaleData = JSON.parse(readFile(path.join(baseLocalePath, fileName)));
3678
+ const baseLocaleData = JSON.parse(readFile(path$1.join(baseLocalePath, fileName)));
3597
3679
  baseLocaleKeys[fileName] = baseLocaleData.flatMap((entry) => Object.keys(entry));
3598
3680
  });
3599
3681
  return baseLocaleKeys;
@@ -3689,7 +3771,7 @@ const validate = (dirPath, requiredLocales) => {
3689
3771
 
3690
3772
  locales.forEach((locale) => {
3691
3773
  const localeWarnings = [];
3692
- const filesList = readDir(path.join(dirPath, locale));
3774
+ const filesList = readDir(path$1.join(dirPath, locale));
3693
3775
  // checks all needed files presence
3694
3776
  const missedFiles = REQUIRED_FILES
3695
3777
  .filter((el) => !filesList.includes(el));
@@ -3707,7 +3789,7 @@ const validate = (dirPath, requiredLocales) => {
3707
3789
 
3708
3790
  // iterate over existent files
3709
3791
  presentFiles.forEach((fileName) => {
3710
- const messagesPath = path.join(dirPath, locale, fileName);
3792
+ const messagesPath = path$1.join(dirPath, locale, fileName);
3711
3793
  let messagesData;
3712
3794
  try {
3713
3795
  messagesData = JSON.parse(readFile(messagesPath));
@@ -4280,7 +4362,8 @@ const EXTENDED_CSS_RULES_PATTERNS = [
4280
4362
  ];
4281
4363
 
4282
4364
  /**
4283
- * Used for `EXTENSION_CHROMIUM`, `EXTENSION_CHROMIUM_MV3`, `EXTENSION_EDGE`, and `EXTENSION_OPERA` platforms.
4365
+ * Used for `EXTENSION_CHROMIUM`, `EXTENSION_CHROMIUM_MV3`, `EXTENSION_EDGE`,
4366
+ * `EXTENSION_OPERA`, and `EXTENSION_OPERA_MV3` platforms.
4284
4367
  */
4285
4368
  const CHROMIUM_BASED_EXTENSION_PATTERNS = [
4286
4369
  ...HTML_FILTERING_MODIFIER_PATTERNS,
@@ -4487,6 +4570,24 @@ const platformsConfig = {
4487
4570
  'adguard_ext_chromium': true,
4488
4571
  },
4489
4572
  },
4573
+ 'EXTENSION_OPERA_MV3': {
4574
+ 'platform': 'ext_opera_mv3',
4575
+ 'path': 'extension/opera-mv3',
4576
+ 'expires': '10 days',
4577
+ 'configuration': {
4578
+ 'removeRulePatterns': [
4579
+ ...CHROMIUM_BASED_EXTENSION_PATTERNS,
4580
+ ...REDIRECT_MODIFIER_PATTERNS,
4581
+ ],
4582
+ 'replacements': null,
4583
+ 'ignoreRuleHints': false,
4584
+ },
4585
+ 'defines': {
4586
+ 'adguard': true,
4587
+ 'adguard_ext_opera_mv3': true,
4588
+ 'adguard_ext_chromium_mv3': true,
4589
+ },
4590
+ },
4490
4591
  'EXTENSION_FIREFOX': {
4491
4592
  'platform': 'ext_ff',
4492
4593
  'path': 'extension/firefox',
@@ -4615,9 +4716,9 @@ const platformsConfig = {
4615
4716
  // Sets configuration compatibility
4616
4717
  setConfiguration({ compatibility: CompatibilityTypes.Corelibs });
4617
4718
 
4618
- const __dirname = path.dirname(new URL(import.meta.url).pathname);
4719
+ const __dirname = path$1.dirname(new URL(import.meta.url).pathname);
4619
4720
 
4620
- const jsonSchemasConfigDir = path.join(__dirname, './schemas/');
4721
+ const jsonSchemasConfigDir = path$1.join(__dirname, './schemas/');
4621
4722
 
4622
4723
  process.on('unhandledRejection', (error) => {
4623
4724
  throw error;