@adguard/filters-compiler 3.2.12 → 3.3.0

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 CHANGED
@@ -467,7 +467,8 @@ always fetched remotely, even when using the local cache.
467
467
  1. Call `download(basePath, includedFilterIds, excludedFilterIds)` to save
468
468
  `stats.json` for filters listed in the remote `percent.json`.
469
469
  `includedFilterIds` limits processing to those IDs (default `[]` = all).
470
- `excludedFilterIds` skips those IDs (default `[]` = none). The two cannot use both.
470
+ `excludedFilterIds` skips those IDs (default `[]` = none). When both are
471
+ given, a filter is processed only if it is included and not excluded.
471
472
  2. Call `use(basePath)` before `compile()` to tell
472
473
  `getOptimizationStatistics` to read stats from local files under
473
474
  `basePath` instead of fetching from the remote server.
package/dist/index.cjs CHANGED
@@ -17,7 +17,8 @@ var util = require('util');
17
17
  var module$1 = require('module');
18
18
  var jsdom = require('jsdom');
19
19
  var crypto = require('crypto');
20
- var moment = require('moment');
20
+ var dateFns = require('date-fns');
21
+ var utc = require('@date-fns/utc');
21
22
  var fs$1 = require('fs/promises');
22
23
  var tldts = require('tldts');
23
24
  var Ajv = require('ajv');
@@ -1187,43 +1188,50 @@ function assertValidStats(filterId, stats) {
1187
1188
  const localOptimizationStatistics = {
1188
1189
  /**
1189
1190
  * Downloads `stats.json` files for filters listed in the remote
1190
- * `percent.json` and saves them to disk.
1191
- * Existing `stats.json` files will be overwritten.
1191
+ * `percent.json` and saves them to disk. Downloads into a staged
1192
+ * directory first and swaps it in only after all downloads succeed,
1193
+ * so a failed refresh leaves any previously cached stats intact.
1192
1194
  *
1193
- * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
1195
+ * When both `includedFilterIds` and `excludedFilterIds` are non-empty,
1196
+ * a filter is processed only if it is in `includedFilterIds` and not in
1197
+ * `excludedFilterIds`.
1194
1198
  *
1195
1199
  * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
1196
1200
  * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
1197
1201
  * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
1198
- * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
1199
1202
  */
1200
1203
  download: async (basePath, includedFilterIds = [], excludedFilterIds = []) => {
1201
- if (includedFilterIds.length > 0 && excludedFilterIds.length > 0) {
1202
- throw new Error('includedFilterIds and excludedFilterIds cannot both be non-empty');
1203
- }
1204
1204
  const percent = JSON.parse(await downloadOptimizationPercent());
1205
- const configs = percent.config.filter(({ filterId }) => {
1206
- if (includedFilterIds.length > 0) {
1207
- return includedFilterIds.includes(filterId);
1208
- }
1209
- if (excludedFilterIds.length > 0) {
1210
- return !excludedFilterIds.includes(filterId);
1211
- }
1212
- return true;
1213
- });
1205
+ const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
1206
+ && (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
1214
1207
  const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
1208
+ const STAGED_PREFIX = `${process.pid}_${Date.now()}`;
1209
+ const STAGED_FILTERS_PATH = path$1.join(basePath, `${STAGED_PREFIX}_${FILTERS_DIR_NAME}`);
1215
1210
  /**
1216
1211
  * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1217
1212
  * parallel requests if the underlying transport ever becomes truly async.
1218
1213
  */
1219
1214
  const DOWNLOAD_CONCURRENCY = 8;
1220
- await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1221
- const dir = path$1.join(FILTERS_PATH, String(filterId));
1222
- const statsPath = path$1.join(dir, STATS_JSON);
1223
- const content = await downloadOptimizationStats(filterId);
1224
- await fs$1.mkdir(dir, { recursive: true });
1225
- await fs$1.writeFile(statsPath, content, 'utf-8');
1226
- });
1215
+ try {
1216
+ await fs$1.mkdir(STAGED_FILTERS_PATH, { recursive: true });
1217
+ await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1218
+ const dir = path$1.join(STAGED_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
+ catch (error) {
1226
+ // Downloads incomplete: staged dir is garbage, safe to discard.
1227
+ await fs$1.rm(STAGED_FILTERS_PATH, { recursive: true, force: true });
1228
+ throw error;
1229
+ }
1230
+ // Swap only after every fetch succeeded. From here on, staged dir is
1231
+ // a complete cache — never rm it on failure, that'd destroy the only
1232
+ // remaining valid copy once the old FILTERS_PATH is gone.
1233
+ await fs$1.rm(FILTERS_PATH, { recursive: true, force: true });
1234
+ await fs$1.rename(STAGED_FILTERS_PATH, FILTERS_PATH);
1227
1235
  },
1228
1236
  /**
1229
1237
  * Configures `getOptimizationStatistics` to read stats from local files under
@@ -1697,7 +1705,7 @@ const makeHeader = function (metadataFile, revisionFile, platformsJsonExpires) {
1697
1705
  `! Title: ${metadata.name}`,
1698
1706
  `! Description: ${metadata.description}`,
1699
1707
  `! Version: ${revision.version}`,
1700
- `! TimeUpdated: ${moment(revision.timeUpdated).format()}`,
1708
+ `! TimeUpdated: ${dateFns.format(revision.timeUpdated, 'yyyy-MM-dd\'T\'HH:mm:ssxxx', { in: utc.utc })}`,
1701
1709
  `! Expires: ${expires} (update frequency)`,
1702
1710
  ];
1703
1711
  };
@@ -2417,8 +2425,8 @@ const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilte
2417
2425
 
2418
2426
  const result = JSON.parse(metadataString);
2419
2427
  result.version = revision.version;
2420
- result.timeUpdated = moment(revision.timeUpdated).format('YYYY-MM-DDTHH:mm:ssZZ');
2421
- result.timeAdded = moment(result.timeAdded).format('YYYY-MM-DDTHH:mm:ssZZ');
2428
+ result.timeUpdated = dateFns.format(revision.timeUpdated, 'yyyy-MM-dd\'T\'HH:mm:ssxx', { in: utc.utc });
2429
+ result.timeAdded = dateFns.format(result.timeAdded, 'yyyy-MM-dd\'T\'HH:mm:ssxx', { in: utc.utc });
2422
2430
  delete result.disabled;
2423
2431
 
2424
2432
  const { filterId } = result;
@@ -4391,6 +4399,25 @@ const REPLACE_MODIFIER_PATTERNS = [
4391
4399
  '\\$(?!#|(path|domain)=.*]|.*removeparam=).*replace(,|=|$)',
4392
4400
  ];
4393
4401
 
4402
+ /**
4403
+ * Pattern to check if rule contains `$urltransform` modifier
4404
+ * and does not contain non-basic modifiers like `$domain` or `$path`.
4405
+ *
4406
+ * Pattern parts:
4407
+ * - `\\$` — modifiers divider
4408
+ * - `(?!#|(path|domain)=.*]|.*removeparam=).*` — negative lookahead to exclude CSS rules (#$#) and rules like
4409
+ * `[$path=...]##.textad,[urltransform="ads"]` and rules with `$removeparam` modifier like `$removeparam=urltransform`
4410
+ * - `.*urltransform` — urltransform modifier itself
4411
+ * - `(,|=|$)` — end of line or modifiers divider, as `$urltransform` can be followed by other modifiers (`,`),
4412
+ * it may have a value (`=`), or it may be the last modifier in the rule (`$`).
4413
+ *
4414
+ * @example
4415
+ * ```||example.com^$urltransform=/firstpath/secondpath/```
4416
+ */
4417
+ const URLTRANSFORM_MODIFIER_PATTERNS = [
4418
+ '\\$(?!#|(path|domain)=.*]|.*removeparam=).*urltransform(,|=|$)',
4419
+ ];
4420
+
4394
4421
  /* eslint-disable max-len */
4395
4422
 
4396
4423
  /**
@@ -4669,6 +4696,7 @@ const SAFARI_BASED_EXTENSION_PATTERNS = [
4669
4696
  ...REMOVEHEADER_MODIFIER_PATTERNS,
4670
4697
  ...MP4_MODIFIER_PATTERNS,
4671
4698
  ...REPLACE_MODIFIER_PATTERNS,
4699
+ ...URLTRANSFORM_MODIFIER_PATTERNS,
4672
4700
  ...STEALTH_MODIFIER_PATTERNS,
4673
4701
  ...COOKIE_MODIFIER_PATTERNS,
4674
4702
  ...APP_MODIFIER_PATTERNS,
@@ -4932,6 +4960,7 @@ const platformsConfig = {
4932
4960
  ...CSS_RULES_PATTERNS,
4933
4961
  ...MP4_MODIFIER_PATTERNS,
4934
4962
  ...REPLACE_MODIFIER_PATTERNS,
4963
+ ...URLTRANSFORM_MODIFIER_PATTERNS,
4935
4964
  ...STEALTH_MODIFIER_PATTERNS,
4936
4965
  ...COOKIE_MODIFIER_PATTERNS,
4937
4966
  ...EMPTY_MODIFIER_PATTERNS,
@@ -4965,6 +4994,7 @@ const platformsConfig = {
4965
4994
  ...HTML_FILTERING_MODIFIER_PATTERNS,
4966
4995
  ...MP4_MODIFIER_PATTERNS,
4967
4996
  ...REPLACE_MODIFIER_PATTERNS,
4997
+ ...URLTRANSFORM_MODIFIER_PATTERNS,
4968
4998
  ...STEALTH_MODIFIER_PATTERNS,
4969
4999
  ...COOKIE_MODIFIER_PATTERNS,
4970
5000
  ...APP_MODIFIER_PATTERNS,
package/dist/index.js CHANGED
@@ -15,7 +15,8 @@ import { TextEncoder, TextDecoder } from 'util';
15
15
  import { createRequire } from 'module';
16
16
  import { JSDOM } from 'jsdom';
17
17
  import crypto from 'crypto';
18
- import moment from 'moment';
18
+ import { format } from 'date-fns';
19
+ import { utc } from '@date-fns/utc';
19
20
  import fs$1 from 'fs/promises';
20
21
  import { parse as parse$1 } from 'tldts';
21
22
  import Ajv from 'ajv';
@@ -1184,43 +1185,50 @@ function assertValidStats(filterId, stats) {
1184
1185
  const localOptimizationStatistics = {
1185
1186
  /**
1186
1187
  * 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.
1188
+ * `percent.json` and saves them to disk. Downloads into a staged
1189
+ * directory first and swaps it in only after all downloads succeed,
1190
+ * so a failed refresh leaves any previously cached stats intact.
1189
1191
  *
1190
- * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
1192
+ * When both `includedFilterIds` and `excludedFilterIds` are non-empty,
1193
+ * a filter is processed only if it is in `includedFilterIds` and not in
1194
+ * `excludedFilterIds`.
1191
1195
  *
1192
1196
  * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
1193
1197
  * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
1194
1198
  * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
1195
- * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
1196
1199
  */
1197
1200
  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');
1200
- }
1201
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
- });
1202
+ const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
1203
+ && (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
1211
1204
  const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
1205
+ const STAGED_PREFIX = `${process.pid}_${Date.now()}`;
1206
+ const STAGED_FILTERS_PATH = path$1.join(basePath, `${STAGED_PREFIX}_${FILTERS_DIR_NAME}`);
1212
1207
  /**
1213
1208
  * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1214
1209
  * parallel requests if the underlying transport ever becomes truly async.
1215
1210
  */
1216
1211
  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
- });
1212
+ try {
1213
+ await fs$1.mkdir(STAGED_FILTERS_PATH, { recursive: true });
1214
+ await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1215
+ const dir = path$1.join(STAGED_FILTERS_PATH, String(filterId));
1216
+ const statsPath = path$1.join(dir, STATS_JSON);
1217
+ const content = await downloadOptimizationStats(filterId);
1218
+ await fs$1.mkdir(dir, { recursive: true });
1219
+ await fs$1.writeFile(statsPath, content, 'utf-8');
1220
+ });
1221
+ }
1222
+ catch (error) {
1223
+ // Downloads incomplete: staged dir is garbage, safe to discard.
1224
+ await fs$1.rm(STAGED_FILTERS_PATH, { recursive: true, force: true });
1225
+ throw error;
1226
+ }
1227
+ // Swap only after every fetch succeeded. From here on, staged dir is
1228
+ // a complete cache — never rm it on failure, that'd destroy the only
1229
+ // remaining valid copy once the old FILTERS_PATH is gone.
1230
+ await fs$1.rm(FILTERS_PATH, { recursive: true, force: true });
1231
+ await fs$1.rename(STAGED_FILTERS_PATH, FILTERS_PATH);
1224
1232
  },
1225
1233
  /**
1226
1234
  * Configures `getOptimizationStatistics` to read stats from local files under
@@ -1694,7 +1702,7 @@ const makeHeader = function (metadataFile, revisionFile, platformsJsonExpires) {
1694
1702
  `! Title: ${metadata.name}`,
1695
1703
  `! Description: ${metadata.description}`,
1696
1704
  `! Version: ${revision.version}`,
1697
- `! TimeUpdated: ${moment(revision.timeUpdated).format()}`,
1705
+ `! TimeUpdated: ${format(revision.timeUpdated, 'yyyy-MM-dd\'T\'HH:mm:ssxxx', { in: utc })}`,
1698
1706
  `! Expires: ${expires} (update frequency)`,
1699
1707
  ];
1700
1708
  };
@@ -2414,8 +2422,8 @@ const loadFilterMetadata = function (filterDir, includedFilterIds, excludedFilte
2414
2422
 
2415
2423
  const result = JSON.parse(metadataString);
2416
2424
  result.version = revision.version;
2417
- result.timeUpdated = moment(revision.timeUpdated).format('YYYY-MM-DDTHH:mm:ssZZ');
2418
- result.timeAdded = moment(result.timeAdded).format('YYYY-MM-DDTHH:mm:ssZZ');
2425
+ result.timeUpdated = format(revision.timeUpdated, 'yyyy-MM-dd\'T\'HH:mm:ssxx', { in: utc });
2426
+ result.timeAdded = format(result.timeAdded, 'yyyy-MM-dd\'T\'HH:mm:ssxx', { in: utc });
2419
2427
  delete result.disabled;
2420
2428
 
2421
2429
  const { filterId } = result;
@@ -4388,6 +4396,25 @@ const REPLACE_MODIFIER_PATTERNS = [
4388
4396
  '\\$(?!#|(path|domain)=.*]|.*removeparam=).*replace(,|=|$)',
4389
4397
  ];
4390
4398
 
4399
+ /**
4400
+ * Pattern to check if rule contains `$urltransform` modifier
4401
+ * and does not contain non-basic modifiers like `$domain` or `$path`.
4402
+ *
4403
+ * Pattern parts:
4404
+ * - `\\$` — modifiers divider
4405
+ * - `(?!#|(path|domain)=.*]|.*removeparam=).*` — negative lookahead to exclude CSS rules (#$#) and rules like
4406
+ * `[$path=...]##.textad,[urltransform="ads"]` and rules with `$removeparam` modifier like `$removeparam=urltransform`
4407
+ * - `.*urltransform` — urltransform modifier itself
4408
+ * - `(,|=|$)` — end of line or modifiers divider, as `$urltransform` can be followed by other modifiers (`,`),
4409
+ * it may have a value (`=`), or it may be the last modifier in the rule (`$`).
4410
+ *
4411
+ * @example
4412
+ * ```||example.com^$urltransform=/firstpath/secondpath/```
4413
+ */
4414
+ const URLTRANSFORM_MODIFIER_PATTERNS = [
4415
+ '\\$(?!#|(path|domain)=.*]|.*removeparam=).*urltransform(,|=|$)',
4416
+ ];
4417
+
4391
4418
  /* eslint-disable max-len */
4392
4419
 
4393
4420
  /**
@@ -4666,6 +4693,7 @@ const SAFARI_BASED_EXTENSION_PATTERNS = [
4666
4693
  ...REMOVEHEADER_MODIFIER_PATTERNS,
4667
4694
  ...MP4_MODIFIER_PATTERNS,
4668
4695
  ...REPLACE_MODIFIER_PATTERNS,
4696
+ ...URLTRANSFORM_MODIFIER_PATTERNS,
4669
4697
  ...STEALTH_MODIFIER_PATTERNS,
4670
4698
  ...COOKIE_MODIFIER_PATTERNS,
4671
4699
  ...APP_MODIFIER_PATTERNS,
@@ -4929,6 +4957,7 @@ const platformsConfig = {
4929
4957
  ...CSS_RULES_PATTERNS,
4930
4958
  ...MP4_MODIFIER_PATTERNS,
4931
4959
  ...REPLACE_MODIFIER_PATTERNS,
4960
+ ...URLTRANSFORM_MODIFIER_PATTERNS,
4932
4961
  ...STEALTH_MODIFIER_PATTERNS,
4933
4962
  ...COOKIE_MODIFIER_PATTERNS,
4934
4963
  ...EMPTY_MODIFIER_PATTERNS,
@@ -4962,6 +4991,7 @@ const platformsConfig = {
4962
4991
  ...HTML_FILTERING_MODIFIER_PATTERNS,
4963
4992
  ...MP4_MODIFIER_PATTERNS,
4964
4993
  ...REPLACE_MODIFIER_PATTERNS,
4994
+ ...URLTRANSFORM_MODIFIER_PATTERNS,
4965
4995
  ...STEALTH_MODIFIER_PATTERNS,
4966
4996
  ...COOKIE_MODIFIER_PATTERNS,
4967
4997
  ...APP_MODIFIER_PATTERNS,
@@ -52,15 +52,17 @@ export declare function assertValidStats(filterId: number, stats: unknown): asse
52
52
  export declare const localOptimizationStatistics: {
53
53
  /**
54
54
  * Downloads `stats.json` files for filters listed in the remote
55
- * `percent.json` and saves them to disk.
56
- * Existing `stats.json` files will be overwritten.
55
+ * `percent.json` and saves them to disk. Downloads into a staged
56
+ * directory first and swaps it in only after all downloads succeed,
57
+ * so a failed refresh leaves any previously cached stats intact.
57
58
  *
58
- * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
59
+ * When both `includedFilterIds` and `excludedFilterIds` are non-empty,
60
+ * a filter is processed only if it is in `includedFilterIds` and not in
61
+ * `excludedFilterIds`.
59
62
  *
60
63
  * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
61
64
  * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
62
65
  * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
63
- * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
64
66
  */
65
67
  download: (basePath: string, includedFilterIds?: number[], excludedFilterIds?: number[]) => Promise<void>;
66
68
  /**
@@ -52,15 +52,17 @@ export declare function assertValidStats(filterId: number, stats: unknown): asse
52
52
  export declare const localOptimizationStatistics: {
53
53
  /**
54
54
  * Downloads `stats.json` files for filters listed in the remote
55
- * `percent.json` and saves them to disk.
56
- * Existing `stats.json` files will be overwritten.
55
+ * `percent.json` and saves them to disk. Downloads into a staged
56
+ * directory first and swaps it in only after all downloads succeed,
57
+ * so a failed refresh leaves any previously cached stats intact.
57
58
  *
58
- * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
59
+ * When both `includedFilterIds` and `excludedFilterIds` are non-empty,
60
+ * a filter is processed only if it is in `includedFilterIds` and not in
61
+ * `excludedFilterIds`.
59
62
  *
60
63
  * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
61
64
  * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
62
65
  * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
63
- * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
64
66
  */
65
67
  download: (basePath: string, includedFilterIds?: number[], excludedFilterIds?: number[]) => Promise<void>;
66
68
  /**
@@ -5,6 +5,7 @@
5
5
  ! ##+js
6
6
  ! #$#
7
7
  ! $replace
8
+ ! $urltransform
8
9
  ! #%#
9
10
  ! #@%#
10
11
  !
@@ -9,5 +9,6 @@
9
9
  #%#//scriptlet('trusted-
10
10
  #%#//scriptlet("trusted-
11
11
  $replace
12
+ $urltransform
12
13
  #%#
13
- #@%#
14
+ #@%#
@@ -7,5 +7,6 @@
7
7
  ##+js
8
8
  #$#
9
9
  $replace
10
+ $urltransform
10
11
  #%#
11
- #@%#
12
+ #@%#
package/package.json CHANGED
@@ -21,20 +21,21 @@
21
21
  "pnpm": ">=10.33.4 <11"
22
22
  },
23
23
  "dependencies": {
24
- "@adguard/agtree": "4.1.2",
24
+ "@adguard/agtree": "4.2.1",
25
25
  "@adguard/css-tokenizer": "1.2.0",
26
26
  "@adguard/ecss-tree": "2.0.1",
27
- "@adguard/extended-css": "2.1.1",
27
+ "@adguard/extended-css": "2.2.0",
28
28
  "@adguard/filters-downloader": "2.4.4",
29
29
  "@adguard/logger": "2.0.0",
30
- "@adguard/scriptlets": "2.3.1",
31
- "@adguard/tsurlfilter": "4.0.5",
30
+ "@adguard/scriptlets": "2.5.1",
31
+ "@adguard/tsurlfilter": "6.0.3",
32
+ "@date-fns/utc": "2.1.1",
32
33
  "@eslint/css-tree": "3.6.6",
33
34
  "ajv": "8.17.1",
34
35
  "child_process": "1.0.2",
36
+ "date-fns": "^4.4.0",
35
37
  "jsdom": "21.1.2",
36
38
  "md5": "2.3.0",
37
- "moment": "2.30.1",
38
39
  "tldts": "5.7.112",
39
40
  "utf8": "3.0.0"
40
41
  },
@@ -58,7 +59,7 @@
58
59
  "typescript": "6.0.3",
59
60
  "vitest": "3.0.5"
60
61
  },
61
- "version": "3.2.12",
62
+ "version": "3.3.0",
62
63
  "scripts": {
63
64
  "prebuild": "rimraf dist",
64
65
  "build": "rollup --config rollup.config.js --silent",