📅 You're Invited: Meet the Socket team at RSAC (April 28 – May 1).RSVP →

@adguard/diff-builder

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@adguard/diff-builder - npm Package Compare versions

Comparing version

to
1.1.0

@@ -8,3 +8,11 @@ # Diff Builder Changelog

## [1.1.0] - 2025-03-13
### Changed
- Switched from js 'diff' to system GNU utility 'diff' [#4].
[#4]: https://github.com/AdguardTeam/DiffBuilder/issues/4
[1.1.0]: https://github.com/AdguardTeam/DiffBuilder/compare/v1.0.18...v1.1.0
## [1.0.18] - 2024-04-04

@@ -11,0 +19,0 @@

@@ -1,3 +0,1 @@

/// <reference path="../../../types/diff.d.ts" />
import { TypesOfChanges } from '../common/types-of-change';
import { Resolution } from '../common/patch-name';

@@ -56,18 +54,12 @@ export declare const PATCH_EXTENSION = ".patch";

/**
* Detects type of diff changes: add or delete.
* Creates patch in [RCS format](https://www.gnu.org/software/diffutils/manual/diffutils.html#RCS).
*
* @param line Line of string to parse.
* @param oldContent Content of old file.
* @param newContent Content of new file.
*
* @returns String type: 'Add' or 'Delete' or null if type cannot be parsed.
*/
export declare const detectTypeOfChanges: (line: string) => TypesOfChanges | null;
/**
* Creates patch in [RCS format](https://www.gnu.org/software/diffutils/manual/diffutils.html#RCS).
* @throws Error if the patch creation fails.
*
* @param oldFile Old file.
* @param newFile New file.
*
* @returns Difference between old and new files in RCS format.
* @returns Promise resolving to the difference in RCS format.
*/
export declare const createPatch: (oldFile: string, newFile: string) => string;
export declare const createPatch: (oldContent: string, newContent: string) => Promise<string>;
/**

@@ -74,0 +66,0 @@ * Checks if the provided file content contains a checksum tag within its first 200 characters.

{
"name": "@adguard/diff-builder",
"version": "1.0.18",
"version": "1.1.0",
"description": "A tool for generating differential updates for filter lists.",

@@ -50,3 +50,2 @@ "repository": {

"lint": "eslint --cache . && tsc --noEmit",
"diff-builder": "node -r esbuild-register cli/index.ts build -c",
"test": "jest"

@@ -56,4 +55,3 @@ },

"commander": "^11.1.0",
"crypto-js": "^4.2.0",
"diff": "git+https://github.com/105th/jsdiff.git#2be2e7df90e8eebd99f0385c7b1dc16c2f4dcc1a"
"crypto-js": "^4.2.0"
},

@@ -68,3 +66,2 @@ "devDependencies": {

"@types/crypto-js": "^4.2.1",
"@types/diff": "^5.0.7",
"@types/express": "^4.17.21",

@@ -71,0 +68,0 @@ "@types/jest": "^29.5.6",

@@ -5,12 +5,24 @@ # AdGuard Diff Builder

- [How to install](#how-to-install)
- [How to use](#how-to-use)
- [Use as CLI](#cli)
- [Use as API](#api)
## Contents
- [Prerequisites](#prerequisites)
- [How to Install](#how-to-install)
- [How to Use](#how-to-use)
- [CLI](#cli)
- [API](#api)
- [Algorithm](#algorithm)
## How to install
## Prerequisites
`yarn add @adguard/diff-builder`
- **diff utility**: This tool relies on the standard Unix `diff` utility to generate patches efficiently. Make sure it's installed on your system.
- On macOS: Available by default or through XCode CLI tools
- On Linux: Available by default or install via your package manager (e.g., `apt-get install diffutils`)
- On Windows: Available via WSL or Git Bash
## How to Install
```bash
yarn add @adguard/diff-builder
```
## How to Use

@@ -17,0 +29,0 @@

@@ -1,11 +0,5 @@

/// <reference path="../../types/diff.d.ts" />
import path from 'path';
import fs from 'fs';
// eslint-disable-next-line import/extensions
import { structuredPatch } from 'diff/src/index.js';
import { CHECKSUM_TAG, DIFF_PATH_TAG } from '../common/constants';
import { TypesOfChanges } from '../common/types-of-change';
import { createDiffDirective, parseDiffDirective } from '../common/diff-directive';

@@ -27,7 +21,9 @@ import { calculateChecksumMD5 } from '../common/calculate-checksum';

import { applyRcsPatch } from '../diff-updater/update';
import { getErrorMessage } from '../common/get-error-message';
import { spawnDiff } from '../common/spawn-diff';
import { getTempFilePaths } from '../common/get-temp-file-paths';
import { writeToTempFiles, deleteTempFiles } from '../common/temp-files-utils';
const DEFAULT_PATCH_TTL_SECONDS = 60 * 60 * 24 * 7;
const NEW_LINE_INFO = '\\ No newline at end of file';
let log: (message: string) => void;

@@ -97,138 +93,37 @@

/**
* Detects type of diff changes: add or delete.
* Creates patch in [RCS format](https://www.gnu.org/software/diffutils/manual/diffutils.html#RCS).
*
* @param line Line of string to parse.
* @param oldContent Content of old file.
* @param newContent Content of new file.
*
* @returns String type: 'Add' or 'Delete' or null if type cannot be parsed.
*/
export const detectTypeOfChanges = (line: string): TypesOfChanges | null => {
if (line.startsWith('+')) {
return TypesOfChanges.Add;
}
if (line.startsWith('-')) {
return TypesOfChanges.Delete;
}
return null;
};
/**
* Creates patch in [RCS format](https://www.gnu.org/software/diffutils/manual/diffutils.html#RCS).
* @throws Error if the patch creation fails.
*
* @param oldFile Old file.
* @param newFile New file.
*
* @returns Difference between old and new files in RCS format.
* @returns Promise resolving to the difference in RCS format.
*/
export const createPatch = (oldFile: string, newFile: string): string => {
const { hunks } = structuredPatch(
'oldFile',
'newFile',
oldFile,
newFile,
'',
'',
{
context: 0,
ignoreCase: false,
},
);
export const createPatch = async (oldContent: string, newContent: string): Promise<string> => {
const tempFiles = getTempFilePaths();
const outDiff: string[] = [];
try {
// Wait for files to be written before creating diff
await writeToTempFiles({
oldFilePath: tempFiles.oldFilePath,
oldContent,
newFilePath: tempFiles.newFilePath,
newContent,
});
let stringsToAdd: string[] = [];
let nStringsToDelete = 0;
// Generate the diff using our utility function
const patch = await spawnDiff(tempFiles.oldFilePath, tempFiles.newFilePath);
const collectParsedDiffBlock = (
curIndex: number,
deleted: number,
added: string[],
): string[] => {
if (deleted > 0) {
const deleteFromPosition = curIndex;
const rcsLines = [`d${deleteFromPosition} ${deleted}`];
return rcsLines;
}
if (added.length > 0) {
const addFromPosition = curIndex - 1;
const rcsLines = [
`a${addFromPosition} ${added.length}`,
...added,
];
return rcsLines;
}
return [];
};
hunks.forEach((hunk, hunkIdx) => {
const { oldStart, lines } = hunk;
let fileIndexScanned = oldStart;
// Library will print some debug info so we need to skip this line.
const filteredLines = lines.filter((l) => l !== NEW_LINE_INFO);
for (let index = 0; index < filteredLines.length; index += 1) {
const line = filteredLines[index];
// Detect type of diff operation
const typeOfChange = detectTypeOfChanges(line);
// Library will print some debug info so we need to skip this line.
if (typeOfChange === null) {
throw new Error(`Cannot parse line: ${line}`);
}
if (typeOfChange === TypesOfChanges.Delete) {
// In RCS format we don't need content of deleted string.
nStringsToDelete += 1;
}
if (typeOfChange === TypesOfChanges.Add) {
// Slice "+" from the start of string.
stringsToAdd.push(line.slice(1));
}
// Check type of next line for possible change diff type from 'add'
// to 'delete' or from 'delete' to 'add'.
const nextLineTypeOfChange = index + 1 < filteredLines.length
? detectTypeOfChanges(filteredLines[index + 1])
: null;
// If type will change - save current block
const typeWillChangeOnNextLine = nextLineTypeOfChange && typeOfChange !== nextLineTypeOfChange;
// Or if current line is the last - we need to save collected info.
const isLastLine = index === filteredLines.length - 1;
if (typeWillChangeOnNextLine || isLastLine) {
const diffRCSLines = collectParsedDiffBlock(
fileIndexScanned,
nStringsToDelete,
stringsToAdd,
);
outDiff.push(...diffRCSLines);
// Drop counters
nStringsToDelete = 0;
stringsToAdd = [];
// Move scanned index
fileIndexScanned += index + 1;
}
}
// Check if we need to insert new line to the patch or not
if ((lines.filter((l) => l === NEW_LINE_INFO).length > 0 && lines[lines.length - 1] !== NEW_LINE_INFO)
|| (lines.filter((l) => l === NEW_LINE_INFO).length === 0 && hunkIdx === hunks.length - 1)) {
outDiff[outDiff.length - 1] = outDiff[outDiff.length - 1].concat('\n');
}
});
return outDiff.join('\n');
return patch;
} catch (e) {
log(`Failed to create a patch: ${getErrorMessage(e)}`);
throw e;
} finally {
await deleteTempFiles(tempFiles.oldFilePath, tempFiles.newFilePath, log);
}
};
/**
* Scans `absolutePatchesPath` for files with the "*.patch" pattern and deletes
* Scans `absolutePatchesPath` for files with the `*.${PATCH_EXTENSION}` pattern and deletes
* those whose created epoch timestamp has expired and whose are not empty.

@@ -239,2 +134,4 @@ *

*
* @see {@link PATCH_EXTENSION}
*
* @returns Returns number of deleted patches.

@@ -373,9 +270,15 @@ */

const updatedFile = applyRcsPatch(
splitByLines(oldFile),
diffDirective ? patchLines.slice(1) : patchLines,
diffDirective ? diffDirective.checksum : undefined,
);
try {
const updatedFile = applyRcsPatch(
splitByLines(oldFile),
diffDirective ? patchLines.slice(1) : patchLines,
diffDirective ? diffDirective.checksum : undefined,
);
return updatedFile === newFile;
return updatedFile === newFile;
} catch (e) {
log(`Failed to apply patch to the old file: ${getErrorMessage(e)}`);
return false;
}
};

@@ -464,3 +367,3 @@

// Generate the diff patch.
let patch = createPatch(oldFile, newFileWithUpdatedTags);
let patch = await createPatch(oldFile, newFileWithUpdatedTags);

@@ -467,0 +370,0 @@ // Optionally add a checksum to the patch.

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet