@adguard/filters-compiler 3.2.12 → 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 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
@@ -1187,43 +1187,50 @@ function assertValidStats(filterId, stats) {
1187
1187
  const localOptimizationStatistics = {
1188
1188
  /**
1189
1189
  * 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.
1190
+ * `percent.json` and saves them to disk. Downloads into a staged
1191
+ * directory first and swaps it in only after all downloads succeed,
1192
+ * so a failed refresh leaves any previously cached stats intact.
1192
1193
  *
1193
- * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
1194
+ * When both `includedFilterIds` and `excludedFilterIds` are non-empty,
1195
+ * a filter is processed only if it is in `includedFilterIds` and not in
1196
+ * `excludedFilterIds`.
1194
1197
  *
1195
1198
  * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
1196
1199
  * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
1197
1200
  * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
1198
- * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
1199
1201
  */
1200
1202
  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
1203
  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
- });
1204
+ const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
1205
+ && (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
1214
1206
  const FILTERS_PATH = path$1.join(basePath, FILTERS_DIR_NAME);
1207
+ const STAGED_PREFIX = `${process.pid}_${Date.now()}`;
1208
+ const STAGED_FILTERS_PATH = path$1.join(basePath, `${STAGED_PREFIX}_${FILTERS_DIR_NAME}`);
1215
1209
  /**
1216
1210
  * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1217
1211
  * parallel requests if the underlying transport ever becomes truly async.
1218
1212
  */
1219
1213
  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
- });
1214
+ try {
1215
+ await fs$1.mkdir(STAGED_FILTERS_PATH, { recursive: true });
1216
+ await mapWithConcurrency(configs, DOWNLOAD_CONCURRENCY, async ({ filterId }) => {
1217
+ const dir = path$1.join(STAGED_FILTERS_PATH, String(filterId));
1218
+ const statsPath = path$1.join(dir, STATS_JSON);
1219
+ const content = await downloadOptimizationStats(filterId);
1220
+ await fs$1.mkdir(dir, { recursive: true });
1221
+ await fs$1.writeFile(statsPath, content, 'utf-8');
1222
+ });
1223
+ }
1224
+ catch (error) {
1225
+ // Downloads incomplete: staged dir is garbage, safe to discard.
1226
+ await fs$1.rm(STAGED_FILTERS_PATH, { recursive: true, force: true });
1227
+ throw error;
1228
+ }
1229
+ // Swap only after every fetch succeeded. From here on, staged dir is
1230
+ // a complete cache — never rm it on failure, that'd destroy the only
1231
+ // remaining valid copy once the old FILTERS_PATH is gone.
1232
+ await fs$1.rm(FILTERS_PATH, { recursive: true, force: true });
1233
+ await fs$1.rename(STAGED_FILTERS_PATH, FILTERS_PATH);
1227
1234
  },
1228
1235
  /**
1229
1236
  * Configures `getOptimizationStatistics` to read stats from local files under
package/dist/index.js CHANGED
@@ -1184,43 +1184,50 @@ function assertValidStats(filterId, stats) {
1184
1184
  const localOptimizationStatistics = {
1185
1185
  /**
1186
1186
  * Downloads `stats.json` files for filters listed in the remote
1187
- * `percent.json` and saves them to disk.
1188
- * Existing `stats.json` files will be overwritten.
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.
1189
1190
  *
1190
- * `includedFilterIds` and `excludedFilterIds` cannot both be non-empty.
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`.
1191
1194
  *
1192
1195
  * @param basePath - Directory to save `filters/<filterId>/stats.json` into.
1193
1196
  * @param includedFilterIds - Filter IDs to process; empty (default) processes all.
1194
1197
  * @param excludedFilterIds - Filter IDs to exclude; empty (default) excludes none.
1195
- * @throws {Error} When both `includedFilterIds` and `excludedFilterIds` are non-empty.
1196
1198
  */
1197
1199
  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
1200
  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
- });
1201
+ const configs = percent.config.filter(({ filterId }) => (includedFilterIds.length === 0 || includedFilterIds.includes(filterId))
1202
+ && (excludedFilterIds.length === 0 || !excludedFilterIds.includes(filterId)));
1211
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}`);
1212
1206
  /**
1213
1207
  * Bounds concurrency so a large `percent.json` cannot fan out into unbounded
1214
1208
  * parallel requests if the underlying transport ever becomes truly async.
1215
1209
  */
1216
1210
  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
- });
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;
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);
1224
1231
  },
1225
1232
  /**
1226
1233
  * Configures `getOptimizationStatistics` to read stats from local files under
@@ -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
  /**
package/package.json CHANGED
@@ -58,7 +58,7 @@
58
58
  "typescript": "6.0.3",
59
59
  "vitest": "3.0.5"
60
60
  },
61
- "version": "3.2.12",
61
+ "version": "3.3.0-beta.1",
62
62
  "scripts": {
63
63
  "prebuild": "rimraf dist",
64
64
  "build": "rollup --config rollup.config.js --silent",