diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1ac41a5..8459f3a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -6,7 +6,7 @@ on: types: - completed branches: - - master + - main jobs: publish: @@ -27,15 +27,11 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "npm" - registry-url: https://registry.npmjs.org - cache-dependency-path: package-lock.json - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Install dependencies - run: npm install + run: npm ci - name: Build the project run: npm run build @@ -43,5 +39,5 @@ jobs: - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true run: npx semantic-release diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40f02cf..82ca421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI Pipeline on: push: branches: - - master + - main pull_request: branches: - - master + - main jobs: build: @@ -19,23 +19,11 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "npm" - registry-url: https://registry.npmjs.org - cache-dependency-path: package-lock.json - - - name: Retrieve dependencies - id: "modules_cache" - uses: actions/cache@v4 - with: - path: node_modules - key: node-modules-${{ runner.os }}-${{ hashFiles('package-lock.json') }} - restore-keys: | - node-modules-${{ runner.os }}- - name: Install dependencies - if: steps.modules_cache.outputs.cache-hit != 'true' - run: npm install + run: npm ci - name: Run typescript run: npm run tsc diff --git a/README.md b/README.md index 2433cbd..a5449eb 100644 --- a/README.md +++ b/README.md @@ -516,6 +516,216 @@ diff.on("finish", () => console.log("Your data has been processed. The full diff diff.on("error", (err) => console.log(err)) ``` +
+ +### getTextDiff + +```js +import { getTextDiff } from "@donedeal0/superdiff"; +``` + +Compares two texts and returns a diff for each characters, words or sentence, depending on your preference. + +The output is optimized by default to produce a readable, visual diff (like GitHub or Git). A strict mode that tracks exact token moves and updates is also available. + +All language subtleties (Unicode, CJK scripts, locale-aware sentence segmentation, etc.) are handled. + +#### FORMAT + +**Input** + +```ts + previousText: string | null | undefined, + currentText: string | null | undefined, + options?: { + showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default. + separation?: "character" | "word" | "sentence", // "word" by default + mode?: "visual" | "strict", // "visual" by default + ignoreCase?: boolean, // false by default + ignorePunctuation?: boolean, // false by default + locale?: Intl.Locale | string // english by default + } +``` +- `previousText`: the original text. +- `currentText`: the new text. +- `options` + - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). + - `moved` and `updated` are only available in `strict` mode. + - `separation` whether you want a `character`, `word` or `sentence` based diff. + - `mode`: + - `visual` (default): optimized for readability. Token moves are ignored so insertions don’t cascade and break equality (recommended for UI diffing). + - `strict`: tracks token moves exactly. Semantically precise, but noisier (a simple addition will move all the next tokens, breaking equality). + - `ignoreCase`: if set to `true` `hello` and `HELLO` will be considered equal. + - `ignorePunctuation`: if set to `true` `hello!` and `hello` will be considered equal. + - `locale`: the locale of your text. + +**Output** + +```ts +type TextDiff = { + type: "text"; + status: "added" | "deleted" | "equal" | "updated"; + diff: { + value: string; + previousValue?: string + status: "added" | "deleted" | "equal" | "moved" | "updated"; + currentIndex: number | null; + previousIndex: number | null; + }[]; +}; +``` + +#### USAGE + +**VISUAL MODE** + +`visual` is optimized for readability. Token moves are ignored so insertions don’t cascade and break equality (recommended for UI diffing). Token updates are rendered as two `added` and `deleted` entries. + +This mode is based on a [longest common subsequence (LCS) computation](https://en.wikipedia.org/wiki/Longest_common_subsequence), similar to Git and GitHub diffs. + +**Input** + +```diff +getTextDiff( +- "The brown fox jumped high", ++ "The orange cat has jumped", +{ mode: "visual", separation: "word" } +); +``` + +**Output** + +```diff +{ + type: "text", ++ status: "updated", + diff: [ + { + value: 'The', + status: 'equal', + currentIndex: 0, + previousIndex: 0 + }, +- { +- value: "brown", +- status: "deleted", +- currentIndex: null, +- previousIndex: 1, +- } +- { +- value: "fox", +- status: "deleted", +- currentIndex: null, +- previousIndex: 2, +- } ++ { ++ value: "orange", ++ status: "added", ++ currentIndex: 1, ++ previousIndex: null, ++ }, ++ { ++ value: "cat", ++ status: "added", ++ currentIndex: 2, ++ previousIndex: null, ++ }, ++ { ++ value: "has", ++ status: "added", ++ currentIndex: 3, ++ previousIndex: null, ++ }, + { + value: "jumped", + status: "equal", + currentIndex: 4, + previousIndex: 3, + }, +- { +- value: "high", +- status: "deleted", +- currentIndex: null, +- previousIndex: 4, +- } + ], + } +``` + +**STRICT MODE** + +`strict` tracks token moves exactly. Semantically precise, but noisier (a simple addition will move all the next tokens, breaking equality). It also considers direct token swaps as `updated`. + +**Input** + +```diff +getTextDiff( +- "The brown fox jumped high", ++ "The orange cat has jumped", +{ mode: "strict", separation: "word" } +); +``` + +**Output** + +```diff +{ + type: "text", ++ status: "updated", + diff: [ + { + value: 'The', + status: 'equal', + currentIndex: 0, + previousIndex: 0 + }, ++ { ++ value: "orange", ++ previousValue: "brown", ++ status: "updated", ++ currentIndex: 1, ++ previousIndex: null, ++ }, ++ { ++ value: "cat", ++ previousValue: "fox", ++ status: "updated", ++ currentIndex: 2, ++ previousIndex: null, ++ }, ++ { ++ value: "has", ++ status: "added", ++ currentIndex: 3, ++ previousIndex: null, ++ }, ++ { ++ value: "jumped", ++ status: "moved", ++ currentIndex: 4, ++ previousIndex: 3, ++ }, +- { +- value: "high", +- status: "deleted", +- currentIndex: null, +- previousIndex: 4, +- } + ], + } +``` + +#### TOKEN STATUSES + +| Status | Represents | Index meaning | +| ------- | ------------- | --------------------------------------- | +| **equal** | same token | both indexes valid | +| **added** | new token | `previousIndex = null` | +| **deleted** | removed token | `currentIndex = null` | +| **moved** | same token (only in `strict` mode) | both indexes valid | +| **updated** | replacement (only in `strict` mode) | no shared identity, one index only | + +
### isEqual diff --git a/benchmark/texts.ts b/benchmark/texts.ts new file mode 100644 index 0000000..1301750 --- /dev/null +++ b/benchmark/texts.ts @@ -0,0 +1,41 @@ +import * as Diff from "diff"; +import { getTextDiff } from "../src"; +import { bench } from "./utils"; + +export function generateText(wordCount: number, mutate = false): string { + const baseWords = []; + for (let i = 0; i < wordCount; i++) { + baseWords.push(`word${i}`); + } + if (!mutate) return baseWords.join(" "); + const mutated = [...baseWords]; + mutated[100] = "changed_word"; + mutated.splice(500, 0, "inserted_word"); + mutated.splice(800, 1); + + return mutated.join(" "); +} + +export function runTextBench10K() { + const prev = generateText(10_000); + const curr = generateText(10_000, true); + console.log("\nText diff – 10k words"); + + const diff = bench("diff", 1, () => Diff.diffWords(prev, curr)); + const superdiff = bench("Superdiff", 1, () => { + getTextDiff(prev, curr, { separation: "word", mode: "strict" }); + }); + return { superdiff, diff }; +} + +export function runTextBench100K() { + const prev = generateText(100_000); + const curr = generateText(100_000, true); + console.log("\nText diff – 100k words"); + + const diff = bench("diff", 1, () => Diff.diffWords(prev, curr)); + const superdiff = bench("Superdiff", 1, () => { + getTextDiff(prev, curr, { separation: "word", mode: "visual" }); + }); + return { superdiff, diff }; +} diff --git a/package-lock.json b/package-lock.json index cd3a46e..20d944f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "blob-polyfill": "^9.0.20240710", "deep-diff": "^1.0.2", "deep-object-diff": "^1.1.9", + "diff": "^8.0.2", "eslint": "^9.21.0", "husky": "^9.1.7", "jest": "^29.7.0", @@ -146,7 +147,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -618,6 +618,7 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.1.90" } @@ -732,7 +733,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -756,7 +756,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2951,6 +2950,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2990,7 +2990,6 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -3537,6 +3536,7 @@ "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.0.tgz", "integrity": "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q==", "dev": true, + "peer": true, "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", @@ -4261,6 +4261,7 @@ "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.1.tgz", "integrity": "sha512-K0w+5220TM4HZTthE5dDpIuFrnkN1NfTGPidJFm04ULT1DEZ9WG89VNXN7F0c+6nMEpWgqmPvb7vY7JkB2jyyA==", "dev": true, + "peer": true, "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", @@ -4285,6 +4286,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, + "peer": true, "engines": { "node": ">=16" }, @@ -4304,6 +4306,7 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -4338,7 +4341,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -4758,8 +4760,7 @@ "version": "18.11.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -4771,7 +4772,8 @@ "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/stack-utils": { "version": "2.0.3", @@ -4849,7 +4851,6 @@ "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", @@ -5079,6 +5080,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -5088,25 +5090,29 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -5117,13 +5123,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -5136,6 +5144,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -5145,6 +5154,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -5153,13 +5163,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -5176,6 +5188,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -5189,6 +5202,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -5201,6 +5215,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", @@ -5215,6 +5230,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" @@ -5224,13 +5240,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/abab": { "version": "2.0.6", @@ -5246,7 +5264,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5270,6 +5287,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, + "peer": true, "peerDependencies": { "acorn": "^8" } @@ -5324,7 +5342,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5341,6 +5358,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -5375,6 +5393,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -5419,7 +5438,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/arr-diff": { "version": "4.0.0", @@ -5435,7 +5455,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true + "dev": true, + "peer": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -5703,7 +5724,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -5816,6 +5836,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5855,6 +5876,7 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, + "peer": true, "engines": { "node": ">=6.0" } @@ -5896,6 +5918,7 @@ "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, + "peer": true, "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -5917,6 +5940,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5932,6 +5956,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5948,6 +5973,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -5959,6 +5985,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5970,13 +5997,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/cli-highlight/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -5986,6 +6015,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5998,6 +6028,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "peer": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -6016,6 +6047,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "peer": true, "engines": { "node": ">=10" } @@ -6025,6 +6057,7 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, + "peer": true, "dependencies": { "string-width": "^4.2.0" }, @@ -6072,6 +6105,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -6080,7 +6114,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -6109,6 +6144,7 @@ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, + "peer": true, "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" @@ -6152,6 +6188,7 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", "dev": true, + "peer": true, "dependencies": { "compare-func": "^2.0.0" }, @@ -6164,6 +6201,7 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz", "integrity": "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==", "dev": true, + "peer": true, "dependencies": { "@types/semver": "^7.5.5", "conventional-commits-filter": "^5.0.0", @@ -6183,6 +6221,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -6195,6 +6234,7 @@ "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, + "peer": true, "engines": { "node": ">=18" } @@ -6204,6 +6244,7 @@ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz", "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", "dev": true, + "peer": true, "dependencies": { "meow": "^13.0.0" }, @@ -6219,6 +6260,7 @@ "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -6237,13 +6279,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -6585,10 +6629,11 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -6634,6 +6679,7 @@ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, + "peer": true, "dependencies": { "is-obj": "^2.0.0" }, @@ -6661,6 +6707,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, + "peer": true, "dependencies": { "readable-stream": "^2.0.2" } @@ -6700,13 +6747,15 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -6881,6 +6930,7 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -6890,6 +6940,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -6930,7 +6981,8 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -6968,7 +7020,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7018,6 +7069,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -7050,7 +7102,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7342,6 +7393,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.x" } @@ -7498,6 +7550,7 @@ "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -7510,6 +7563,7 @@ "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", "dev": true, + "peer": true, "dependencies": { "semver-regex": "^4.0.5", "super-regex": "^1.0.0" @@ -7602,6 +7656,7 @@ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, + "peer": true, "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -7658,6 +7713,7 @@ "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -7763,6 +7819,7 @@ "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", "dev": true, + "peer": true, "dependencies": { "argv-formatter": "~1.0.0", "spawn-error-forwarder": "~1.0.0", @@ -7809,7 +7866,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", @@ -7874,6 +7932,7 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, + "peer": true, "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -7895,6 +7954,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -7946,6 +8006,7 @@ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, + "peer": true, "engines": { "node": "*" } @@ -7955,6 +8016,7 @@ "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", "dev": true, + "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -7967,6 +8029,7 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "peer": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -7978,7 +8041,8 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", @@ -8118,6 +8182,7 @@ "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.4.tgz", "integrity": "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==", "dev": true, + "peer": true, "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" @@ -8151,6 +8216,7 @@ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true, + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8215,6 +8281,7 @@ "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", "dev": true, + "peer": true, "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" @@ -8302,6 +8369,7 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -8352,7 +8420,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/isexe": { "version": "2.0.0", @@ -11631,7 +11700,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11747,6 +11817,7 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -11762,6 +11833,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, + "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -11775,6 +11847,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -11794,6 +11867,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "peer": true, "engines": { "node": ">=6.11.5" } @@ -11942,6 +12016,7 @@ "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.1.0.tgz", "integrity": "sha512-+pvwa14KZL74MVXjYdPR3nSInhGhNvPce/3mqLVZT2oUvt654sL1XImFuLZ1pkA866IYZ3ikDTOFUIC7XzpZZg==", "dev": true, + "peer": true, "dependencies": { "ansi-escapes": "^7.0.0", "chalk": "^5.3.0", @@ -11962,6 +12037,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "peer": true, "dependencies": { "environment": "^1.0.0" }, @@ -11977,6 +12053,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, + "peer": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -11999,6 +12076,7 @@ "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -12140,7 +12218,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/nerf-dart": { "version": "1.0.0", @@ -12153,6 +12232,7 @@ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", "dev": true, + "peer": true, "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -12181,6 +12261,7 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, + "peer": true, "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", @@ -12195,6 +12276,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -14242,7 +14324,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14438,6 +14519,7 @@ "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -14465,6 +14547,7 @@ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -14596,13 +14679,15 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true + "dev": true, + "peer": true }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, + "peer": true, "dependencies": { "parse5": "^6.0.1" } @@ -14611,7 +14696,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/path-exists": { "version": "4.0.0", @@ -14709,6 +14795,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -14728,6 +14815,7 @@ "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", "dev": true, + "peer": true, "dependencies": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" @@ -14741,6 +14829,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, + "peer": true, "dependencies": { "locate-path": "^2.0.0" }, @@ -14753,6 +14842,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, + "peer": true, "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -14766,6 +14856,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, + "peer": true, "dependencies": { "p-try": "^1.0.0" }, @@ -14778,6 +14869,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, + "peer": true, "dependencies": { "p-limit": "^1.1.0" }, @@ -14790,6 +14882,7 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -14799,6 +14892,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -14962,7 +15056,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "dev": true, + "peer": true }, "node_modules/prompts": { "version": "2.4.2", @@ -15035,6 +15130,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -15075,6 +15171,7 @@ "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "dev": true, + "peer": true, "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", @@ -15092,6 +15189,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", "dev": true, + "peer": true, "engines": { "node": ">=16" }, @@ -15104,6 +15202,7 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, + "peer": true, "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", @@ -15123,6 +15222,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.22.13", "index-to-position": "^0.1.2", @@ -15140,6 +15240,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", "dev": true, + "peer": true, "engines": { "node": ">=16" }, @@ -15152,6 +15253,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15323,7 +15425,8 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -15349,6 +15452,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -15411,7 +15515,8 @@ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semantic-release/node_modules/@octokit/plugin-paginate-rest": { "version": "13.2.1", @@ -15419,6 +15524,7 @@ "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/types": "^15.0.1" }, @@ -15435,6 +15541,7 @@ "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/openapi-types": "^26.0.0" } @@ -15444,6 +15551,7 @@ "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, + "peer": true, "engines": { "node": ">=18" } @@ -15454,6 +15562,7 @@ "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/core": "^7.0.0", "@octokit/plugin-paginate-rest": "^13.0.0", @@ -15485,6 +15594,7 @@ "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", @@ -15512,6 +15622,7 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -15524,6 +15635,7 @@ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, + "peer": true, "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" @@ -15540,6 +15652,7 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", "dev": true, + "peer": true, "dependencies": { "escape-string-regexp": "5.0.0" }, @@ -15555,6 +15668,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -15567,6 +15681,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.0.tgz", "integrity": "sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==", "dev": true, + "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", @@ -15593,6 +15708,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, + "peer": true, "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" @@ -15609,6 +15725,7 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.0.tgz", "integrity": "sha512-4nw3vOVR+vHUOT8+U4giwe2tcGv+R3pwwRidUe67DoMBTjhrfr6rZYJVVwdkBE+Um050SG+X9tf0Jo4fOpn01w==", "dev": true, + "peer": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -15621,6 +15738,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true, + "peer": true, "engines": { "node": ">=18.18.0" } @@ -15630,6 +15748,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -15642,6 +15761,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -15653,7 +15773,8 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/semantic-release/node_modules/npm": { "version": "10.9.4", @@ -15731,6 +15852,7 @@ ], "dev": true, "license": "Artistic-2.0", + "peer": true, "workspaces": [ "docs", "smoke-tests", @@ -15821,6 +15943,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, + "peer": true, "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" @@ -18103,7 +18226,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18360,6 +18482,7 @@ "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -18372,6 +18495,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -18384,6 +18508,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -18396,6 +18521,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "peer": true, "engines": { "node": ">=14" }, @@ -18408,6 +18534,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -18420,6 +18547,7 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -18442,6 +18570,7 @@ "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", "dev": true, + "peer": true, "dependencies": { "semver": "^7.3.5" }, @@ -18457,6 +18586,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -18469,6 +18599,7 @@ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -18481,6 +18612,7 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -18517,6 +18649,7 @@ "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, + "peer": true, "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", @@ -18531,6 +18664,7 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, + "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -18550,6 +18684,7 @@ "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, + "peer": true, "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -18591,7 +18726,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", - "dev": true + "dev": true, + "peer": true }, "node_modules/spdx-correct": { "version": "3.2.0", @@ -18630,6 +18766,7 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", "dev": true, + "peer": true, "dependencies": { "through2": "~2.0.0" } @@ -18669,6 +18806,7 @@ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", "dev": true, + "peer": true, "dependencies": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -18679,6 +18817,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -18810,6 +18949,7 @@ "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", "dev": true, + "peer": true, "dependencies": { "function-timeout": "^1.0.1", "time-span": "^5.1.0" @@ -18826,6 +18966,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -18838,6 +18979,7 @@ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -18854,6 +18996,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -18863,6 +19006,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -18920,6 +19064,7 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -18980,6 +19125,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -18998,6 +19144,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -19032,6 +19179,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -19041,6 +19189,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -19055,6 +19204,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -19069,13 +19219,15 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/terser/node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -19144,6 +19296,7 @@ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, + "peer": true, "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -19154,6 +19307,7 @@ "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", "dev": true, + "peer": true, "dependencies": { "convert-hrtime": "^5.0.0" }, @@ -19212,7 +19366,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -19290,6 +19443,7 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", "dev": true, + "peer": true, "engines": { "node": ">= 0.4" }, @@ -19331,7 +19485,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -19370,6 +19523,16 @@ } } }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tsup": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", @@ -19439,7 +19602,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -19454,6 +19616,456 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -19505,7 +20117,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19551,6 +20162,7 @@ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "optional": true, + "peer": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -19573,6 +20185,7 @@ "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -19582,6 +20195,7 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, + "peer": true, "engines": { "node": ">=18" }, @@ -19683,7 +20297,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", @@ -19743,6 +20358,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -19781,6 +20397,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -19827,6 +20444,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, + "peer": true, "engines": { "node": ">=10.13.0" } @@ -19836,6 +20454,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -19849,6 +20468,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -19857,7 +20477,8 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/whatwg-encoding": { "version": "2.0.0", @@ -19925,7 +20546,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -20091,6 +20713,7 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, + "peer": true, "engines": { "node": ">=0.4" } diff --git a/package.json b/package.json index 12647da..679e5ad 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "blob-polyfill": "^9.0.20240710", "deep-diff": "^1.0.2", "deep-object-diff": "^1.1.9", + "diff": "^8.0.2", "eslint": "^9.21.0", "husky": "^9.1.7", "jest-environment-jsdom": "^29.7.0", diff --git a/src/index.ts b/src/index.ts index 5a2e03d..f361c2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export { getObjectDiff } from "./lib/object-diff"; export { getListDiff } from "./lib/list-diff"; export { isEqual, isObject } from "./lib/utils"; +export { getTextDiff } from "./lib/text-diff"; export * from "./models/list"; export * from "./models/object"; export * from "./models/stream"; diff --git a/src/lib/text-diff/index.ts b/src/lib/text-diff/index.ts new file mode 100644 index 0000000..671bdde --- /dev/null +++ b/src/lib/text-diff/index.ts @@ -0,0 +1,51 @@ +import { + DEFAULT_TEXT_DIFF_OPTIONS, + TextDiff, + TextDiffOptions, + TextStatus, +} from "@models/text"; +import { tokenizeText } from "./tokenize"; +import { getStrictTextDiff } from "./strict"; +import { getLCSTextDiff } from "./lcs"; + +export function getTextDiff( + previousText: string | null | undefined, + currentText: string | null | undefined, + options: TextDiffOptions = DEFAULT_TEXT_DIFF_OPTIONS, +): TextDiff { + const previousTokens = tokenizeText(previousText, options); + const currentTokens = tokenizeText(currentText, options); + if (!previousText && !currentText) { + return { type: "text", status: TextStatus.EQUAL, diff: [] }; + } + + if (!previousText) { + return { + type: "text", + status: TextStatus.ADDED, + diff: currentTokens.map((token, i) => ({ + value: token.value, + status: TextStatus.ADDED, + currentIndex: i, + previousIndex: null, + })), + }; + } + if (!currentText) { + return { + type: "text", + status: TextStatus.DELETED, + diff: previousTokens.map((token, i) => ({ + value: token.value, + status: TextStatus.DELETED, + previousIndex: i, + currentIndex: null, + })), + }; + } + + if (options.mode === "strict") { + return getStrictTextDiff(previousTokens, currentTokens); + } + return getLCSTextDiff(previousTokens, currentTokens); +} diff --git a/src/lib/text-diff/lcs/index.ts b/src/lib/text-diff/lcs/index.ts new file mode 100644 index 0000000..093817a --- /dev/null +++ b/src/lib/text-diff/lcs/index.ts @@ -0,0 +1,52 @@ +import { TextDiff, TextStatus, TextToken, TextTokenDiff } from "@models/text"; +import { myersDiff } from "./myers"; +import { getDiffStatus } from "../utils/status"; + +export function getLCSTextDiff( + previousTokens: TextToken[], + currentTokens: TextToken[], +): TextDiff { + const edits = myersDiff(previousTokens, currentTokens); + const diff: TextTokenDiff[] = []; + const statusSet = new Set(); + + for (let i = 0; i < edits.length; i++) { + const edit = edits[i]; + + if (edit.status === TextStatus.EQUAL) { + diff.push({ + value: currentTokens[edit.curr].value, + status: TextStatus.EQUAL, + currentIndex: edit.curr, + previousIndex: edit.prev, + }); + statusSet.add(TextStatus.EQUAL); + } + + if (edit.status === TextStatus.ADDED) { + diff.push({ + value: currentTokens[edit.curr].value, + status: TextStatus.ADDED, + currentIndex: edit.curr, + previousIndex: null, + }); + statusSet.add(TextStatus.ADDED); + } + + if (edit.status === TextStatus.DELETED) { + diff.push({ + value: previousTokens[edit.prev].value, + status: TextStatus.DELETED, + previousIndex: edit.prev, + currentIndex: null, + }); + statusSet.add(TextStatus.DELETED); + } + } + + return { + type: "text", + status: getDiffStatus(statusSet), + diff, + }; +} diff --git a/src/lib/text-diff/lcs/myers.ts b/src/lib/text-diff/lcs/myers.ts new file mode 100644 index 0000000..f2f596b --- /dev/null +++ b/src/lib/text-diff/lcs/myers.ts @@ -0,0 +1,106 @@ +import { TextStatus, TextToken } from "@models/text"; + +type MyersEdit = + | { status: TextStatus.EQUAL; prev: number; curr: number } + | { status: TextStatus.ADDED; curr: number } + | { status: TextStatus.DELETED; prev: number }; + +function backtrack( + trace: Map[], + a: TextToken[], + b: TextToken[], +): MyersEdit[] { + let x = a.length; + let y = b.length; + const edits: MyersEdit[] = []; + + for (let d = trace.length - 1; d >= 0; d--) { + const v = trace[d]; + const k = x - y; + + let prevK: number; + if (k === -d || (k !== d && (v.get(k - 1) ?? 0) < (v.get(k + 1) ?? 0))) { + prevK = k + 1; + } else { + prevK = k - 1; + } + + const prevX = v.get(prevK) ?? 0; + const prevY = prevX - prevK; + + // Snake (equal) + while (x > prevX && y > prevY) { + edits.push({ + status: TextStatus.EQUAL, + prev: x - 1, + curr: y - 1, + }); + x--; + y--; + } + + if (d === 0) break; + + // Edit step + if (x === prevX) { + edits.push({ + status: TextStatus.ADDED, + curr: y - 1, + }); + y--; + } else { + edits.push({ + status: TextStatus.DELETED, + prev: x - 1, + }); + x--; + } + } + + return edits.reverse(); +} + +export function myersDiff(a: TextToken[], b: TextToken[]): MyersEdit[] { + const N = a.length; + const M = b.length; + const max = N + M; + + const trace: Map[] = []; + const v = new Map(); + v.set(1, 0); + + for (let d = 0; d <= max; d++) { + const vSnapshot = new Map(v); + + for (let k = -d; k <= d; k += 2) { + let x: number; + + if (k === -d || (k !== d && (v.get(k - 1) ?? 0) < (v.get(k + 1) ?? 0))) { + // Down (insert) + x = v.get(k + 1) ?? 0; + } else { + // Right (delete) + x = (v.get(k - 1) ?? 0) + 1; + } + + let y = x - k; + + // Snake (match) + while (x < N && y < M && a[x].normalizedValue === b[y].normalizedValue) { + x++; + y++; + } + + v.set(k, x); + + if (x >= N && y >= M) { + trace.push(vSnapshot); + return backtrack(trace, a, b); + } + } + + trace.push(vSnapshot); + } + + return []; +} diff --git a/src/lib/text-diff/strict/index.ts b/src/lib/text-diff/strict/index.ts new file mode 100644 index 0000000..6fc535b --- /dev/null +++ b/src/lib/text-diff/strict/index.ts @@ -0,0 +1,85 @@ +import { TextDiff, TextStatus, TextToken, TextTokenDiff } from "@models/text"; +import { getDiffStatus } from "../utils/status"; + +export function getStrictTextDiff( + previousTokens: TextToken[], + currentTokens: TextToken[], +): TextDiff { + const previousTokensMap = new Map(); + const addedTokensMap = new Map(); + const statusMap = new Set(); + const diff: TextTokenDiff[] = []; + + previousTokens.forEach((previousToken) => { + const match = previousTokensMap.get(previousToken.normalizedValue); + if (match) { + previousTokensMap.set(previousToken.normalizedValue, [ + ...match, + previousToken, + ]); + } else { + previousTokensMap.set(previousToken.normalizedValue, [previousToken]); + } + }); + + currentTokens.forEach((currentToken) => { + const prevTokens = previousTokensMap.get(currentToken.normalizedValue); + const prevToken = prevTokens?.at(0); + if (prevTokens && prevToken) { + const nextStatus = + prevToken.currentIndex === currentToken.currentIndex + ? TextStatus.EQUAL + : TextStatus.MOVED; + statusMap.add(nextStatus); + diff.push({ + value: currentToken.value, + status: nextStatus, + currentIndex: currentToken.currentIndex, + previousIndex: prevToken.currentIndex, + }); + const nextPrevTokens = prevTokens.splice(1); + if (nextPrevTokens.length === 0) { + previousTokensMap.delete(prevToken.normalizedValue); + } else { + previousTokensMap.set(prevToken.normalizedValue, nextPrevTokens); + } + } else { + addedTokensMap.set(currentToken.currentIndex, currentToken); + statusMap.add(TextStatus.ADDED); + diff.push({ + value: currentToken.value, + status: TextStatus.ADDED, + currentIndex: currentToken.currentIndex, + previousIndex: null, + }); + } + }); + + previousTokensMap.forEach((previousTokens) => { + previousTokens.forEach((previousToken) => { + const match = addedTokensMap.get(previousToken.currentIndex); + if (match) { + statusMap.add(TextStatus.UPDATED); + diff[previousToken.currentIndex] = { + value: match.value, + previousValue: previousToken.value, + status: TextStatus.UPDATED, + previousIndex: null, + currentIndex: match.currentIndex, + }; + } else { + statusMap.add(TextStatus.DELETED); + diff.push({ + value: previousToken.value, + status: TextStatus.DELETED, + previousIndex: previousToken.currentIndex, + currentIndex: null, + }); + } + }); + }); + + const status = getDiffStatus(statusMap); + + return { type: "text", status, diff }; +} diff --git a/src/lib/text-diff/text-diff.test.ts b/src/lib/text-diff/text-diff.test.ts new file mode 100644 index 0000000..90a70d2 --- /dev/null +++ b/src/lib/text-diff/text-diff.test.ts @@ -0,0 +1,502 @@ +import { getTextDiff } from "."; + +describe("getTextDiff - general", () => { + it("returns all equal tokens when texts are identical", () => { + expect(getTextDiff("A B C", "A B C")).toStrictEqual({ + type: "text", + status: "equal", + diff: [ + { value: "A", status: "equal", currentIndex: 0, previousIndex: 0 }, + { value: "B", status: "equal", currentIndex: 1, previousIndex: 1 }, + { value: "C", status: "equal", currentIndex: 2, previousIndex: 2 }, + ], + }); + }); + + it("marks all tokens as added when previous text is empty", () => { + expect(getTextDiff("", "A B")).toStrictEqual({ + type: "text", + status: "added", + diff: [ + { value: "A", status: "added", currentIndex: 0, previousIndex: null }, + { value: "B", status: "added", currentIndex: 1, previousIndex: null }, + ], + }); + }); + + it("marks all tokens as deleted when current text is empty", () => { + expect(getTextDiff("A B", "")).toStrictEqual({ + type: "text", + status: "deleted", + diff: [ + { value: "A", status: "deleted", currentIndex: null, previousIndex: 0 }, + { value: "B", status: "deleted", currentIndex: null, previousIndex: 1 }, + ], + }); + }); +}); + +describe("getTextDiff – visual", () => { + it("merges delete + add at same position into updated", () => { + expect(getTextDiff("A B C", "A X C")).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "A", status: "equal", currentIndex: 0, previousIndex: 0 }, + { + value: "B", + status: "deleted", + currentIndex: null, + previousIndex: 1, + }, + { + value: "X", + status: "added", + currentIndex: 1, + previousIndex: null, + }, + { value: "C", status: "equal", currentIndex: 2, previousIndex: 2 }, + ], + }); + }); + + it("represents reordering as delete + add in visual mode", () => { + expect(getTextDiff("A B C A B", "A B A B C")).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "A", status: "equal", currentIndex: 0, previousIndex: 0 }, + { value: "B", status: "equal", currentIndex: 1, previousIndex: 1 }, + { value: "C", status: "deleted", currentIndex: null, previousIndex: 2 }, + { value: "A", status: "equal", currentIndex: 2, previousIndex: 3 }, + { value: "B", status: "equal", currentIndex: 3, previousIndex: 4 }, + { value: "C", status: "added", currentIndex: 4, previousIndex: null }, + ], + }); + }); + + it("handles moves, updates, adds and deletes correctly - by word", () => { + expect( + getTextDiff( + "Solemnly he came and mounted the rounded gunrest.", + "He, solemnly came and he mounted square gunrest.", + { ignoreCase: true, separation: "word" }, + ), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "He,", status: "added", currentIndex: 0, previousIndex: null }, + { + value: "solemnly", + status: "equal", + currentIndex: 1, + previousIndex: 0, + }, + { + value: "he", + status: "deleted", + currentIndex: null, + previousIndex: 1, + }, + { value: "came", status: "equal", currentIndex: 2, previousIndex: 2 }, + { value: "and", status: "equal", currentIndex: 3, previousIndex: 3 }, + { value: "he", status: "added", currentIndex: 4, previousIndex: null }, + { + value: "mounted", + status: "equal", + currentIndex: 5, + previousIndex: 4, + }, + { + value: "the", + status: "deleted", + currentIndex: null, + previousIndex: 5, + }, + { + value: "rounded", + status: "deleted", + currentIndex: null, + previousIndex: 6, + }, + { + value: "square", + status: "added", + currentIndex: 6, + previousIndex: null, + }, + { + value: "gunrest.", + status: "equal", + currentIndex: 7, + previousIndex: 7, + }, + ], + }); + }); + + it("handles moves, updates, adds and deletes correctly - by character", () => { + expect( + getTextDiff("abc", "xcy", { separation: "character", mode: "visual" }), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { + value: "a", + status: "deleted", + previousIndex: 0, + currentIndex: null, + }, + { + value: "b", + status: "deleted", + previousIndex: 1, + currentIndex: null, + }, + { + value: "x", + status: "added", + currentIndex: 0, + previousIndex: null, + }, + { value: "c", status: "equal", currentIndex: 1, previousIndex: 2 }, + { + value: "y", + status: "added", + currentIndex: 2, + previousIndex: null, + }, + ], + }); + }); + + it("handles moves, updates, adds and deletes correctly - by sentence", () => { + expect( + getTextDiff( + "Hello world. I like turtles. Goodbye moon.", + "Hello world. I love turtles. Welcome sun.", + { separation: "sentence", mode: "visual" }, + ), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { + value: "Hello world.", + status: "equal", + currentIndex: 0, + previousIndex: 0, + }, + { + value: "I like turtles.", + status: "deleted", + previousIndex: 1, + currentIndex: null, + }, + { + value: "Goodbye moon.", + status: "deleted", + previousIndex: 2, + currentIndex: null, + }, + { + value: "I love turtles.", + status: "added", + currentIndex: 1, + previousIndex: null, + }, + { + value: "Welcome sun.", + status: "added", + currentIndex: 2, + previousIndex: null, + }, + ], + }); + }); + + it("ignores case when ignoreCase is true", () => { + const diff = getTextDiff("Hello WORLD", "hello world", { + ignoreCase: true, + }); + + expect(diff.diff.every((d) => d.status === "equal")).toBe(true); + }); + + it("ignores punctuation when ignorePunctuation is true", () => { + const diff = getTextDiff("hello!", "hello", { + ignorePunctuation: true, + }); + + expect(diff.diff[0].status).toBe("equal"); + }); + + it("handles character separation", () => { + expect( + getTextDiff("abc", "axc", { separation: "character" }), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "a", status: "equal", currentIndex: 0, previousIndex: 0 }, + { + value: "b", + status: "deleted", + currentIndex: null, + previousIndex: 1, + }, + { + value: "x", + status: "added", + currentIndex: 1, + previousIndex: null, + }, + { value: "c", status: "equal", currentIndex: 2, previousIndex: 2 }, + ], + }); + }); + + it("handles sentence separation", () => { + expect( + getTextDiff("Hello world. How are you?", "Hello world. I'm fine.", { + separation: "sentence", + }), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { + value: "Hello world.", + status: "equal", + currentIndex: 0, + previousIndex: 0, + }, + { + value: "How are you?", + status: "deleted", + currentIndex: null, + previousIndex: 1, + }, + { + value: "I'm fine.", + status: "added", + currentIndex: 1, + previousIndex: null, + }, + ], + }); + }); +}); + +describe("getTextDiff – strict", () => { + it("merges delete + add at same position into updated", () => { + expect(getTextDiff("A B C", "A X C", { mode: "strict" })).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "A", status: "equal", currentIndex: 0, previousIndex: 0 }, + { + value: "X", + previousValue: "B", + status: "updated", + currentIndex: 1, + previousIndex: null, + }, + { value: "C", status: "equal", currentIndex: 2, previousIndex: 2 }, + ], + }); + }); + + it("ignores case when ignoreCase is true", () => { + expect( + getTextDiff("Hello World", "hello world", { + ignoreCase: true, + mode: "strict", + }), + ).toStrictEqual({ + type: "text", + status: "equal", + diff: [ + { value: "hello", status: "equal", currentIndex: 0, previousIndex: 0 }, + { value: "world", status: "equal", currentIndex: 1, previousIndex: 1 }, + ], + }); + }); + + it("ignores punctuation when ignorePunctuation is true", () => { + expect( + getTextDiff("Hello, world!", "Hello world", { + ignorePunctuation: true, + mode: "strict", + }), + ).toStrictEqual({ + type: "text", + status: "equal", + diff: [ + { value: "Hello", status: "equal", currentIndex: 0, previousIndex: 0 }, + { value: "world", status: "equal", currentIndex: 1, previousIndex: 1 }, + ], + }); + }); + + it("handles moves, updates, adds and deletes correctly - by word", () => { + expect( + getTextDiff( + "Solemnly he came and mounted the rounded gunrest.", + "He, solemnly came and he mounted square gunrest.", + { ignoreCase: true, separation: "word", mode: "strict" }, + ), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "He,", status: "added", currentIndex: 0, previousIndex: null }, + { + value: "solemnly", + status: "moved", + currentIndex: 1, + previousIndex: 0, + }, + { value: "came", status: "equal", currentIndex: 2, previousIndex: 2 }, + { value: "and", status: "equal", currentIndex: 3, previousIndex: 3 }, + { value: "he", status: "moved", currentIndex: 4, previousIndex: 1 }, + { + value: "mounted", + status: "moved", + currentIndex: 5, + previousIndex: 4, + }, + { + value: "square", + previousValue: "rounded", + status: "updated", + currentIndex: 6, + previousIndex: null, + }, + { + value: "gunrest.", + status: "equal", + currentIndex: 7, + previousIndex: 7, + }, + { + value: "the", + status: "deleted", + currentIndex: null, + previousIndex: 5, + }, + ], + }); + }); + + it("handles moves, updates, adds and deletes correctly - by character", () => { + expect( + getTextDiff("abcdz", "xbcy", { + separation: "character", + mode: "strict", + }), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { + value: "x", + previousValue: "a", + status: "updated", + currentIndex: 0, + previousIndex: null, + }, + { + value: "b", + status: "equal", + currentIndex: 1, + previousIndex: 1, + }, + { + value: "c", + status: "equal", + currentIndex: 2, + previousIndex: 2, + }, + { + value: "y", + previousValue: "d", + status: "updated", + currentIndex: 3, + previousIndex: null, + }, + { + value: "z", + status: "deleted", + currentIndex: null, + previousIndex: 4, + }, + ], + }); + }); + + it("handles moves, updates, adds and deletes correctly - by sentence", () => { + console.log( + "tyty", + getTextDiff("The brown fox jumped high", "The orange cat has jumped", { + mode: "visual", + separation: "word", + }), + ); + expect( + getTextDiff( + "A one. B two. C three. D four.", + "B two. A ONE. C three. E five.", + { separation: "sentence", mode: "strict", ignoreCase: true }, + ), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { + value: "B two.", + status: "moved", + currentIndex: 0, + previousIndex: 1, + }, + { + value: "A ONE.", + status: "moved", + currentIndex: 1, + previousIndex: 0, + }, + { + value: "C three.", + status: "equal", + currentIndex: 2, + previousIndex: 2, + }, + { + value: "E five.", + previousValue: "D four.", + status: "updated", + previousIndex: null, + currentIndex: 3, + }, + ], + }); + }); + + it("detects moves with duplicates", () => { + expect( + getTextDiff("A B C A B", "A B A B C", { mode: "strict" }), + ).toStrictEqual({ + type: "text", + status: "updated", + diff: [ + { value: "A", status: "equal", currentIndex: 0, previousIndex: 0 }, + { value: "B", status: "equal", currentIndex: 1, previousIndex: 1 }, + { value: "A", status: "moved", currentIndex: 2, previousIndex: 3 }, + { value: "B", status: "moved", currentIndex: 3, previousIndex: 4 }, + { value: "C", status: "moved", currentIndex: 4, previousIndex: 2 }, + ], + }); + }); +}); diff --git a/src/lib/text-diff/tokenize/index.ts b/src/lib/text-diff/tokenize/index.ts new file mode 100644 index 0000000..c22656d --- /dev/null +++ b/src/lib/text-diff/tokenize/index.ts @@ -0,0 +1,106 @@ +import { + DEFAULT_TEXT_DIFF_OPTIONS, + TextDiffOptions, + TextToken, +} from "@models/text"; + +const normalizeToken = (token: string, options: TextDiffOptions): string => { + let nextToken = token; + if (options.ignoreCase) { + nextToken = nextToken.toLowerCase(); + } + if (options.ignorePunctuation) { + nextToken = nextToken.replace(/[",;:!?“”‘’'«»()[\]{}…—–-]/g, ""); + } + return nextToken; +}; + +export const tokenizeText = ( + text: string | null | undefined, + options: TextDiffOptions = DEFAULT_TEXT_DIFF_OPTIONS, +): TextToken[] => { + const result: TextToken[] = []; + + const generateToken = (token: string, currentIndex: number) => { + if (token) { + result.push({ + value: token, + normalizedValue: normalizeToken(token, options), + currentIndex, + }); + } + }; + + // Intl.Segmenter splits words and punctuation separately. + // This function merges them into user-expected tokens like: "word!", "Jean-Claude", "day..." + const mergeWordsPunctuation = (tokens: Intl.SegmentData[]): string[] => { + const mergedWords: string[] = []; + + const pushSplit = (segment: string) => { + const parts = segment + .split( + /(\p{Emoji_Presentation}|\p{Extended_Pictographic}|[+\\/*=<>%&|^~@#$€£¥])/gu, + ) + .filter(Boolean); + mergedWords.push(...parts); + }; + + let lastEndIndex: number | null = null; + + for (const { segment, isWordLike, index } of tokens) { + const endIndex = index + segment.length; + const validSegment = segment.trim(); + if (!validSegment) { + lastEndIndex = endIndex; + continue; + } + if (isWordLike) { + const isAdjacent = lastEndIndex === index; + const endsWithDash = /[—–-]$/.test(mergedWords.at(-1) || ""); + + if (mergedWords.length > 0 && isAdjacent && endsWithDash) { + const prev = mergedWords.pop()!; + pushSplit(prev + validSegment); + } else { + pushSplit(validSegment); + } + } else if (mergedWords.length > 0) { + const prev = mergedWords.pop()!; + pushSplit(prev + validSegment); + } else { + pushSplit(validSegment); + } + lastEndIndex = endIndex; + } + return mergedWords; + }; + + if (!text?.trim()) return result; + switch (options.separation) { + case "character": + [...text].forEach((token, i) => generateToken(token.trim(), i)); + break; + case "sentence": { + const segmenter = new Intl.Segmenter(options.locale, { + granularity: "sentence", + }); + for (const [i, { segment }] of [...segmenter.segment(text)].entries()) { + generateToken(segment.trim(), i); + } + break; + } + case "word": { + const segmenter = new Intl.Segmenter(options.locale, { + granularity: "word", + }); + const tokens = [...segmenter.segment(text)]; + mergeWordsPunctuation(tokens).forEach((token, i) => + generateToken(token, i), + ); + break; + } + default: + text.split(/\s+/u).forEach(generateToken); + } + return result; +}; diff --git a/src/lib/text-diff/tokenize/tokenize.test.ts b/src/lib/text-diff/tokenize/tokenize.test.ts new file mode 100644 index 0000000..951077a --- /dev/null +++ b/src/lib/text-diff/tokenize/tokenize.test.ts @@ -0,0 +1,223 @@ +import { tokenizeText } from "."; + +describe("tokenizeText", () => { + const base = "hello wrld! It's a great day... A wonderful day! Yeah."; + + it("splits text into sentences", () => { + const tokens = tokenizeText(base, { separation: "sentence" }); + + expect(tokens).toEqual([ + { value: "hello wrld!", normalizedValue: "hello wrld!", currentIndex: 0 }, + { + value: "It's a great day...", + normalizedValue: "It's a great day...", + currentIndex: 1, + }, + { + value: "A wonderful day!", + normalizedValue: "A wonderful day!", + currentIndex: 2, + }, + { value: "Yeah.", normalizedValue: "Yeah.", currentIndex: 3 }, + ]); + }); + + it("splits text into words and merges punctuation", () => { + const tokens = tokenizeText(base, { separation: "word" }); + + expect(tokens).toEqual([ + { value: "hello", normalizedValue: "hello", currentIndex: 0 }, + { value: "wrld!", normalizedValue: "wrld!", currentIndex: 1 }, + { value: "It's", normalizedValue: "It's", currentIndex: 2 }, + { value: "a", normalizedValue: "a", currentIndex: 3 }, + { value: "great", normalizedValue: "great", currentIndex: 4 }, + { value: "day...", normalizedValue: "day...", currentIndex: 5 }, + { value: "A", normalizedValue: "A", currentIndex: 6 }, + { value: "wonderful", normalizedValue: "wonderful", currentIndex: 7 }, + { value: "day!", normalizedValue: "day!", currentIndex: 8 }, + { value: "Yeah.", normalizedValue: "Yeah.", currentIndex: 9 }, + ]); + }); + + it("splits text into characters", () => { + const tokens = tokenizeText("abc!", { separation: "character" }); + + expect(tokens).toEqual([ + { value: "a", normalizedValue: "a", currentIndex: 0 }, + { value: "b", normalizedValue: "b", currentIndex: 1 }, + { value: "c", normalizedValue: "c", currentIndex: 2 }, + { value: "!", normalizedValue: "!", currentIndex: 3 }, + ]); + }); + + it("splits text by words when separation type is unknown", () => { + const tokens = tokenizeText("hello world"); + + expect(tokens).toEqual([ + { value: "hello", normalizedValue: "hello", currentIndex: 0 }, + { value: "world", normalizedValue: "world", currentIndex: 1 }, + ]); + }); + + it("normalizes tokens by lowercasing when ignoreCase is true", () => { + const tokens = tokenizeText("Hello WORLD!", { + separation: "word", + ignoreCase: true, + }); + + expect(tokens).toEqual([ + { value: "Hello", normalizedValue: "hello", currentIndex: 0 }, + { value: "WORLD!", normalizedValue: "world!", currentIndex: 1 }, + ]); + }); + + it("removes punctuation in normalizedValue when ignorePunctuation is true", () => { + const tokens = tokenizeText("hello world!", { + separation: "word", + ignorePunctuation: true, + }); + + expect(tokens).toEqual([ + { value: "hello", normalizedValue: "hello", currentIndex: 0 }, + { value: "world!", normalizedValue: "world", currentIndex: 1 }, + ]); + }); + + it("applies both ignoreCase and ignorePunctuation", () => { + const tokens = tokenizeText("Hello WORLD!", { + separation: "word", + ignoreCase: true, + ignorePunctuation: true, + }); + + expect(tokens).toEqual([ + { value: "Hello", normalizedValue: "hello", currentIndex: 0 }, + { value: "WORLD!", normalizedValue: "world", currentIndex: 1 }, + ]); + }); + + it("returns empty array for empty input", () => { + expect(tokenizeText("", { separation: "word" })).toEqual([]); + expect(tokenizeText(null, { separation: "word" })).toEqual([]); + expect(tokenizeText(undefined, { separation: "word" })).toEqual([]); + }); + + it("handles locale-specific segmentation (Japanese)", () => { + const tokens = tokenizeText("今日はいい天気ですね。", { + separation: "sentence", + locale: "ja", + }); + + expect(tokens).toEqual([ + { + value: "今日はいい天気ですね。", + normalizedValue: "今日はいい天気ですね。", + currentIndex: 0, + }, + ]); + }); + + it("handles CJK word segmentation", () => { + const tokens = tokenizeText("私は学生です。", { + separation: "word", + locale: "ja", + }); + + expect(tokens).toEqual([ + { value: "私", normalizedValue: "私", currentIndex: 0 }, + { value: "は", normalizedValue: "は", currentIndex: 1 }, + { value: "学生", normalizedValue: "学生", currentIndex: 2 }, + { value: "です。", normalizedValue: "です。", currentIndex: 3 }, + ]); + }); + + it("trims extra spacing in sentences", () => { + const tokens = tokenizeText(" Hello world! This is fine. ", { + separation: "sentence", + }); + + expect(tokens).toEqual([ + { + value: "Hello world!", + normalizedValue: "Hello world!", + currentIndex: 0, + }, + { + value: "This is fine.", + normalizedValue: "This is fine.", + currentIndex: 1, + }, + ]); + }); + + it("merges multiple punctuation marks", () => { + const tokens = tokenizeText("Wait!!! Really??", { separation: "word" }); + expect(tokens).toEqual([ + { value: "Wait!!!", normalizedValue: "Wait!!!", currentIndex: 0 }, + { value: "Really??", normalizedValue: "Really??", currentIndex: 1 }, + ]); + }); + + it("keeps emojis as standalone tokens", () => { + const tokens = tokenizeText("Hello 😊 world!", { separation: "word" }); + expect(tokens).toEqual([ + { value: "Hello", normalizedValue: "Hello", currentIndex: 0 }, + { value: "😊", normalizedValue: "😊", currentIndex: 1 }, + { value: "world!", normalizedValue: "world!", currentIndex: 2 }, + ]); + }); + + it("handles numbers and punctuation", () => { + const tokens = tokenizeText("Version 2.0 is out!", { separation: "word" }); + + expect(tokens).toEqual([ + { value: "Version", normalizedValue: "Version", currentIndex: 0 }, + { value: "2.0", normalizedValue: "2.0", currentIndex: 1 }, + { value: "is", normalizedValue: "is", currentIndex: 2 }, + { value: "out!", normalizedValue: "out!", currentIndex: 3 }, + ]); + }); + + it("handles mixed scripts", () => { + const tokens = tokenizeText("Hello 世界!", { separation: "word" }); + + expect(tokens).toEqual([ + { value: "Hello", normalizedValue: "Hello", currentIndex: 0 }, + { value: "世界!", normalizedValue: "世界!", currentIndex: 1 }, + ]); + }); + + it("does not merge symbols that are not punctuation", () => { + const tokens = tokenizeText("hello + world", { separation: "word" }); + + expect(tokens).toEqual([ + { value: "hello", normalizedValue: "hello", currentIndex: 0 }, + { value: "+", normalizedValue: "+", currentIndex: 1 }, + { value: "world", normalizedValue: "world", currentIndex: 2 }, + ]); + }); + + it("handles unicode punctuation like em-dash and ellipsis", () => { + const tokens = tokenizeText("Is Jean-Claude cool?", { separation: "word" }); + expect(tokens).toEqual([ + { value: "Is", normalizedValue: "Is", currentIndex: 0 }, + { value: "Jean-Claude", normalizedValue: "Jean-Claude", currentIndex: 1 }, + { value: "cool?", normalizedValue: "cool?", currentIndex: 2 }, + ]); + }); + + it("ignorePunctuation removes unicode punctuation", () => { + const tokens = tokenizeText("Wait—really…?", { + separation: "word", + ignorePunctuation: true, + }); + + expect(tokens).toEqual([ + { + value: "Wait—really…?", + normalizedValue: "Waitreally", + currentIndex: 0, + }, + ]); + }); +}); diff --git a/src/lib/text-diff/utils/status.ts b/src/lib/text-diff/utils/status.ts new file mode 100644 index 0000000..4d2a333 --- /dev/null +++ b/src/lib/text-diff/utils/status.ts @@ -0,0 +1,21 @@ +import { TextDiff, TextStatus } from "@models/text"; + +export function getDiffStatus(statusMap: Set): TextDiff["status"] { + if (statusMap.has(TextStatus.UPDATED)) return TextStatus.UPDATED; + + const isUniqueStatus = (status: TextStatus) => { + let isUnique = true; + for (const value of statusMap) { + if (value !== status) { + isUnique = false; + break; + } + } + return isUnique; + }; + + if (isUniqueStatus(TextStatus.ADDED)) return TextStatus.ADDED; + if (isUniqueStatus(TextStatus.DELETED)) return TextStatus.DELETED; + if (isUniqueStatus(TextStatus.EQUAL)) return TextStatus.EQUAL; + return TextStatus.UPDATED; +} diff --git a/src/models/text/index.ts b/src/models/text/index.ts new file mode 100644 index 0000000..5536755 --- /dev/null +++ b/src/models/text/index.ts @@ -0,0 +1,56 @@ +export const DEFAULT_TEXT_DIFF_OPTIONS: TextDiffOptions = { + showOnly: [], + mode: "visual", + separation: "word", + ignoreCase: false, + ignorePunctuation: false, + locale: undefined, +}; + +export type TextToken = { + value: string; + normalizedValue: string; + currentIndex: number; +}; + +export type TextTokenDiff = { + value: string; + previousValue?: string; + status: TextStatus; + currentIndex: number | null; + previousIndex: number | null; +}; + +export enum TextStatus { + ADDED = "added", + EQUAL = "equal", + DELETED = "deleted", + UPDATED = "updated", + MOVED = "moved", +} + +export type TextDiffOptions = { + showOnly?: `${TextStatus}`[]; + separation?: "character" | "word" | "sentence"; + mode?: "visual" | "strict"; + ignoreCase?: boolean; + ignorePunctuation?: boolean; + + locale?: Intl.Locale | string; +}; + +export type TextDiff = { + type: "text"; + status: + | TextStatus.ADDED + | TextStatus.DELETED + | TextStatus.EQUAL + | TextStatus.UPDATED; + diff: { + value: string; + previousValue?: string; + status: TextStatus; + currentIndex: number | null; + previousIndex: number | null; + }[]; +};