diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..00689c497 --- /dev/null +++ b/.babelrc @@ -0,0 +1,11 @@ +{ + "sourceMaps": "inline", + "presets": ["@babel/preset-env"], + "env": { + "test": { + "plugins": [ + "istanbul" + ] + } + } +} diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index d3f0f0d59..000000000 --- a/.eslintrc +++ /dev/null @@ -1,197 +0,0 @@ -{ - "env": { - "browser": true - }, - "ecmaFeatures": { - // Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time. - "arrowFunctions": true, - "blockBindings": true, - "classes": false, - "defaultParams": true, - "destructuring": true, - "modules": true, - "objectLiteralComputedProperties": true, - "objectLiteralDuplicateProperties": true, - "objectLiteralShorthandMethods": true, - "objectLiteralShorthandProperties": true, - "regexUFlag": true, - "restParams": true, - "spread": true, - "templateStrings": true - }, - "rules": { - // Possible Errors // - //-----------------// - - "comma-dangle": [2, "never"], - "no-cond-assign": [2, "except-parens"], - - // Allow for debugging - "no-console": 1, - - "no-constant-condition": 2, - "no-control-regex": 2, - - // Allow for debugging - "no-debugger": 1, - - "no-dupe-args": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty": 2, - "no-empty-character-class": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-extra-semi": 2, - "no-func-assign": 2, - - // Stylistic... might consider disallowing in the future - "no-inner-declarations": 0, - - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-negated-in-lhs": 2, - "no-obj-calls": 2, - "no-regex-spaces": 2, - "no-sparse-arrays": 0, - - // Optimizer and coverage will handle/highlight this and can be useful for debugging - "no-unreachable": 1, - - "use-isnan": 2, - "valid-jsdoc": 0, - "valid-typeof": 2, - - - // Best Practices // - //----------------// - "block-scoped-var": 0, - "complexity": 0, - "consistent-return": 0, - "curly": 2, - "default-case": 1, - "dot-notation": [2, {"allowKeywords": false}], - "eqeqeq": 0, - "guard-for-in": 1, - "no-alert": 2, - "no-caller": 2, - "no-div-regex": 1, - "no-else-return": 0, - "no-empty-label": 2, - "no-eq-null": 0, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-implied-eval": 2, - "no-iterator": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-loop-func": 0, - "no-multi-spaces": 2, - "no-multi-str": 1, - "no-native-reassign": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-wrappers": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-param-reassign": 0, - "no-process-env": 2, - "no-proto": 2, - "no-redeclare": 2, - "no-return-assign": 2, - "no-script-url": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-throw-literal": 2, - "no-unused-expressions": 2, - "no-void": 0, - "no-warning-comments": 1, - "no-with": 2, - "radix": 2, - "vars-on-top": 0, - "wrap-iife": 2, - "yoda": 0, - - - // Strict // - //--------// - "strict": 0, - - - // Variables // - //-----------// - "no-catch-shadow": 2, - "no-delete-var": 2, - "no-label-var": 2, - "no-shadow": 0, - "no-shadow-restricted-names": 0, - "no-undef": 2, - "no-undef-init": 2, - "no-undefined": 0, - "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], - "no-use-before-define": [2, "nofunc"], - - - // Node.js // - //---------// - // Others left to environment defaults - "no-mixed-requires": 0, - - - // Stylistic // - //-----------// - "indent": 0, - "brace-style": [2, "1tbs", {"allowSingleLine": true}], - "camelcase": 2, - "comma-spacing": [2, {"before": false, "after": true}], - "comma-style": [2, "last"], - "consistent-this": [1, "self"], - "eol-last": 2, - "func-names": 0, - "func-style": [2, "declaration"], - "key-spacing": [2, { - "beforeColon": false, - "afterColon": true - }], - "max-nested-callbacks": 0, - "new-cap": 2, - "new-parens": 2, - "newline-after-var": 0, - "no-array-constructor": 2, - "no-continue": 0, - "no-inline-comments": 0, - "no-lonely-if": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multiple-empty-lines": 0, - "no-nested-ternary": 1, - "no-new-object": 2, - "no-spaced-func": 2, - "no-ternary": 0, - "no-trailing-spaces": 2, - "no-underscore-dangle": 0, - "one-var": 0, - "operator-assignment": 0, - "padded-blocks": 0, - "quote-props": [2, "as-needed", {"keywords": true}], - "quotes": [2, "single", "avoid-escape"], - "semi": 2, - "semi-spacing": [2, {"before": false, "after": true}], - "sort-vars": 0, - "space-after-keywords": [2, "always"], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}], - "space-in-brackets": 0, - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-return-throw-case": 2, - "space-unary-ops": 2, - "spaced-comment": [2, "always"], - "wrap-regex": 1, - - "no-var": 2 - } -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e7eb46a0b..22460965c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,17 @@ coverage node_modules -npm-debug.log -lib -dist \ No newline at end of file +libesm +libcjs +dist +.nyc_output +.vscode +.zed + +# Per https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored: +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/.npmignore b/.npmignore index fb99d3852..e863752a2 100644 --- a/.npmignore +++ b/.npmignore @@ -1,13 +1,22 @@ +.babelrc +.eslint.config.mjs +.gitignore +.npmignore +.nyc_output +.vscode +.zed +test-d components coverage +examples +images +index.html +karma.conf.js +rollup.config.mjs +runtime.js src tasks test -.eslintrc -.gitignore -.travis.yml -.npmignore -Gruntfile.js -index.html -karma.conf.js -style.css +tsconfig.json +yarn-error.log +yarn.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9731b6e43..000000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: node_js -sudo: false -env: - global: - - secure: fyHOH0NftlAdy2tQawAq+qqcnc7GWo7ojRl5oHi30ueu79wFWTTWWvARCA8eo+FtWZsGlLfvyuzpmjnbJfMaN3wCzVfCG8ddd+nDhYKYbA4j+iqJarELQlVY5QguVGryO1emRPxFg2+WaSiaHCEvp5/eC7KYyHmlGtq0K0y1Svc= - - secure: y/hyg7hEbYMoBjQOJ8UUrRp01PT4un0fTJWzNbRWUzAC6RXNOPd/SaOREHkJ/exLoyl5xV5I3gI4jOqy/KgtOH1DBEDuV6puq61Xc5C5GQ7OWiTNAnUZIbmNeTNgKdsfOySLvrEMNVN5xfL/SPe9yQKB2R4fUZ1MdBDAHs+xIas= - -script: grunt travis - -matrix: - include: - - node_js: "0.10" - env: KARMA=false - - node_js: "node" - env: KARMA= - - node_js: "iojs" - env: KARMA=false - -cache: - directories: - - node_modules diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 000000000..3186f3f07 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 989a6c000..320bd00ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,39 +1,28 @@ -# How to Contribute - -## Pull Requests - -We also accept [pull requests][pull-request]! - -Generally we like to see pull requests that -- Maintain the existing code style -- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request) -- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) -- Have tests -- Don't decrease the current code coverage (see coverage/lcov-report/index.html) - -## Building +## Building and testing ``` -npm install -grunt -```` +yarn +yarn test +``` -The `grunt dev` implements watching for tests within Node and `karma start` may be used for manual testing in browsers. +To run tests in a *browser* (for instance to test compatibility with Firefox, with Safari, or with old browser versions), run `yarn karma start`, then open http://localhost:9876/ in the browser you want to test in. Results of the test run will appear in the terminal where `yarn karma start` is running. If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/kpdecker/jsdiff/issues](http://github.com/kpdecker/jsdiff/issues). ## Releasing -JsDiff utilizes the [release yeoman generator][generator-release] to perform most release tasks. +Run a test in Firefox via the procedure above before releasing. -A full release may be completed with the following: +A full release may be completed by first updating the `"version"` property in package.json, then running the following: ``` -yo release -npm publish -yo release:publish components jsdiff dist/components/ +yarn clean +yarn build +yarn npm publish ``` -[generator-release]: https://github.com/walmartlabs/generator-release -[pull-request]: https://github.com/kpdecker/jsdiff/pull/new/master +After releasing, remember to: +* commit the `package.json` change and push it to GitHub +* create a new version tag on GitHub +* update `diff.js` on the `gh-pages` branch to the latest built version from the `dist/` folder. diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 65bd332d9..000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-env node */ -/* eslint-disable no-process-env, camelcase */ -module.exports = function(grunt) { - - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - - eslint: { - options: { - }, - files: [ - 'src/**/*.js', - 'test/**/*.js' - ] - }, - - clean: ['lib', 'dist'], - - babel: { - options: { - sourceMaps: 'inline', - loose: ['es6.modules'], - auxiliaryCommentBefore: 'istanbul ignore next' - }, - cjs: { - options: { - modules: 'common' - }, - files: [{ - cwd: 'src/', - expand: true, - src: '**/*.js', - dest: 'lib/' - }] - } - }, - webpack: { - options: { - context: 'lib/', - output: { - path: 'dist/', - library: 'JsDiff', - libraryTarget: 'umd' - } - }, - dist: { - entry: './index.js', - output: { - filename: 'diff.js' - } - } - }, - - mochaTest: { - test: { - options: { - require: ['babel/register'], - reporter: 'dot' - }, - src: ['test/**/*.js'] - } - }, - - mocha_istanbul: { - coverage: { - src: 'test/**/*.js' - } - }, - istanbul_check_coverage: { - 'default': { - options: { - coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results - check: { - statements: 100, - functions: 100, - branches: 100, - lines: 100 - } - } - } - }, - - karma: { - options: { - configFile: 'karma.conf.js', - autoWatch: false - }, - unit: { - singleRun: true - }, - sauce: { - singleRun: true, - browsers: ['sl_chrome', 'sl_firefox', 'sl_safari', 'sl_ie_11', 'sl_ie_9'] - } - }, - - uglify: { - options: { - mangle: true, - compress: true, - preserveComments: 'some' - }, - dist: { - files: [{ - cwd: 'dist/', - expand: true, - src: ['*.js', '!*.min.js'], - dest: 'dist/', - rename: function(dest, src) { - return dest + src.replace(/\.js$/, '.min.js'); - } - }] - } - }, - - copy: { - dist: { - options: { - processContent: function(content) { - return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n') - + content; - } - }, - files: [ - {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'} - ] - }, - components: { - files: [ - {expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'}, - {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'} - ] - } - }, - - watch: { - scripts: { - options: { - atBegin: true - }, - - files: ['src/**/*.js', 'test/**/*.js'], - tasks: ['build', 'mochaTest', 'cover'] - } - } - }); - - // Build a new version of the library - this.registerTask('build', 'Builds a distributable version of the current project', ['eslint', 'babel', 'webpack']); - this.registerTask('test', ['build', 'mochaTest', 'karma:unit']); - this.registerTask('cover', ['mocha_istanbul:coverage', 'istanbul_check_coverage']); - - this.registerTask('release', ['clean', 'test', 'uglify', 'copy:dist', 'copy:components']); - - // Load tasks from npm - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-babel'); - grunt.loadNpmTasks('grunt-eslint'); - grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-mocha-test'); - grunt.loadNpmTasks('grunt-mocha-istanbul'); - grunt.loadNpmTasks('grunt-webpack'); - - grunt.task.loadTasks('tasks'); - - grunt.registerTask('travis', - !process.env.KARMA && process.env.SAUCE_USERNAME - ? ['clean', 'build', 'karma:unit', 'karma:sauce', 'cover'] - : ['clean', 'build', 'cover']); - - grunt.registerTask('dev', ['clean', 'watch']); - grunt.registerTask('default', ['clean', 'build', 'cover']); -}; diff --git a/LICENSE b/LICENSE index 4e7146ed7..2d48b19fc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,31 +1,29 @@ -Software License Agreement (BSD License) +BSD 3-Clause License Copyright (c) 2009-2015, Kevin Decker - All rights reserved. -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index ca00a744d..323f0ce5a 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,147 @@ # jsdiff -[![Build Status](https://secure.travis-ci.org/kpdecker/jsdiff.svg)](http://travis-ci.org/kpdecker/jsdiff) -[![Sauce Test Status](https://saucelabs.com/buildstatus/jsdiff)](https://saucelabs.com/u/jsdiff) - -A javascript text differencing implementation. +A JavaScript text differencing implementation. Try it out in the **[online demo](https://kpdecker.github.io/jsdiff)**. Based on the algorithm proposed in -["An O(ND) Difference Algorithm and its Variations" (Myers, 1986)](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927). +["An O(ND) Difference Algorithm and its Variations" (Myers, 1986)](http://www.xmailserver.org/diff2.pdf). ## Installation +```bash +npm install diff --save +``` + +## Getting started + +### Imports + +In an environment where you can use imports, everything you need can be imported directly from `diff`. e.g. + +ESM: + +``` +import {diffChars, createPatch} from 'diff'; +``` + +CommonJS + +``` +const {diffChars, createPatch} = require('diff'); +``` + +If you want to serve jsdiff to a web page without using a module system, you can use `dist/diff.js` or `dist/diff.min.js`. These create a global called `Diff` that contains the entire JsDiff API as its properties. + +### Usage - npm install diff +jsdiff's diff functions all take an old text and a new text and perform three steps: -or +1. Split both texts into arrays of "tokens". What constitutes a token varies; in `diffChars`, each character is a token, while in `diffLines`, each line is a token. - bower install jsdiff +2. Find the smallest set of single-token *insertions* and *deletions* needed to transform the first array of tokens into the second. + This step depends upon having some notion of a token from the old array being "equal" to one from the new array, and this notion of equality affects the results. Usually two tokens are equal if `===` considers them equal, but some of the diff functions use an alternative notion of equality or have options to configure it. For instance, by default `diffChars("Foo", "FOOD")` will require two deletions (`o`, `o`) and three insertions (`O`, `O`, `D`), but `diffChars("Foo", "FOOD", {ignoreCase: true})` will require just one insertion (of a `D`), since `ignoreCase` causes `o` and `O` to be considered equal. + +3. Return an array representing the transformation computed in the previous step as a series of [change objects](#change-objects). The array is ordered from the start of the input to the end, and each change object represents *inserting* one or more tokens, *deleting* one or more tokens, or *keeping* one or more tokens. ## API -* `JsDiff.diffChars(oldStr, newStr[, options])` - diffs two blocks of text, comparing character by character. +* `diffChars(oldStr, newStr[, options])` - diffs two blocks of text, treating each character as a token. + + ("Characters" here means Unicode code points - the elements you get when you loop over a string with a `for ... of ...` loop.) + + Returns a list of [change objects](#change-objects). - Returns a list of change objects (See below). + Options + * `ignoreCase`: If `true`, the uppercase and lowercase forms of a character are considered equal. Defaults to `false`. + +* `diffWords(oldStr, newStr[, options])` - diffs two blocks of text, treating each word and each punctuation mark as a token. Whitespace is ignored when computing the diff (but preserved as far as possible in the final change objects). + + Returns a list of [change objects](#change-objects). -* `JsDiff.diffWords(oldStr, newStr[, options])` - diffs two blocks of text, comparing word by word, ignoring whitespace. + Options + * `ignoreCase`: Same as in `diffChars`. Defaults to false. + * `intlSegmenter`: An optional [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) object (which must have a `granularity` of `'word'`) for `diffWords` to use to split the text into words. - Returns a list of change objects (See below). + By default, `diffWords` does not use an `Intl.Segmenter`, just some regexes for splitting text into words. This will tend to give worse results than `Intl.Segmenter` would, but ensures the results are consistent across environments; `Intl.Segmenter` behaviour is only loosely specced and the implementations in browsers could in principle change dramatically in future. If you want to use `diffWords` with an `Intl.Segmenter` but ensure it behaves the same whatever environment you run it in, use an `Intl.Segmenter` polyfill instead of the JavaScript engine's native `Intl.Segmenter` implementation. -* `JsDiff.diffWordsWithSpace(oldStr, newStr[, options])` - diffs two blocks of text, comparing word by word, treating whitespace as significant. + Using an `Intl.Segmenter` should allow better word-level diffing of non-English text than the default behaviour. For instance, `Intl.Segmenter`s can generally identify via built-in dictionaries which sequences of adjacent Chinese characters form words, allowing word-level diffing of Chinese. By specifying a language when instantiating the segmenter (e.g. `new Intl.Segmenter('sv', {granularity: 'word'})`) you can also support language-specific rules, like treating Swedish's colon separated contractions (like *k:a* for *kyrka*) as single words; by default this would be seen as two words separated by a colon. - Returns a list of change objects (See below). +* `diffWordsWithSpace(oldStr, newStr[, options])` - diffs two blocks of text, treating each word, punctuation mark, newline, or run of (non-newline) whitespace as a token. -* `JsDiff.diffLines(oldStr, newStr[, options])` - diffs two blocks of text, comparing line by line. +* `diffLines(oldStr, newStr[, options])` - diffs two blocks of text, treating each line as a token. Options - * `ignoreWhitespace`: `true` to ignore leading and trailing whitespace. This is the same as `diffTrimmedLines` - * `newlineIsToken`: `true` to treat newline characters as separate tokens. This allows for changes to the newline structure to occur independently of the line content and to be treated as such. In general this is the more human friendly form of `diffLines` and `diffLines` is better suited for patches and other computer friendly output. + * `ignoreWhitespace`: `true` to ignore leading and trailing whitespace characters when checking if two lines are equal. Defaults to `false`. + * `ignoreNewlineAtEof`: `true` to ignore a missing newline character at the end of the last line when comparing it to other lines. (By default, the line `'b\n'` in text `'a\nb\nc'` is not considered equal to the line `'b'` in text `'a\nb'`; this option makes them be considered equal.) Ignored if `ignoreWhitespace` or `newlineIsToken` are also true. + * `stripTrailingCr`: `true` to remove all trailing CR (`\r`) characters before performing the diff. Defaults to `false`. + This helps to get a useful diff when diffing UNIX text files against Windows text files. + * `newlineIsToken`: `true` to treat the newline character at the end of each line as its own token. This allows for changes to the newline structure to occur independently of the line content and to be treated as such. In general this is the more human friendly form of `diffLines`; the default behavior with this option turned off is better suited for patches and other computer friendly output. Defaults to `false`. - Returns a list of change objects (See below). + Note that while using `ignoreWhitespace` in combination with `newlineIsToken` is not an error, results may not be as expected. With `ignoreWhitespace: true` and `newlineIsToken: false`, changing a completely empty line to contain some spaces is treated as a non-change, but with `ignoreWhitespace: true` and `newlineIsToken: true`, it is treated as an insertion. This is because the content of a completely blank line is not a token at all in `newlineIsToken` mode. -* `JsDiff.diffTrimmedLines(oldStr, newStr[, options])` - diffs two blocks of text, comparing line by line, ignoring leading and trailing whitespace. + Returns a list of [change objects](#change-objects). - Returns a list of change objects (See below). +* `diffSentences(oldStr, newStr[, options])` - diffs two blocks of text, treating each sentence, and the whitespace between each pair of sentences, as a token. The characters `.`, `!`, and `?`, when followed by whitespace, are treated as marking the end of a sentence; nothing else besides the end of the string is considered to mark a sentence end. -* `JsDiff.diffSentences(oldStr, newStr[, options])` - diffs two blocks of text, comparing sentence by sentence. + (For more sophisticated detection of sentence breaks, including support for non-English punctuation, consider instead tokenizing with an [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) with `granularity: 'sentence'` and passing the result to `diffArrays`.) - Returns a list of change objects (See below). + Returns a list of [change objects](#change-objects). -* `JsDiff.diffCss(oldStr, newStr[, options])` - diffs two blocks of text, comparing CSS tokens. +* `diffCss(oldStr, newStr[, options])` - diffs two blocks of text, comparing CSS tokens. - Returns a list of change objects (See below). + Returns a list of [change objects](#change-objects). -* `JsDiff.diffJson(oldObj, newObj[, options])` - diffs two JSON objects, comparing the fields defined on each. The order of fields, etc does not matter in this comparison. +* `diffJson(oldObj, newObj[, options])` - diffs two JSON-serializable objects by first serializing them to prettily-formatted JSON and then treating each line of the JSON as a token. Object properties are ordered alphabetically in the serialized JSON, so the order of properties in the objects being compared doesn't affect the result. - Returns a list of change objects (See below). + Returns a list of [change objects](#change-objects). + + Options + * `stringifyReplacer`: A custom replacer function. Operates similarly to the `replacer` parameter to [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter), but must be a function. + * `undefinedReplacement`: A value to replace `undefined` with. Ignored if a `stringifyReplacer` is provided. -* `JsDiff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. +* `diffArrays(oldArr, newArr[, options])` - diffs two arrays of tokens, comparing each item for strict equality (===). + + Options + * `comparator`: `function(left, right)` for custom equality checks + + Returns a list of [change objects](#change-objects). + +* `createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch by first computing a diff with `diffLines` and then serializing it to unified diff format. Parameters: - * `oldFileName` : String to be output in the filename section of the patch for the removals - * `newFileName` : String to be output in the filename section of the patch for the additions - * `oldStr` : Original string value - * `newStr` : New string value - * `oldHeader` : Additional information to include in the old file header - * `newHeader` : Additional information to include in thew new file header - * `options` : An object with options. Currently, only `context` is supported and describes how many lines of context should be included. + * `oldFileName`: String to be output in the filename section of the patch for the removals + * `newFileName`: String to be output in the filename section of the patch for the additions + * `oldStr`: Original string value + * `newStr`: New string value + * `oldHeader`: Optional additional information to include in the old file header. Default: `undefined`. + * `newHeader`: Optional additional information to include in the new file header. Default: `undefined`. + * `options`: An object with options. + - `context`: describes how many lines of context should be included. You can set this to `Number.MAX_SAFE_INTEGER` or `Infinity` to include the entire file content in one hunk. + - `ignoreWhitespace`: Same as in `diffLines`. Defaults to `false`. + - `stripTrailingCr`: Same as in `diffLines`. Defaults to `false`. + - `headerOptions`: Configures the format of patch headers in the returned patch. (Note these are distinct from *hunk* headers, which are a mandatory part of the unified diff format and not configurable.) Has three subfields (all default to `true`): + - `includeIndex`: whether to include a line like `Index: filename.txt` at the start of the patch header. (Even if this is `true`, this line will be omitted if `oldFileName` and `newFileName` are not identical.) + - `includeUnderline`: whether to include `===================================================================`. + - `includeFileHeaders`: whether to include two lines indicating the old and new filename, formatted like `--- old.txt` and `+++ new.txt`. -* `JsDiff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. + Note further that jsdiff exports three top-level constants that can be used as `headerOptions` values, named `INCLUDE_HEADERS` (the default), `FILE_HEADERS_ONLY`, and `OMIT_HEADERS`. - Just like JsDiff.createTwoFilesPatch, but with oldFileName being equal to newFileName. + (Note that in the case where `includeIndex` and `includeFileHeaders` are both false, the `oldFileName` and `newFileName` parameters are ignored entirely.) - -* `JsDiff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)` - returns an object with an array of hunk objects. + The GNU `patch` util will accept patches produced with any configuration of these header options (and refers to patch headers as "leading garbage", which in typical usage it makes no attempt to parse or use in any way). However, other tools for working with unified diff format patches may be less liberal (and are not unambiguously wrong to be so, since the format has no real standard). Tinkering with the `headerOptions` setting thus provides a way to help make patches produced by jsdiff compatible with other tools. + +* `createPatch(fileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch. - This method is similar to createTwoFilesPatch, but returns a data structure + Just like createTwoFilesPatch, but with oldFileName being equal to newFileName. + +* `formatPatch(patch[, headerOptions])` - creates a unified diff patch. + + `patch` may be either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`). The optional `headerOptions` argument behaves the same as the `headerOptions` option of `createTwoFilesPatch`, except that it is ignored for Git patches (i.e. patches where `isGit` is `true`). + + When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's output: it emits a `diff --git` header, emits Git extended headers as appropriate based on properties like `isRename`, `isCreate`, `newMode`, etc, and always emits `---`/`+++` file headers when hunks are present but omits them when there are no hunks (e.g. renames without content changes). The `headerOptions` parameter has no effect on Git patches since the header format is fully determined by the Git extended header properties. + +* `structuredPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - returns an object with an array of hunk objects. + + This method is similar to createTwoFilesPatch, but returns a data structure suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: ```js @@ -87,91 +155,158 @@ or } ``` -* `JsDiff.applyPatch(source, patch[, options])` - applies a unified diff patch. +* `applyPatch(source, patch[, options])` - attempts to apply a unified diff patch. + + Hunks are applied first to last. `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly. If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly. If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match. Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly. - Return a string containing new version of provided data. `patch` may be a string diff or the output from the `parsePatch` or `structuredPatch` methods. + Once a hunk is successfully fitted, the process begins again with the next hunk. Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks. + + If a hunk cannot be successfully fitted *anywhere* with fewer than `fuzzFactor` mismatches, `applyPatch` fails and returns `false`. + + If a hunk is successfully fitted but not at the line number specified by the hunk header, all subsequent hunks have their target line number adjusted accordingly. (e.g. if the first hunk is applied 10 lines below where the hunk header said it should fit, `applyPatch` will *start* looking for somewhere to apply the second hunk 10 lines below where its hunk header says it goes.) + + If the patch was applied successfully, returns a string containing the patched text. If the patch could not be applied (because some hunks in the patch couldn't be fitted to the text in `source`), `applyPatch` returns false. + + `patch` may be a string diff or the output from the `parsePatch` or `structuredPatch` methods. The optional `options` object may have the following keys: - - `fuzzFactor`: Number of lines that are allowed to differ before rejecting a patch. Defaults to 0. - - `compareLine(lineNumber, line, operation, patchContent)`: Callback used to compare to given lines to determine if they should be considered equal when patching. Defaults to strict equality but may be overriden to provide fuzzier comparison. Should return false if the lines should be rejected. + - `fuzzFactor`: Maximum Levenshtein distance (in lines deleted, added, or subtituted) between the context shown in a patch hunk and the lines found in the file. Defaults to 0. + - `autoConvertLineEndings`: If `true`, and if the file to be patched consistently uses different line endings to the patch (i.e. either the file always uses Unix line endings while the patch uses Windows ones, or vice versa), then `applyPatch` will behave as if the line endings in the patch were the same as those in the source file. (If `false`, the patch will usually fail to apply in such circumstances since lines deleted in the patch won't be considered to match those in the source file.) Defaults to `true`. + - `compareLine(lineNumber, line, operation, patchContent)`: Callback used to compare to given lines to determine if they should be considered equal when patching. Defaults to strict equality but may be overridden to provide fuzzier comparison. Should return false if the lines should be rejected. + +* `applyPatches(patch, options)` - applies one or more patches. -* `JsDiff.applyPatches(patch, options)` - applies one or more patches. + `patch` may be either an array of structured patch objects, or a string representing a patch in unified diff format (which may patch one or more files). This method will iterate over the contents of the patch and apply to data provided through callbacks. The general flow for each patch index is: - `options.loadFile(index, callback)` is called. The caller should then load the contents of the file and then pass that to the `callback(err, data)` callback. Passing an `err` will terminate further patch execution. - - `options.patched(index, content)` is called once the patch has been applied. `content` will be the return value frmo `applyPatch`. + - `options.patched(index, content, callback)` is called once the patch has been applied. `content` will be the return value from `applyPatch`. When it's ready, the caller should call `callback(err)` callback. Passing an `err` will terminate further patch execution. Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made. -* `JsDiff.parsePatch(diffStr)` - Parses a patch into structured data +* `parsePatch(diffStr)` - Parses a unified diff format patch into a structured patch object. + + Returns a JSON object representation of the patch, suitable for use with the `applyPatch` method. This parses to the same structure returned by `structuredPatch`, except that `oldFileName` and `newFileName` may be `undefined` if the patch doesn't contain enough information to determine them (e.g. a hunk-only patch with no file headers). + + `parsePatch` has some understanding of [Git's particular dialect of unified diff format](https://git-scm.com/docs/git-diff#generate_patch_text_with_p). When parsing a Git patch, each index in the result may contain the following additional fields not included in the data structure returned by `structuredPatch`: + - `isGit` - set to `true` when parsing from a Git-style patch. + - `isRename` - set to `true` when parsing a Git diff that includes `rename from`/`rename to` extended headers, indicating the file was renamed (and the old file no longer exists). Consumers applying the patch should delete the old file. + - `isCopy` - set to `true` when parsing a Git diff that includes `copy from`/`copy to` extended headers, indicating the file was copied (and the old file still exists). Consumers applying the patch should NOT delete the old file. + - `isCreate` - set to `true` when parsing a Git diff that includes a `new file mode` extended header, indicating the file was newly created. + - `isDelete` - set to `true` when parsing a Git diff that includes a `deleted file mode` extended header, indicating the file was deleted. + - `oldMode` - the file mode (e.g. `'100644'`, `'100755'`) of the old file, parsed from Git extended headers (`old mode` or `deleted file mode`). + - `newMode` - the file mode (e.g. `'100644'`, `'100755'`) of the new file, parsed from Git extended headers (`new mode` or `new file mode`). + - `isBinary` - set to `true` when parsing a Git diff that includes a `Binary files ... differ` line, indicating a binary file change. Binary patches have no hunks, so the patch content alone is not sufficient to apply the change; consumers should handle this case specially (e.g. by warning the user or fetching the binary content separately). + +* `reversePatch(patch)` - Returns a new structured patch which when applied will undo the original `patch`. + + `patch` may be either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`). + + When `patch` is a Git-style patch, `reversePatch` handles extended header information (relating to renames, file modes, etc.) to the extent that doing so is possible, but note one fundamental limitation: the correct inverse of a patch featuring `copy from`/`copy to` headers cannot, in general, be determined based on the information contained in the patch alone, and so `reversePatch`'s output when passed such a patch will usually be rejected by `git apply`. (The correct inverse would be a patch that deletes the newly-created file, but for Git to apply such a patch, the patch must explicitly delete every line of content in the file too, and that content cannot be determined from the original patch on its own. `reversePatch` therefore does the only vaguely reasonable thing it can do in this scenario: it outputs a patch with a `deleted file mode` header - indicating that the file should be deleted - but no hunks.) + +* `convertChangesToXML(changes)` - converts a list of change objects to a serialized XML format - Return a JSON object representation of the a patch, suitable for use with the `applyPatch` method. This parses to the same structure returned by `JsDiff.structuredPatch`. +* `convertChangesToDMP(changes)` - converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library -* `convertChangesToXML(changes)` - converts a list of changes to a serialized XML format +#### Universal `options` +Certain options can be provided in the `options` object of *any* method that calculates a diff (including `diffChars`, `diffLines` etc. as well as `structuredPatch`, `createPatch`, and `createTwoFilesPatch`): -All methods above which accept the optional `callback` method will run in sync mode when that parameter is omitted and in async mode when supplied. This allows for larger diffs without blocking the event loop. This may be passed either directly as the final parameter or as the `callback` field in the `options` object. +* `callback`: if provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + + (Note that if the ONLY option you want to provide is a callback, you can pass the callback function directly as the `options` parameter instead of passing an object with a `callback` property.) + +* `maxEditLength`: a number specifying the maximum edit distance to consider between the old and new texts. You can use this to limit the computational cost of diffing large, very different texts by giving up early if the cost will be huge. This option can be passed either to diffing functions (`diffLines`, `diffChars`, etc) or to patch-creation function (`structuredPatch`, `createPatch`, etc), all of which will indicate that the max edit length was reached by returning `undefined` instead of whatever they'd normally return. + +* `timeout`: a number of milliseconds after which the diffing algorithm will abort and return `undefined`. Supported by the same functions as `maxEditLength`. + +* `oneChangePerToken`: if `true`, the array of change objects returned will contain one change object per token (e.g. one per line if calling `diffLines`), instead of runs of consecutive tokens that are all added / all removed / all conserved being combined into a single change object. + +### Defining custom diffing behaviors + +If you need behavior a little different to what any of the text diffing functions above offer, you can roll your own by customizing both the tokenization behavior used and the notion of equality used to determine if two tokens are equal. + +The simplest way to customize tokenization behavior is to simply tokenize the texts you want to diff yourself, with your own code, then pass the arrays of tokens to `diffArrays`. For instance, if you wanted a semantically-aware diff of some code, you could try tokenizing it using a parser specific to the programming language the code is in, then passing the arrays of tokens to `diffArrays`. + +To customize the notion of token equality used, use the `comparator` option to `diffArrays`. + +For even more customisation of the diffing behavior, you can extend the `Diff()` class, override its `castInput`, `tokenize`, `removeEmpty`, `equals`, and `join` properties with your own functions, then call its `diff(oldString, newString[, options])` method. The methods you can override are used as follows: + +* `castInput(value, options)`: used to transform the `oldString` and `newString` before any other steps in the diffing algorithm happen. For instance, `diffJson` uses `castInput` to serialize the objects being diffed to JSON. Defaults to a no-op. +* `tokenize(value, options)`: used to convert each of `oldString` and `newString` (after they've gone through `castInput`) to an array of tokens. Defaults to returning `value.split('')` (returning an array of individual characters). +* `removeEmpty(array)`: called on the arrays of tokens returned by `tokenize` and can be used to modify them. Defaults to stripping out falsey tokens, such as empty strings. `diffArrays` overrides this to simply return the `array`, which means that falsey values like empty strings can be handled like any other token by `diffArrays`. +* `equals(left, right, options)`: called to determine if two tokens (one from the old string, one from the new string) should be considered equal. Defaults to comparing them with `===`. +* `join(tokens)`: gets called with an array of consecutive tokens that have either all been added, all been removed, or are all common. Needs to join them into a single value that can be used as the `value` property of the [change object](#change-objects) for these tokens. Defaults to simply returning `tokens.join('')` (and therefore by default will error out if your tokens are not strings; differs that support non-string tokens like `diffArrays` should override it to be a no-op to fix this). +* `postProcess(changeObjects, options)`: gets called at the end of the algorithm with the [change objects](#change-objects) produced, and can do final cleanups on them. Defaults to simply returning `changeObjects` unchanged. ### Change Objects -Many of the methods above return change objects. These objects are consist of the following fields: +Many of the methods above return change objects. These objects consist of the following fields: -* `value`: Text content -* `added`: True if the value was inserted into the new string -* `removed`: True of the value was removed from the old string +* `value`: The concatenated content of all the tokens represented by this change object - i.e. generally the text that is either added, deleted, or common, as a single string. In cases where tokens are considered common but are non-identical (e.g. because an option like `ignoreCase` or a custom `comparator` was used), the value from the *new* string will be provided here. +* `added`: true if the value was inserted into the new string, otherwise false +* `removed`: true if the value was removed from the old string, otherwise false +* `count`: How many tokens (e.g. chars for `diffChars`, lines for `diffLines`) the value in the change object consists of -Note that some cases may omit a particular flag field. Comparison on the flag fields should always be done in a truthy or falsy manner. +(Change objects where `added` and `removed` are both false represent content that is common to the old and new strings.) ## Examples -Basic example in Node +#### Basic example in Node ```js -require('colors') -var jsdiff = require('diff'); +require('colors'); +const {diffChars} = require('diff'); -var one = 'beep boop'; -var other = 'beep boob blah'; +const one = 'beep boop'; +const other = 'beep boob blah'; -var diff = jsdiff.diffChars(one, other); +const diff = diffChars(one, other); -diff.forEach(function(part){ +diff.forEach((part) => { // green for additions, red for deletions - // grey for common parts - var color = part.added ? 'green' : - part.removed ? 'red' : 'grey'; - process.stderr.write(part.value[color]); + let text = part.added ? part.value.bgGreen : + part.removed ? part.value.bgRed : + part.value; + process.stderr.write(text); }); -console.log() +console.log(); ``` Running the above program should yield Node Example -Basic example in a web page +#### Basic example in a web page ```html

 
 
 ```
 
@@ -179,14 +314,155 @@ Open the above .html file in a browser and you should see
 
 Node Example
 
-**[Full online demo](http://kpdecker.github.com/jsdiff)**
+#### Example of generating a patch from Node
+
+The code below is roughly equivalent to the Unix command `diff -u file1.txt file2.txt > mydiff.patch`:
+
+```
+const {createTwoFilesPatch} = require('diff');
+const file1Contents = fs.readFileSync("file1.txt").toString();
+const file2Contents = fs.readFileSync("file2.txt").toString();
+const patch = createTwoFilesPatch("file1.txt", "file2.txt", file1Contents, file2Contents);
+fs.writeFileSync("mydiff.patch", patch);
+```
+
+#### Examples of parsing and applying a patch from Node
+
+##### Applying a patch to a specified file
+
+The code below is roughly equivalent to the Unix command `patch file1.txt mydiff.patch`:
+
+```
+const {applyPatch} = require('diff');
+const file1Contents = fs.readFileSync("file1.txt").toString();
+const patch = fs.readFileSync("mydiff.patch").toString();
+const patchedFile = applyPatch(file1Contents, patch);
+fs.writeFileSync("file1.txt", patchedFile);
+```
+
+##### Applying a multi-file patch to the files specified by the patch file itself
+
+The code below is roughly equivalent to the Unix command `patch < mydiff.patch`:
+
+```
+const {applyPatches} = require('diff');
+const patch = fs.readFileSync("mydiff.patch").toString();
+applyPatches(patch, {
+    loadFile: (patch, callback) => {
+        let fileContents;
+        try {
+            fileContents = fs.readFileSync(patch.oldFileName).toString();
+        } catch (e) {
+            callback(`No such file: ${patch.oldFileName}`);
+            return;
+        }
+        callback(undefined, fileContents);
+    },
+    patched: (patch, patchedContent, callback) => {
+        if (patchedContent === false) {
+            callback(`Failed to apply patch to ${patch.oldFileName}`)
+            return;
+        }
+        fs.writeFileSync(patch.oldFileName, patchedContent);
+        callback();
+    },
+    complete: (err) => {
+        if (err) {
+            console.log("Failed with error:", err);
+        }
+    }
+});
+```
+
+##### Applying a multi-file Git patch that may include renames and mode changes
+
+[Git patches](https://git-scm.com/docs/git-diff#generate_patch_text_with_p) can include file renames and copies (with or without content changes), which need to be handled in the callbacks you provide to `applyPatches`. `parsePatch` sets `isRename` or `isCopy` on the structured patch object so you can distinguish these cases. Patches can also potentially include file *swaps* (renaming `a → b` and `b → a`), in which case it is incorrect to simply apply each change atomically in sequence. The pattern with the `pendingWrites` Map below handles all of these nuances:
+
+```
+const {applyPatches} = require('diff');
+const patch = fs.readFileSync("git-diff.patch").toString();
+const DELETE = Symbol('delete');
+const pendingWrites = new Map(); // filePath → {content, mode} or DELETE sentinel
+applyPatches(patch, {
+    loadFile: (patch, callback) => {
+        if (patch.isCreate) {
+            // Newly created file — no old content to load
+            callback(undefined, '');
+            return;
+        }
+        try {
+            // Git diffs use a/ and b/ prefixes; strip them to get the real path
+            const filePath = patch.oldFileName.replace(/^a\//, '');
+            callback(undefined, fs.readFileSync(filePath).toString());
+        } catch (e) {
+            callback(`No such file: ${patch.oldFileName}`);
+        }
+    },
+    patched: (patch, patchedContent, callback) => {
+        if (patchedContent === false) {
+            callback(`Failed to apply patch to ${patch.oldFileName}`);
+            return;
+        }
+        const oldPath = patch.oldFileName.replace(/^a\//, '');
+        const newPath = patch.newFileName.replace(/^b\//, '');
+        if (patch.isDelete) {
+            if (!pendingWrites.has(oldPath)) {
+                pendingWrites.set(oldPath, DELETE);
+            }
+        } else {
+            pendingWrites.set(newPath, {content: patchedContent, mode: patch.newMode});
+            // For renames, delete the old file (but not for copies,
+            // where the old file should be kept)
+            if (patch.isRename && !pendingWrites.has(oldPath)) {
+                pendingWrites.set(oldPath, DELETE);
+            }
+        }
+        callback();
+    },
+    complete: (err) => {
+        if (err) {
+            console.log("Failed with error:", err);
+            return;
+        }
+        for (const [filePath, entry] of pendingWrites) {
+            if (entry === DELETE) {
+                fs.unlinkSync(filePath);
+            } else {
+                fs.writeFileSync(filePath, entry.content);
+                if (entry.mode) {
+                    fs.chmodSync(filePath, entry.mode.slice(-3));
+                }
+            }
+        }
+    }
+});
+```
 
 ## Compatibility
 
-[![Sauce Test Status](https://saucelabs.com/browser-matrix/jsdiff.svg)](https://saucelabs.com/u/jsdiff)
+Version 8 of jsdiff supported ES5.
+
+Starting from version 9, at the point of release, each jsdiff release should fully support the latest LTS version of Node plus all [Baseline](https://web.dev/baseline) "core browser" releases that are at least 30 months old. (That is: jsdiff only uses JavaScript features that are deemed "widely available" by Baseline.) If you find one that it doesn't support, please [open an issue](https://github.com/kpdecker/jsdiff/issues).
+
+## TypeScript
+
+As of version 8, JsDiff ships with type definitions. From version 8 onwards, you should not depend on the `@types/diff` package.
 
-jsdiff supports all ES3 environments with some known issues on IE8 and below. Under these browsers some diff algorithms such as word diff and others may fail due to lack of support for capturing groups in the `split` operation.
+One tricky pattern pervades the type definitions and is worth explaining here. Most diff-generating and patch-generating functions (`diffChars`, `diffWords`, `structuredPatch`, etc) can be run in async mode (by providing a `callback` option), in abortable mode (by passing a `timeout` or `maxEditLength` property), or both. This is awkward for typing, because these modes have different call signatures:
+  * in abortable mode, the result *might* be `undefined`, and
+  * in async mode, the result (which *might* be allowed to be `undefined`, depending upon whether we're in abortable mode) is passed to the provide callback instead of being returned, and the return value is always `undefined`
+
+Our type definitions handle this as best they can by declaring different types for multiple [overload signatures](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads) for each such function - and also by declaring different types for abortable and nonabortable options objects. For instance, an object of type `DiffCharsOptionsAbortable` is valid to pass as the `options` argument to `diffChars` and represents an *abortable* call (whose result may be `undefined`) since it necessarily contains either the `timeout` or `maxEditLength` property.
+
+This approach, while probably the least bad way available to add types to JsDiff without radically refactoring the library's API, does not yield perfect results. *As long as* TypeScript is able to statically determine the type of your options, and therefore which overload signature is appropriate, everything should work fine. This should always be the case if you are passing an object literal as the `options` argument and inlining the definition of any `callback` function within that literal. But in cases where TypeScript *cannot* manage to do this - as may often be the case if you, say, define an `options: any = {}` object, build up your options programmatically, and then pass the result to a JsDiff function - then it is likely to fail to match the correct overload signature (probably defaulting to assuming you are calling the function in non-abortable, non-async mode), potentially causing type errors. You can either ignore (e.g. with `@ts-expect-error`) any such errors, or try to avoid them by refactoring your code so that TypeScript can always statically determine the type of the options you pass.
 
 ## License
 
 See [LICENSE](https://github.com/kpdecker/jsdiff/blob/master/LICENSE).
+
+## Deviations from the published Myers diff algorithm
+
+jsdiff deviates from the published algorithm in a couple of ways that don't affect results but do affect performance:
+
+* jsdiff keeps track of the diff for each diagonal using a linked list of change objects for each diagonal, rather than the historical array of furthest-reaching D-paths on each diagonal contemplated on page 8 of Myers's paper.
+* jsdiff skips considering diagonals where the furthest-reaching D-path would go off the edge of the edit graph. This dramatically reduces the time cost (from quadratic to linear) in cases where the new text just appends or truncates content at the end of the old text.
diff --git a/components/bower.json b/components/bower.json
deleted file mode 100644
index 65f994184..000000000
--- a/components/bower.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "name": "jsdiff",
-  "version": "2.1.3",
-  "main": [
-    "diff.js"
-  ],
-  "dependencies": {},
-  "ignore": []
-}
diff --git a/components/component.json b/components/component.json
deleted file mode 100644
index 52e869811..000000000
--- a/components/component.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "name": "jsdiff",
-  "repo": "kpdecker/jsdiff",
-  "description": "A javascript text diff implementation.",
-  "keywords": [
-    "diff",
-    "text"
-  ],
-  "version": "2.1.3",
-  "scripts": [ "diff.js" ],
-  "main":  "diff.js",
-  "license": "BSD"
-}
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 000000000..ea1c73566
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,182 @@
+// @ts-check
+
+import eslint from '@eslint/js';
+import tseslint from 'typescript-eslint';
+import globals from "globals";
+
+export default tseslint.config(
+  {
+    ignores: [
+      "**/*", // ignore everything...
+      "!src/**/", "!src/**/*.ts", // ... except our TypeScript source files...
+      "!test/**/", "!test/**/*.js", // ... and our tests
+    ],
+  },
+  eslint.configs.recommended,
+  tseslint.configs.recommended,
+  {
+    files: ['src/**/*.ts'],
+    languageOptions: {
+      parserOptions: {
+        projectService: true,
+        tsconfigRootDir: import.meta.dirname,
+      },
+    },
+    extends: [tseslint.configs.recommendedTypeChecked],
+    rules: {
+      // Not sure if these actually serve a purpose, but they provide a way to enforce SOME of what
+      // would be imposed by having "verbatimModuleSyntax": true in our tsconfig.json without
+      // actually doing that.
+      "@typescript-eslint/consistent-type-imports": 2,
+      "@typescript-eslint/consistent-type-exports": 2,
+
+      // Things from the recommendedTypeChecked shared config that are disabled simply because they
+      // caused lots of errors in our existing code when tried. Plausibly useful to turn on if
+      // possible and somebody fancies doing the work:
+      "@typescript-eslint/no-unsafe-argument": 0,
+      "@typescript-eslint/no-unsafe-assignment": 0,
+      "@typescript-eslint/no-unsafe-call": 0,
+      "@typescript-eslint/no-unsafe-member-access": 0,
+      "@typescript-eslint/no-unsafe-return": 0,
+    }
+  },
+  {
+    languageOptions: {
+      globals: {
+        ...globals.browser,
+      },
+    },
+
+    rules: {
+      // Possible Errors //
+      //-----------------//
+      "comma-dangle": [2, "never"],
+      "no-console": 1, // Allow for debugging
+      "no-debugger": 1, // Allow for debugging
+      "no-extra-parens": [2, "functions"],
+      "no-extra-semi": 2,
+      "no-negated-in-lhs": 2,
+      "no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging
+
+      // Best Practices //
+      //----------------//
+      curly: 2,
+      "default-case": 1,
+      "dot-notation": [2, {
+        allowKeywords: false,
+      }],
+      "guard-for-in": 1,
+      "no-alert": 2,
+      "no-caller": 2,
+      "no-div-regex": 1,
+      "no-eval": 2,
+      "no-extend-native": 2,
+      "no-extra-bind": 2,
+      "no-floating-decimal": 2,
+      "no-implied-eval": 2,
+      "no-iterator": 2,
+      "no-labels": 2,
+      "no-lone-blocks": 2,
+      "no-multi-spaces": 2,
+      "no-multi-str": 1,
+      "no-native-reassign": 2,
+      "no-new": 2,
+      "no-new-func": 2,
+      "no-new-wrappers": 2,
+      "no-octal-escape": 2,
+      "no-process-env": 2,
+      "no-proto": 2,
+      "no-return-assign": 2,
+      "no-script-url": 2,
+      "no-self-compare": 2,
+      "no-sequences": 2,
+      "no-throw-literal": 2,
+      "no-unused-expressions": 2,
+      "no-warning-comments": 1,
+      radix: 2,
+      "wrap-iife": 2,
+
+      // Variables //
+      //-----------//
+      "no-catch-shadow": 2,
+      "no-label-var": 2,
+      "no-undef-init": 2,
+
+      // Node.js //
+      //---------//
+
+      // Stylistic //
+      //-----------//
+      "brace-style": [2, "1tbs", {
+        allowSingleLine: true,
+      }],
+      camelcase: 2,
+      "comma-spacing": [2, {
+        before: false,
+        after: true,
+      }],
+      "comma-style": [2, "last"],
+      "consistent-this": [1, "self"],
+      "eol-last": 2,
+      "func-style": [2, "declaration"],
+      "key-spacing": [2, {
+        beforeColon: false,
+        afterColon: true,
+      }],
+      "new-cap": 2,
+      "new-parens": 2,
+      "no-array-constructor": 2,
+      "no-lonely-if": 2,
+      "no-mixed-spaces-and-tabs": 2,
+      "no-nested-ternary": 1,
+      "no-new-object": 2,
+      "no-spaced-func": 2,
+      "no-trailing-spaces": 2,
+      "quote-props": [2, "as-needed", {
+        keywords: true,
+      }],
+      quotes: [2, "single", "avoid-escape"],
+      semi: 2,
+      "semi-spacing": [2, {
+        before: false,
+        after: true,
+      }],
+      "space-before-blocks": [2, "always"],
+      "space-before-function-paren": [2, {
+        anonymous: "never",
+        named: "never",
+      }],
+      "space-in-parens": [2, "never"],
+      "space-infix-ops": 2,
+      "space-unary-ops": 2,
+      "spaced-comment": [2, "always"],
+      "wrap-regex": 1,
+      "no-var": 2,
+
+      // Typescript //
+      //------------//
+      "@typescript-eslint/no-explicit-any": 0, // Very strict rule, incompatible with our code
+
+      // We use these intentionally - e.g.
+      //     export interface DiffCssOptions extends CommonDiffOptions {}
+      // for the options argument to diffCss which currently takes no options beyond the ones
+      // common to all diffFoo functions. Doing this allows consistency (one options interface per
+      // diffFoo function) and future-proofs against the API having to change in future if we add a
+      // non-common option to one of these functions.
+      "@typescript-eslint/no-empty-object-type": [2, {allowInterfaces: 'with-single-extends'}],
+    },
+  },
+  {
+    files: ['test/**/*.js'],
+    languageOptions: {
+      globals: {
+        ...globals.node,
+        ...globals.mocha,
+      },
+    },
+    rules: {
+      "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax
+      "@typescript-eslint/no-unused-expressions": 0, // (as above)
+    },
+  }
+);
diff --git a/examples/node_example.js b/examples/node_example.js
index 8e696caf2..d928019e1 100644
--- a/examples/node_example.js
+++ b/examples/node_example.js
@@ -1,17 +1,17 @@
-require('colors')
-var jsdiff = require('../');
+require('colors');
+const {diffChars} = require('diff');
 
-var one = 'beep boop';
-var other = 'beep boob blah';
+const one = 'beep boop';
+const other = 'beep boob blah';
 
-var diff = jsdiff.diffChars(one, other);
+const diff = diffChars(one, other);
 
-diff.forEach(function(part){
+diff.forEach((part) => {
   // green for additions, red for deletions
-  // grey for common parts
-  var color = part.added ? 'green' :
-    part.removed ? 'red' : 'grey';
-  process.stderr.write(part.value[color]);
+  let text = part.added ? part.value.bgGreen :
+             part.removed ? part.value.bgRed :
+                            part.value;
+  process.stderr.write(text);
 });
 
-console.log();
\ No newline at end of file
+console.log();
diff --git a/examples/web_example.html b/examples/web_example.html
index 7f2cb6365..f8c9c0d65 100644
--- a/examples/web_example.html
+++ b/examples/web_example.html
@@ -1,20 +1,32 @@
+
 

 
 
\ No newline at end of file
+
+display.appendChild(fragment);
+
+
diff --git a/images/node_example.png b/images/node_example.png
index eb87034ab..cea7375fc 100644
Binary files a/images/node_example.png and b/images/node_example.png differ
diff --git a/images/web_example.png b/images/web_example.png
index aa7e0def7..a3510a767 100644
Binary files a/images/web_example.png and b/images/web_example.png differ
diff --git a/index.html b/index.html
deleted file mode 100644
index a6dff732b..000000000
--- a/index.html
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-	
-	Diff
-	
-
-
-
-

Diff

- - - - - - -
- -github.com/kpdecker/jsdiff - - - - - - - -
restaurantaura
- - - - - diff --git a/karma.conf.js b/karma.conf.js index eb005407d..1f28cf87c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,32 +1,4 @@ -/* eslint-env node */ -/* eslint-disable no-var, camelcase */ -module.exports = function(config) { - var customLaunchers = { - sl_chrome: { - base: 'SauceLabs', - browserName: 'chrome' - }, - sl_firefox: { - base: 'SauceLabs', - browserName: 'firefox' - }, - sl_safari: { - base: 'SauceLabs', - browserName: 'safari', - version: '8' - }, - sl_ie_11: { - base: 'SauceLabs', - browserName: 'internet explorer', - version: '11' - }, - sl_ie_9: { - base: 'SauceLabs', - browserName: 'internet explorer', - version: '9' - } - }; - +export default function(config) { config.set({ basePath: '', @@ -35,39 +7,34 @@ module.exports = function(config) { files: [ 'test/**/*.js' ], + exclude: [ + // The code being tested by this suite heavily involves Node.js + // filesystem operations, so doesn't make sense to run in a browser: + 'test/patch/readme-rename-example.js' + ], preprocessors: { 'test/**/*.js': ['webpack', 'sourcemap'] }, webpack: { - devtool: 'inline-source-map', + devtool: 'eval', module: { - loaders: [ + rules: [ { test: /\.jsx?$/, exclude: /node_modules/, - loader: 'babel-loader?loose=es6.modules' + loader: 'babel-loader' } ] } }, - webpackMiddleware: { - noInfo: true - }, - sauceLabs: { - testName: 'jsdiff' - }, + reporters: ['mocha'], - reporters: ['mocha', 'saucelabs'], - - customLaunchers: customLaunchers, port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, singleRun: false, - - browsers: ['PhantomJS'] }); }; diff --git a/package.json b/package.json index 4865110fc..e1751ed68 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,20 @@ { "name": "diff", - "version": "2.1.3", - "description": "A javascript text diff implementation.", + "version": "9.0.0", + "description": "A JavaScript text diff implementation.", "keywords": [ "diff", + "jsdiff", + "compare", + "patch", + "text", + "json", + "css", "javascript" ], "maintainers": [ - "Kevin Decker (http://incaseofstairs.com)" + "Kevin Decker (http://incaseofstairs.com)", + "Mark Amery " ], "bugs": { "email": "kpdecker@gmail.com", @@ -16,50 +23,108 @@ "license": "BSD-3-Clause", "repository": { "type": "git", - "url": "git://github.com/kpdecker/jsdiff.git" + "url": "https://github.com/kpdecker/jsdiff.git" }, "engines": { "node": ">=0.3.1" }, - "main": "./lib", + "main": "./libcjs/index.js", + "module": "./libesm/index.js", + "browser": "./dist/diff.js", + "unpkg": "./dist/diff.js", + "exports": { + ".": { + "import": { + "types": "./libesm/index.d.ts", + "default": "./libesm/index.js" + }, + "require": { + "types": "./libcjs/index.d.ts", + "default": "./libcjs/index.js" + } + }, + "./package.json": "./package.json", + "./lib/*.js": { + "import": { + "types": "./libesm/*.d.ts", + "default": "./libesm/*.js" + }, + "require": { + "types": "./libcjs/*.d.ts", + "default": "./libcjs/*.js" + } + }, + "./lib/": { + "import": { + "types": "./libesm/", + "default": "./libesm/" + }, + "require": { + "types": "./libcjs/", + "default": "./libcjs/" + } + } + }, + "type": "module", + "types": "libcjs/index.d.ts", "scripts": { - "test": "grunt" + "clean": "rm -rf libcjs/ libesm/ dist/ coverage/ .nyc_output/", + "lint": "yarn eslint", + "build": "yarn lint && yarn generate-esm && yarn generate-cjs && yarn check-types && yarn run-rollup && yarn run-uglify", + "generate-cjs": "yarn tsc --module commonjs --outDir libcjs && node --eval \"fs.writeFileSync('libcjs/package.json', JSON.stringify({type:'commonjs',sideEffects:false}))\"", + "generate-esm": "yarn tsc --module nodenext --outDir libesm --target es6 && node --eval \"fs.writeFileSync('libesm/package.json', JSON.stringify({type:'module',sideEffects:false}))\"", + "check-types": "yarn run-tsd && yarn run-attw", + "test": "nyc yarn _test", + "_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha", + "run-attw": "yarn attw --pack --entrypoints . && yarn attw --pack --entrypoints lib/diff/word.js --profile node16", + "run-tsd": "yarn tsd --typings libesm/ && yarn tsd --files test-d/", + "run-rollup": "rollup -c rollup.config.mjs", + "run-uglify": "uglifyjs dist/diff.js -c -o dist/diff.min.js", + "run-mocha": "mocha --require ./runtime 'test/**/*.js'" }, - "dependencies": {}, "devDependencies": { - "async": "^1.4.0", - "babel": "^5.8.20", - "babel-core": "^5.8.20", - "babel-loader": "^5.3.2", - "chai": "^3.2.0", - "colors": "^1.1.0", - "eslint": "^1.0.0", - "grunt": "^0.4.5", - "grunt-babel": "^5.0.1", - "grunt-clean": "^0.4.0", - "grunt-cli": "^0.1.13", - "grunt-contrib-clean": "^0.6.0", - "grunt-contrib-copy": "^0.8.0", - "grunt-contrib-uglify": "^0.9.1", - "grunt-contrib-watch": "^0.6.1", - "grunt-eslint": "^17.0.0", - "grunt-karma": "^0.12.0", - "grunt-mocha-istanbul": "^2.4.0", - "grunt-mocha-test": "^0.12.7", - "grunt-webpack": "^1.0.11", - "istanbul": "^0.3.2", - "karma": "^0.13.8", - "karma-mocha": "^0.2.0", - "karma-mocha-reporter": "^1.1.1", - "karma-phantomjs-launcher": "^0.2.1", - "karma-sauce-launcher": "^0.2.14", - "karma-sourcemap-loader": "^0.3.5", - "karma-webpack": "^1.7.0", - "mocha": "^2.2.4", - "phantomjs": "^1.9.17", - "semver": "^5.0.1", - "webpack": "^1.10.5", - "webpack-dev-server": "^1.10.1" + "@arethetypeswrong/cli": "^0.18.2", + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.2", + "@babel/register": "^7.28.6", + "@colors/colors": "^1.6.0", + "@eslint/js": "^10.0.1", + "babel-loader": "^10.1.1", + "babel-plugin-istanbul": "8.0.0", + "chai": "^6.2.2", + "cross-env": "^10.1.0", + "eslint": "^10.2.0", + "globals": "^17.5.0", + "karma": "^6.4.4", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-sourcemap-loader": "^0.4.0", + "karma-webpack": "^5.0.1", + "mocha": "^11.7.5", + "nyc": "^18.0.0", + "rollup": "^4.60.1", + "tsd": "^0.33.0", + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.1", + "uglify-js": "^3.19.3", + "webpack": "^5.106.1", + "webpack-dev-server": "^5.2.3" + }, + "nyc": { + "require": [ + "@babel/register" + ], + "reporter": [ + "lcov", + "text" + ], + "sourceMap": false, + "instrument": false, + "check-coverage": true, + "branches": 100, + "lines": 100, + "functions": 100, + "statements": 100 }, - "optionalDependencies": {} + "packageManager": "yarn@4.12.0" } diff --git a/release-notes.md b/release-notes.md index 9dcae9b12..425bbae17 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,31 +1,356 @@ # Release Notes -## Development +## 9.0.0 -[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.3...master) +(All changes part of PR [#672](https://github.com/kpdecker/jsdiff/pull/672).) + +- **ES5 support is dropped**. `parsePatch` now uses `TextDecoder` and `Uint8Array`, which are not available in ES5, and TypeScript is now compiled with the "es6" `target`. From now on, I intend to freely use any features that are deemed "Widely available" by [Baseline](https://web.dev/baseline). Users who need ES5 support should stick to version 8. + +- **C-style quoted strings in filename headers are now properly supported**. + + When the name of either the old or new file in a patch contains "special characters", both GNU `diff` and Git quote the filename in the patch's headers and escape special characters using the same escape sequences that are used in string literals in C, including octal escapes for all non-ASCII characters. Previously, jsdiff had very little support for this; `parsePatch` would remove the quotes, and unescape any escaped backslashes, but would not unescape other escape sequences. `formatPatch`, meanwhile, did not quote or escape special characters at all. + + Now, `parsePatch` parses all the possible escape sequences that GNU diff (or Git) ever output, and `formatPatch` quotes and escapes filenames containing special characters in the same way GNU diff does. + +- **`formatPatch` now omits file headers when `oldFileName` or `newFileName` in the provided patch object are `undefined`**, regardless of the `headerOptions` parameter. (Previously, it would treat the absence of `oldFileName` or `newFileName` as indicating the filename was the word "undefined" and emit headers `--- undefined` / `+++ undefined`.) + +- **`formatPatch` no longer outputs trailing tab characters at the end of `---`/`+++` headers.** + + Previously, if `formatPatch` was passed a patch object to serialize that had empty strings for the `oldHeader` or `newHeader` property, it would include a trailing tab character after the filename in the `---` and/or `+++` file header. Now, this scenario is treated the same as when `oldHeader`/`newHeader` is `undefined` - i.e. the trailing tab is omitted. + +- **`formatPatch` no longer mutates its input** when serializing a patch containing a hunk where either the old or new content contained zero lines. (Such a hunk occurs only when the hunk has no context lines and represents a pure insertion or pure deletion, which for instance will occur whenever one of the two files being diffed is completely empty.) Previously `formatPatch` would provide the correct output but also mutate the `oldLines` or `newLines` property on the hunk, changing the meaning of the underlying patch. + +- **Git-style patches are now supported by `parsePatch`, `formatPatch`, and `reversePatch`**. + + Patches output by `git diff` can include some features that are unlike those output by GNU `diff`, and therefore not handled by an ordinary unified diff format parser. An ordinary diff simply describes the differences between the *content* of two files, but Git diffs can also indicate, via "extended headers", the creation or deletion of (potentially empty) files, indicate that a file was renamed, and contain information about file mode changes. Furthermore, when these changes appear in a diff in the absence of a content change (e.g. when an empty file is created, or a file is renamed without content changes), the patch will contain no associated `---`/`+++` file headers nor any hunks. + + jsdiff previously did not support parsing Git's extended headers, nor hunkless patches. Now `parsePatch` parses some of the extended headers, parses hunkless Git patches, and can determine filenames (e.g. from the extended headers) when parsing a patch that includes no `---` or `+++` file headers. The additional information conveyed by the extended headers we support is recorded on new fields on the result object returned by `parsePatch`. See `isGit` and subsequent properties in the docs in the README.md file. + + `formatPatch` now outputs extended headers based on these new Git-specific properties, and `reversePatch` respects them as far as possible (with one unavoidable caveat noted in the README.md file). + +- **Unpaired file headers now cause `parsePatch` to throw**. + + It remains acceptable to have a patch with no file headers whatsoever (e.g. one that begins with a `@@` hunk header on the very first line), but a patch with *only* a `---` header or only a `+++` header is now considered an error. + +- **`parsePatch` is now more tolerant of "trailing garbage"** + + That is: after a patch, or between files/indexes in a patch, it is now acceptable to have arbitrary lines of "garbage" (so long as they unambiguously have no syntactic meaning - e.g. trailing garbage that leads with a `+`, `-`, or ` ` and thus is interpretable as part of a hunk still triggers a throw). + + This means we no longer reject patches output by tools that include extra data in "garbage" lines not understood by generic unified diff parsers. (For example, SVN patches can include "Property changes on:" lines that generic unified diff parsers should discard as garbage; jsdiff previously threw errors when encountering them.) + + This change brings jsdiff's behaviour more in line with GNU `patch`, which is highly permissive of "garbage". + +- **The `oldFileName` and `newFileName` fields of `StructuredPatch` are now typed as `string | undefined` instead of `string`**. This type change reflects the (pre-existing) reality that `parsePatch` can produce patches without filenames (e.g. when parsing a patch that simply contains hunks with no file headers). + +## 8.0.4 + +- [#667](https://github.com/kpdecker/jsdiff/pull/667) - **fix another bug in `diffWords` when used with an `Intl.Segmenter`**. If the text to be diffed included a combining mark after a whitespace character (i.e. roughly speaking, an accented space), `diffWords` would previously crash. Now this case is handled correctly. + +## 8.0.3 + +- [#631](https://github.com/kpdecker/jsdiff/pull/631) - **fix support for using an `Intl.Segmenter` with `diffWords`**. This has been almost completely broken since the feature was added in v6.0.0, since it would outright crash on any text that featured two consecutive newlines between a pair of words (a very common case). +- [#635](https://github.com/kpdecker/jsdiff/pull/635) - **small tweaks to tokenization behaviour of `diffWords`** when used *without* an `Intl.Segmenter`. Specifically, the soft hyphen (U+00AD) is no longer considered to be a word break, and the multiplication and division signs (`×` and `÷`) are now treated as punctuation instead of as letters / word characters. +- [#641](https://github.com/kpdecker/jsdiff/pull/641) - **the format of file headers in `createPatch` etc. patches can now be customised somewhat**. It now takes a `headerOptions` option that can be used to disable the file headers entirely, or omit the `Index:` line and/or the underline. In particular, this was motivated by a request to make jsdiff patches compatible with react-diff-view, which they now are if produced with `headerOptions: FILE_HEADERS_ONLY`. +- [#647](https://github.com/kpdecker/jsdiff/pull/647) and [#649](https://github.com/kpdecker/jsdiff/pull/649) - **fix denial-of-service vulnerabilities in `parsePatch` whereby adversarial input could cause a memory-leaking infinite loop, typically crashing the calling process**. Also fixed ReDOS vulnerabilities whereby adversarially-crafted patch headers could take cubic time to parse. Now, `parsePatch` should reliably take linear time. (Handling of headers that include the line break characters `\r`, `\u2028`, or `\u2029` in non-trailing positions is also now more reasonable as side effect of the fix.) + +## 8.0.2 + +- [#616](https://github.com/kpdecker/jsdiff/pull/616) **Restored compatibility of `diffSentences` with old Safari versions.** This was broken in 8.0.0 by the introduction of a regex with a [lookbehind assertion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertion); these weren't supported in Safari prior to version 16.4. +- [#612](https://github.com/kpdecker/jsdiff/pull/612) **Improved tree shakeability** by marking the built CJS and ESM packages with `sideEffects: false`. + +## 8.0.1 + +- [#610](https://github.com/kpdecker/jsdiff/pull/610) **Fixes types for `diffJson` which were broken by 8.0.0**. The new bundled types in 8.0.0 only allowed `diffJson` to be passed string arguments, but it should've been possible to pass either strings or objects (and now is). Thanks to Josh Kelley for the fix. + +## 8.0.0 + +- [#580](https://github.com/kpdecker/jsdiff/pull/580) **Multiple tweaks to `diffSentences`**: + * tokenization no longer takes quadratic time on pathological inputs (reported as a ReDOS vulnerability by Snyk); is now linear instead + * the final sentence in the string is now handled the same by the tokenizer regardless of whether it has a trailing punctuation mark or not. (Previously, "foo. bar." tokenized to `["foo.", " ", "bar."]` but "foo. bar" tokenized to `["foo.", " bar"]` - i.e. whether the space between sentences was treated as a separate token depended upon whether the final sentence had trailing punctuation or not. This was arbitrary and surprising; it is no longer the case.) + * in a string that starts with a sentence end, like "! hello.", the "!" is now treated as a separate sentence + * the README now correctly documents the tokenization behaviour (it was wrong before) +- [#581](https://github.com/kpdecker/jsdiff/pull/581) - **fixed some regex operations used for tokenization in `diffWords` taking O(n^2) time** in pathological cases +- [#595](https://github.com/kpdecker/jsdiff/pull/595) - **fixed a crash in patch creation functions when handling a single hunk consisting of a very large number (e.g. >130k) of lines**. (This was caused by spreading indefinitely-large arrays to `.push()` using `.apply` or the spread operator and hitting the JS-implementation-specific limit on the maximum number of arguments to a function, as shown at https://stackoverflow.com/a/56809779/1709587; thus the exact threshold to hit the error will depend on the environment in which you were running JsDiff.) +- [#596](https://github.com/kpdecker/jsdiff/pull/596) - **removed the `merge` function**. Previously JsDiff included an undocumented function called `merge` that was meant to, in some sense, merge patches. It had at least a couple of serious bugs that could lead to it returning unambiguously wrong results, and it was difficult to simply "fix" because it was [unclear precisely what it was meant to do](https://github.com/kpdecker/jsdiff/issues/181#issuecomment-2198319542). For now, the fix is to remove it entirely. +- [#591](https://github.com/kpdecker/jsdiff/pull/591) - JsDiff's source code has been rewritten in TypeScript. This change entails the following changes for end users: + * **the `diff` package on npm now includes its own TypeScript type definitions**. Users who previously used the `@types/diff` npm package from DefinitelyTyped should remove that dependency when upgrading JsDiff to v8. + + Note that the transition from the DefinitelyTyped types to JsDiff's own type definitions includes multiple fixes and also removes many exported types previously used for `options` arguments to diffing and patch-generation functions. (There are now different exported options types for abortable calls - ones with a `timeout` or `maxEditLength` that may give a result of `undefined` - and non-abortable calls.) See the TypeScript section of the README for some usage tips. + + * **The `Diff` object is now a class**. Custom extensions of `Diff`, as described in the "Defining custom diffing behaviors" section of the README, can therefore now be done by writing a `class CustomDiff extends Diff` and overriding methods, instead of the old way based on prototype inheritance. (I *think* code that did things the old way should still work, though!) + + * **`diff/lib/index.es6.js` and `diff/lib/index.mjs` no longer exist, and the ESM version of the library is no longer bundled into a single file.** + + * **The `ignoreWhitespace` option for `diffWords` is no longer included in the type declarations**. The effect of passing `ignoreWhitespace: true` has always been to make `diffWords` just call `diffWordsWithSpace` instead, which was confusing, because that behaviour doesn't seem properly described as "ignoring" whitespace at all. The property remains available to non-TypeScript applications for the sake of backwards compatibility, but TypeScript applications will now see a type error if they try to pass `ignoreWhitespace: true` to `diffWords` and should change their code to call `diffWordsWithSpace` instead. + + * JsDiff no longer purports to support ES3 environments. (I'm pretty sure it never truly did, despite claiming to in its README, since even the 1.0.0 release used `Array.map` which was added in ES5.) +- [#601](https://github.com/kpdecker/jsdiff/pull/601) - **`diffJson`'s `stringifyReplacer` option behaves more like `JSON.stringify`'s `replacer` argument now.** In particular: + * Each key/value pair now gets passed through the replacer once instead of twice + * The `key` passed to the replacer when the top-level object is passed in as `value` is now `""` (previously, was `undefined`), and the `key` passed with an array element is the array index as a string, like `"0"` or `"1"` (previously was whatever the key for the entire array was). Both the new behaviours match that of `JSON.stringify`. +- [#602](https://github.com/kpdecker/jsdiff/pull/602) - **diffing functions now consistently return `undefined` when called in async mode** (i.e. with a callback). Previously, there was an odd quirk where they would return `true` if the strings being diffed were equal and `undefined` otherwise. + +## 7.0.0 + +Just a single (breaking) bugfix, undoing a behaviour change introduced accidentally in 6.0.0: + +- [#554](https://github.com/kpdecker/jsdiff/pull/554) **`diffWords` treats numbers and underscores as word characters again.** This behaviour was broken in v6.0.0. + +## 6.0.0 + +This is a release containing many, *many* breaking changes. The objective of this release was to carry out a mass fix, in one go, of all the open bugs and design problems that required breaking changes to fix. A substantial, but exhaustive, changelog is below. + +[Commits](https://github.com/kpdecker/jsdiff/compare/v5.2.0...v6.0.0) + +- [#497](https://github.com/kpdecker/jsdiff/pull/497) **`diffWords` behavior has been radically changed.** Previously, even with `ignoreWhitespace: true`, runs of whitespace were tokens, which led to unhelpful and unintuitive diffing behavior in typical texts. Specifically, even when two texts contained overlapping passages, `diffWords` would sometimes choose to delete all the words from the old text and insert them anew in their new positions in order to avoid having to delete or insert whitespace tokens. Whitespace sequences are no longer tokens as of this release, which affects both the generated diffs and the `count`s. + + Runs of whitespace are still tokens in `diffWordsWithSpace`. + + As part of the changes to `diffWords`, **a new `.postProcess` method has been added on the base `Diff` type**, which can be overridden in custom `Diff` implementations. + + **`diffLines` with `ignoreWhitespace: true` will no longer ignore the insertion or deletion of entire extra lines of whitespace at the end of the text**. Previously, these would not show up as insertions or deletions, as a side effect of a hack in the base diffing algorithm meant to help ignore whitespace in `diffWords`. More generally, **the undocumented special handling in the core algorithm for ignored terminals has been removed entirely.** (This special case behavior used to rewrite the final two change objects in a scenario where the final change object was an addition or deletion and its `value` was treated as equal to the empty string when compared using the diff object's `.equals` method.) + +- [#500](https://github.com/kpdecker/jsdiff/pull/500) **`diffChars` now diffs Unicode code points** instead of UTF-16 code units. +- [#508](https://github.com/kpdecker/jsdiff/pull/508) **`parsePatch` now always runs in what was previously "strict" mode; the undocumented `strict` option has been removed.** Previously, by default, `parsePatch` (and other patch functions that use it under the hood to parse patches) would accept a patch where the line counts in the headers were inconsistent with the actual patch content - e.g. where a hunk started with the header `@@ -1,3 +1,6 @@`, indicating that the content below spanned 3 lines in the old file and 6 lines in the new file, but then the actual content below the header consisted of some different number of lines, say 10 lines of context, 5 deletions, and 1 insertion. Actually trying to work with these patches using `applyPatch` or `merge`, however, would produce incorrect results instead of just ignoring the incorrect headers, making this "feature" more of a trap than something actually useful. It's been ripped out, and now we are always "strict" and will reject patches where the line counts in the headers aren't consistent with the actual patch content. +- [#435](https://github.com/kpdecker/jsdiff/pull/435) **Fix `parsePatch` handling of control characters.** `parsePatch` used to interpret various unusual control characters - namely vertical tabs, form feeds, lone carriage returns without a line feed, and EBCDIC NELs - as line breaks when parsing a patch file. This was inconsistent with the behavior of both JsDiff's own `diffLines` method and also the Unix `diff` and `patch` utils, which all simply treat those control characters as ordinary characters. The result of this discrepancy was that some well-formed patches - produced either by `diff` or by JsDiff itself and handled properly by the `patch` util - would be wrongly parsed by `parsePatch`, with the effect that it would disregard the remainder of a hunk after encountering one of these control characters. +- [#439](https://github.com/kpdecker/jsdiff/pull/439) **Prefer diffs that order deletions before insertions.** When faced with a choice between two diffs with an equal total edit distance, the Myers diff algorithm generally prefers one that does deletions before insertions rather than insertions before deletions. For instance, when diffing `abcd` against `acbd`, it will prefer a diff that says to delete the `b` and then insert a new `b` after the `c`, over a diff that says to insert a `c` before the `b` and then delete the existing `c`. JsDiff deviated from the published Myers algorithm in a way that led to it having the opposite preference in many cases, including that example. This is now fixed, meaning diffs output by JsDiff will more accurately reflect what the published Myers diff algorithm would output. +- [#455](https://github.com/kpdecker/jsdiff/pull/455) **The `added` and `removed` properties of change objects are now guaranteed to be set to a boolean value.** (Previously, they would be set to `undefined` or omitted entirely instead of setting them to false.) +- [#464](https://github.com/kpdecker/jsdiff/pull/464) Specifying `{maxEditLength: 0}` now sets a max edit length of 0 instead of no maximum. +- [#460](https://github.com/kpdecker/jsdiff/pull/460) **Added `oneChangePerToken` option.** +- [#467](https://github.com/kpdecker/jsdiff/pull/467) **Consistent ordering of arguments to `comparator(left, right)`.** Values from the old array will now consistently be passed as the first argument (`left`) and values from the new array as the second argument (`right`). Previously this was almost (but not quite) always the other way round. +- [#480](https://github.com/kpdecker/jsdiff/pull/480) **Passing `maxEditLength` to `createPatch` & `createTwoFilesPatch` now works properly** (i.e. returns undefined if the max edit distance is exceeded; previous behavior was to crash with a `TypeError` if the edit distance was exceeded). +- [#486](https://github.com/kpdecker/jsdiff/pull/486) **The `ignoreWhitespace` option of `diffLines` behaves more sensibly now.** `value`s in returned change objects now include leading/trailing whitespace even when `ignoreWhitespace` is used, just like how with `ignoreCase` the `value`s still reflect the case of one of the original texts instead of being all-lowercase. `ignoreWhitespace` is also now compatible with `newlineIsToken`. Finally, **`diffTrimmedLines` is deprecated** (and removed from the docs) in favour of using `diffLines` with `ignoreWhitespace: true`; the two are, and always have been, equivalent. +- [#490](https://github.com/kpdecker/jsdiff/pull/490) **When calling diffing functions in async mode by passing a `callback` option, the diff result will now be passed as the *first* argument to the callback instead of the second.** (Previously, the first argument was never used at all and would always have value `undefined`.) +- [#489](https://github.com/kpdecker/jsdiff/pull/489) **`this.options` no longer exists on `Diff` objects.** Instead, `options` is now passed as an argument to methods that rely on options, like `equals(left, right, options)`. This fixes a race condition in async mode, where diffing behaviour could be changed mid-execution if a concurrent usage of the same `Diff` instances overwrote its `options`. +- [#518](https://github.com/kpdecker/jsdiff/pull/518) **`linedelimiters` no longer exists** on patch objects; instead, when a patch with Windows-style CRLF line endings is parsed, **the lines in `lines` will end with `\r`**. There is now a **new `autoConvertLineEndings` option, on by default**, which makes it so that when a patch with Windows-style line endings is applied to a source file with Unix style line endings, the patch gets autoconverted to use Unix-style line endings, and when a patch with Unix-style line endings is applied to a source file with Windows-style line endings, it gets autoconverted to use Windows-style line endings. +- [#521](https://github.com/kpdecker/jsdiff/pull/521) **the `callback` option is now supported by `structuredPatch`, `createPatch`, and `createTwoFilesPatch`** +- [#529](https://github.com/kpdecker/jsdiff/pull/529) **`parsePatch` can now parse patches where lines starting with `--` or `++` are deleted/inserted**; previously, there were edge cases where the parser would choke on valid patches or give wrong results. +- [#530](https://github.com/kpdecker/jsdiff/pull/530) **Added `ignoreNewlineAtEof` option to `diffLines`** +- [#533](https://github.com/kpdecker/jsdiff/pull/533) **`applyPatch` uses an entirely new algorithm for fuzzy matching.** Differences between the old and new algorithm are as follows: + * The `fuzzFactor` now indicates the maximum [*Levenshtein* distance](https://en.wikipedia.org/wiki/Levenshtein_distance) that there can be between the context shown in a hunk and the actual file content at a location where we try to apply the hunk. (Previously, it represented a maximum [*Hamming* distance](https://en.wikipedia.org/wiki/Hamming_distance), meaning that a single insertion or deletion in the source file could stop a hunk from applying even with a high `fuzzFactor`.) + * A hunk containing a deletion can now only be applied in a context where the line to be deleted actually appears verbatim. (Previously, as long as enough context lines in the hunk matched, `applyPatch` would apply the hunk anyway and delete a completely different line.) + * The context line immediately before and immediately after an insertion must match exactly between the hunk and the file for a hunk to apply. (Previously this was not required.) +- [#535](https://github.com/kpdecker/jsdiff/pull/535) **A bug in patch generation functions is now fixed** that would sometimes previously cause `\ No newline at end of file` to appear in the wrong place in the generated patch, resulting in the patch being invalid. **These invalid patches can also no longer be applied successfully with `applyPatch`.** (It was already the case that tools other than jsdiff, like GNU `patch`, would consider them malformed and refuse to apply them; versions of jsdiff with this fix now do the same thing if you ask them to apply a malformed patch emitted by jsdiff v5.) +- [#535](https://github.com/kpdecker/jsdiff/pull/535) **Passing `newlineIsToken: true` to *patch*-generation functions is no longer allowed.** (Passing it to `diffLines` is still supported - it's only functions like `createPatch` where passing `newlineIsToken` is now an error.) Allowing it to be passed never really made sense, since in cases where the option had any effect on the output at all, the effect tended to be causing a garbled patch to be created that couldn't actually be applied to the source file. +- [#539](https://github.com/kpdecker/jsdiff/pull/539) **`diffWords` now takes an optional `intlSegmenter` option** which should be an `Intl.Segmenter` with word-level granularity. This provides better tokenization of text into words than the default behaviour, even for English but especially for some other languages for which the default behaviour is poor. + +## v5.2.2 - January 2026 + +Only change from 5.2.0 is a backport of the fix to https://github.com/kpdecker/jsdiff/security/advisories/GHSA-73rr-hh4g-fpgx. + +## v5.2.1 (deprecated) + +Accidental release - do not use. + +## v5.2.0 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v5.1.0...v5.2.0) + +- [#411](https://github.com/kpdecker/jsdiff/pull/411) Big performance improvement. Previously an O(n) array-copying operation inside the innermost loop of jsdiff's base diffing code increased the overall worst-case time complexity of computing a diff from O(n²) to O(n³). This is now fixed, bringing the worst-case time complexity down to what it theoretically should be for a Myers diff implementation. +- [#448](https://github.com/kpdecker/jsdiff/pull/448) Performance improvement. Diagonals whose furthest-reaching D-path would go off the edge of the edit graph are now skipped, rather than being pointlessly considered as called for by the original Myers diff algorithm. This dramatically speeds up computing diffs where the new text just appends or truncates content at the end of the old text. +- [#351](https://github.com/kpdecker/jsdiff/issues/351) Importing from the lib folder - e.g. `require("diff/lib/diff/word.js")` - will work again now. This had been broken for users on the latest version of Node since Node 17.5.0, which changed how Node interprets the `exports` property in jsdiff's `package.json` file. +- [#344](https://github.com/kpdecker/jsdiff/issues/344) `diffLines`, `createTwoFilesPatch`, and other patch-creation methods now take an optional `stripTrailingCr: true` option which causes Windows-style `\r\n` line endings to be replaced with Unix-style `\n` line endings before calculating the diff, just like GNU `diff`'s `--strip-trailing-cr` flag. +- [#451](https://github.com/kpdecker/jsdiff/pull/451) Added `diff.formatPatch`. +- [#450](https://github.com/kpdecker/jsdiff/pull/450) Added `diff.reversePatch`. +- [#478](https://github.com/kpdecker/jsdiff/pull/478) Added `timeout` option. + +## v5.1.0 + +- [#365](https://github.com/kpdecker/jsdiff/issues/365) Allow early termination to limit execution time with degenerate cases + +[Commits](https://github.com/kpdecker/jsdiff/compare/v5.0.0...v5.1.0) + +## v5.0.0 + +- Breaking: UMD export renamed from `JsDiff` to `Diff`. +- Breaking: Newlines separated into separate tokens for word diff. +- Breaking: Unified diffs now match ["quirks"](https://www.artima.com/weblogs/viewpost.jsp?thread=164293) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.1...v5.0.0) + +## v4.0.4 - January 2026 + +Only change from 4.0.2 is a backport of the fix to https://github.com/kpdecker/jsdiff/security/advisories/GHSA-73rr-hh4g-fpgx. + +## v4.0.3 (deprecated) + +Accidental release - do not use. + +## v4.0.2 + +No meaningful changes from v4.0.1 - just removed some cruft that shouldn't've been published. + +## v4.0.1 - January 6th, 2019 + +- Fix main reference path - b826104 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.0...v4.0.1) + +## v4.0.0 - January 5th, 2019 + +- [#94](https://github.com/kpdecker/jsdiff/issues/94) - Missing "No newline at end of file" when comparing two texts that do not end in newlines ([@federicotdn](https://api.github.com/users/federicotdn)) +- [#227](https://github.com/kpdecker/jsdiff/issues/227) - Licence +- [#199](https://github.com/kpdecker/jsdiff/issues/199) - Import statement for jsdiff +- [#159](https://github.com/kpdecker/jsdiff/issues/159) - applyPatch affecting wrong line number with with new lines +- [#8](https://github.com/kpdecker/jsdiff/issues/8) - A new state "replace" +- Drop ie9 from karma targets - 79c31bd +- Upgrade deps. Convert from webpack to rollup - 2c1a29c +- Make ()[]"' as word boundaries between each other - f27b899 +- jsdiff: Replaced phantomJS by chrome - ec3114e +- Add yarn.lock to .npmignore - 29466d8 + +Compatibility notes: + +- Bower and Component packages no longer supported + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.5.0...v4.0.0) + +## v3.5.1 - January 2026 + +Only change from 3.5.0 is a backport of the fix to https://github.com/kpdecker/jsdiff/security/advisories/GHSA-73rr-hh4g-fpgx. + +## v3.5.0 - March 4th, 2018 + +- Omit redundant slice in join method of diffArrays - 1023590 +- Support patches with empty lines - fb0f208 +- Accept a custom JSON replacer function for JSON diffing - 69c7f0a +- Optimize parch header parser - 2aec429 +- Fix typos - e89c832 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.4.0...v3.5.0) + +## v3.4.0 - October 7th, 2017 + +- [#183](https://github.com/kpdecker/jsdiff/issues/183) - Feature request: ability to specify a custom equality checker for `diffArrays` +- [#173](https://github.com/kpdecker/jsdiff/issues/173) - Bug: diffArrays gives wrong result on array of booleans +- [#158](https://github.com/kpdecker/jsdiff/issues/158) - diffArrays will not compare the empty string in array? +- comparator for custom equality checks - 30e141e +- count oldLines and newLines when there are conflicts - 53bf384 +- Fix: diffArrays can compare falsey items - 9e24284 +- Docs: Replace grunt with npm test - 00e2f94 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.1...v3.4.0) + +## v3.3.1 - September 3rd, 2017 + +- [#141](https://github.com/kpdecker/jsdiff/issues/141) - Cannot apply patch because my file delimiter is "/r/n" instead of "/n" +- [#192](https://github.com/kpdecker/jsdiff/pull/192) - Fix: Bad merge when adding new files (#189) +- correct spelling mistake - 21fa478 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.0...v3.3.1) + +## v3.3.0 - July 5th, 2017 + +- [#114](https://github.com/kpdecker/jsdiff/issues/114) - /patch/merge not exported +- Gracefully accept invalid newStart in hunks, same as patch(1) does. - d8a3635 +- Use regex rather than starts/ends with for parsePatch - 6cab62c +- Add browser flag - e64f674 +- refactor: simplified code a bit more - 8f8e0f2 +- refactor: simplified code a bit - b094a6f +- fix: some corrections re ignoreCase option - 3c78fd0 +- ignoreCase option - 3cbfbb5 +- Sanitize filename while parsing patches - 2fe8129 +- Added better installation methods - aced50b +- Simple export of functionality - 8690f31 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.2.0...v3.3.0) + +## v3.2.0 - December 26th, 2016 + +- [#156](https://github.com/kpdecker/jsdiff/pull/156) - Add `undefinedReplacement` option to `diffJson` ([@ewnd9](https://api.github.com/users/ewnd9)) +- [#154](https://github.com/kpdecker/jsdiff/pull/154) - Add `examples` and `images` to `.npmignore`. ([@wtgtybhertgeghgtwtg](https://api.github.com/users/wtgtybhertgeghgtwtg)) +- [#153](https://github.com/kpdecker/jsdiff/pull/153) - feat(structuredPatch): Pass options to diffLines ([@Kiougar](https://api.github.com/users/Kiougar)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.1.0...v3.2.0) + +## v3.1.0 - November 27th, 2016 + +- [#146](https://github.com/kpdecker/jsdiff/pull/146) - JsDiff.diffArrays to compare arrays ([@wvanderdeijl](https://api.github.com/users/wvanderdeijl)) +- [#144](https://github.com/kpdecker/jsdiff/pull/144) - Split file using all possible line delimiter instead of hard-coded "/n" and join lines back using the original delimiters ([@soulbeing](https://api.github.com/users/soulbeing)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.0.1...v3.1.0) + +## v3.0.1 - October 9th, 2016 + +- [#139](https://github.com/kpdecker/jsdiff/pull/139) - Make README.md look nicer in npmjs.com ([@takenspc](https://api.github.com/users/takenspc)) +- [#135](https://github.com/kpdecker/jsdiff/issues/135) - parsePatch combines patches from multiple files into a single IUniDiff when there is no "Index" line ([@ramya-rao-a](https://api.github.com/users/ramya-rao-a)) +- [#124](https://github.com/kpdecker/jsdiff/issues/124) - IE7/IE8 failure since 2.0.0 ([@boneskull](https://api.github.com/users/boneskull)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.0.0...v3.0.1) + +## v3.0.0 - August 23rd, 2016 + +- [#130](https://github.com/kpdecker/jsdiff/pull/130) - Add callback argument to applyPatches `patched` option ([@piranna](https://api.github.com/users/piranna)) +- [#120](https://github.com/kpdecker/jsdiff/pull/120) - Correctly handle file names containing spaces ([@adius](https://api.github.com/users/adius)) +- [#119](https://github.com/kpdecker/jsdiff/pull/119) - Do single reflow ([@wifiextender](https://api.github.com/users/wifiextender)) +- [#117](https://github.com/kpdecker/jsdiff/pull/117) - Make more usable with long strings. ([@abnbgist](https://api.github.com/users/abnbgist)) + +Compatibility notes: + +- applyPatches patch callback now is async and requires the callback be called to continue operation + +[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.3...v3.0.0) + +## v2.2.3 - May 31st, 2016 + +- [#118](https://github.com/kpdecker/jsdiff/pull/118) - Add a fix for applying 0-length destination patches ([@chaaz](https://api.github.com/users/chaaz)) +- [#115](https://github.com/kpdecker/jsdiff/pull/115) - Fixed grammar in README ([@krizalys](https://api.github.com/users/krizalys)) +- [#113](https://github.com/kpdecker/jsdiff/pull/113) - fix typo ([@vmazare](https://api.github.com/users/vmazare)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.2...v2.2.3) + +## v2.2.2 - March 13th, 2016 + +- [#102](https://github.com/kpdecker/jsdiff/issues/102) - diffJson with dates, returns empty curly braces ([@dr-dimitru](https://api.github.com/users/dr-dimitru)) +- [#97](https://github.com/kpdecker/jsdiff/issues/97) - Whitespaces & diffWords ([@faiwer](https://api.github.com/users/faiwer)) +- [#92](https://github.com/kpdecker/jsdiff/pull/92) - Fixes typo in the readme ([@bg451](https://api.github.com/users/bg451)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.1...v2.2.2) + +## v2.2.1 - November 12th, 2015 + +- [#89](https://github.com/kpdecker/jsdiff/pull/89) - add in display selector to readme ([@FranDias](https://api.github.com/users/FranDias)) +- [#88](https://github.com/kpdecker/jsdiff/pull/88) - Split diffs based on file headers instead of 'Index:' metadata ([@piranna](https://api.github.com/users/piranna)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.0...v2.2.1) + +## v2.2.0 - October 29th, 2015 + +- [#80](https://github.com/kpdecker/jsdiff/pull/80) - Fix a typo: applyPath -> applyPatch ([@fluxxu](https://api.github.com/users/fluxxu)) +- [#83](https://github.com/kpdecker/jsdiff/pull/83) - Add basic fuzzy matching to applyPatch ([@piranna](https://github.com/piranna)) + [Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.0...v2.2.0) + +## v2.2.0 - October 29th, 2015 + +- [#80](https://github.com/kpdecker/jsdiff/pull/80) - Fix a typo: applyPath -> applyPatch ([@fluxxu](https://api.github.com/users/fluxxu)) +- [#83](https://github.com/kpdecker/jsdiff/pull/83) - Add basic fuzzy matching to applyPatch ([@piranna](https://github.com/piranna)) + [Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.3...v2.2.0) ## v2.1.3 - September 30th, 2015 + - [#78](https://github.com/kpdecker/jsdiff/pull/78) - fix: error throwing when apply patch to empty string ([@21paradox](https://api.github.com/users/21paradox)) [Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.2...v2.1.3) ## v2.1.2 - September 23rd, 2015 + - [#76](https://github.com/kpdecker/jsdiff/issues/76) - diff headers give error ([@piranna](https://api.github.com/users/piranna)) [Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.1...v2.1.2) ## v2.1.1 - September 9th, 2015 + - [#73](https://github.com/kpdecker/jsdiff/issues/73) - Is applyPatches() exposed in the API? ([@davidparsson](https://api.github.com/users/davidparsson)) [Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.0...v2.1.1) ## v2.1.0 - August 27th, 2015 + - [#72](https://github.com/kpdecker/jsdiff/issues/72) - Consider using options object API for flag permutations ([@kpdecker](https://api.github.com/users/kpdecker)) - [#70](https://github.com/kpdecker/jsdiff/issues/70) - diffWords treats \n at the end as significant whitespace ([@nesQuick](https://api.github.com/users/nesQuick)) - [#69](https://github.com/kpdecker/jsdiff/issues/69) - Missing count ([@wfalkwallace](https://api.github.com/users/wfalkwallace)) - [#68](https://github.com/kpdecker/jsdiff/issues/68) - diffLines seems broken ([@wfalkwallace](https://api.github.com/users/wfalkwallace)) - [#60](https://github.com/kpdecker/jsdiff/issues/60) - Support multiple diff hunks ([@piranna](https://api.github.com/users/piranna)) -- [#54](https://github.com/kpdecker/jsdiff/issues/54) - Feature Reuqest: 3-way merge ([@mog422](https://api.github.com/users/mog422)) +- [#54](https://github.com/kpdecker/jsdiff/issues/54) - Feature Request: 3-way merge ([@mog422](https://api.github.com/users/mog422)) - [#42](https://github.com/kpdecker/jsdiff/issues/42) - Fuzz factor for applyPatch ([@stuartpb](https://api.github.com/users/stuartpb)) - Move whitespace ignore out of equals method - 542063c - Include source maps in babel output - 7f7ab21 @@ -34,22 +359,26 @@ - Documentation for parsePatch and applyPatches - 27c4b77 Compatibility notes: + - The undocumented ignoreWhitespace flag has been removed from the Diff equality check directly. This implementation may be copied to diff utilities if dependencies existed on this functionality. [Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.2...v2.1.0) ## v2.0.2 - August 8th, 2015 + - [#67](https://github.com/kpdecker/jsdiff/issues/67) - cannot require from npm module in node ([@commenthol](https://api.github.com/users/commenthol)) - Convert to chai since we don’t support IE8 - a96bbad [Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.1...v2.0.2) ## v2.0.1 - August 7th, 2015 + - Add release build at proper step - 57542fd [Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.0...v2.0.1) ## v2.0.0 - August 7th, 2015 + - [#66](https://github.com/kpdecker/jsdiff/issues/66) - Add karma and sauce tests ([@kpdecker](https://api.github.com/users/kpdecker)) - [#65](https://github.com/kpdecker/jsdiff/issues/65) - Create component repository for bower ([@kpdecker](https://api.github.com/users/kpdecker)) - [#64](https://github.com/kpdecker/jsdiff/issues/64) - Automatically call removeEmpty for all tokenizer calls ([@kpdecker](https://api.github.com/users/kpdecker)) @@ -58,12 +387,14 @@ Compatibility notes: - [#29](https://github.com/kpdecker/jsdiff/issues/29) - word tokenizer works only for 7 bit ascii ([@plasmagunman](https://api.github.com/users/plasmagunman)) Compatibility notes: -- `this.removeEmpty` is now called automatically for all instances. If this is not desired, this may be overriden on a per instance basis. + +- `this.removeEmpty` is now called automatically for all instances. If this is not desired, this may be overridden on a per instance basis. - The library has been refactored to use some ES6 features. The external APIs should remain the same, but bower projects that directly referenced the repository will now have to point to the [components/jsdiff](https://github.com/components/jsdiff) repository. [Commits](https://github.com/kpdecker/jsdiff/compare/v1.4.0...v2.0.0) ## v1.4.0 - May 6th, 2015 + - [#57](https://github.com/kpdecker/jsdiff/issues/57) - createPatch -> applyPatch failed. ([@mog422](https://api.github.com/users/mog422)) - [#56](https://github.com/kpdecker/jsdiff/pull/56) - Two files patch ([@rgeissert](https://api.github.com/users/rgeissert)) - [#14](https://github.com/kpdecker/jsdiff/issues/14) - Flip added and removed order? ([@jakesandlund](https://api.github.com/users/jakesandlund)) @@ -71,6 +402,7 @@ Compatibility notes: [Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.2...v1.4.0) ## v1.3.2 - March 30th, 2015 + - [#53](https://github.com/kpdecker/jsdiff/pull/53) - Updated README.MD with Bower installation instructions ([@ofbriggs](https://api.github.com/users/ofbriggs)) - [#49](https://github.com/kpdecker/jsdiff/issues/49) - Cannot read property 'oldlines' of undefined ([@nwtn](https://api.github.com/users/nwtn)) - [#44](https://github.com/kpdecker/jsdiff/issues/44) - invalid-meta jsdiff is missing "main" entry in bower.json @@ -78,27 +410,32 @@ Compatibility notes: [Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.1...v1.3.2) ## v1.3.1 - March 13th, 2015 + - [#52](https://github.com/kpdecker/jsdiff/pull/52) - Fix for #51 Wrong result of JsDiff.diffLines ([@felicienfrancois](https://api.github.com/users/felicienfrancois)) [Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.0...v1.3.1) ## v1.3.0 - March 2nd, 2015 + - [#47](https://github.com/kpdecker/jsdiff/pull/47) - Adding Diff Trimmed Lines ([@JamesGould123](https://api.github.com/users/JamesGould123)) [Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.2...v1.3.0) ## v1.2.2 - January 26th, 2015 + - [#45](https://github.com/kpdecker/jsdiff/pull/45) - Fix AMD module loading ([@pedrocarrico](https://api.github.com/users/pedrocarrico)) - [#43](https://github.com/kpdecker/jsdiff/pull/43) - added a bower file ([@nbrustein](https://api.github.com/users/nbrustein)) [Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.1...v1.2.2) ## v1.2.1 - December 26th, 2014 + - [#41](https://github.com/kpdecker/jsdiff/pull/41) - change condition of using node export system. ([@ironhee](https://api.github.com/users/ironhee)) [Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.0...v1.2.1) ## v1.2.0 - November 29th, 2014 + - [#37](https://github.com/kpdecker/jsdiff/pull/37) - Add support for sentences. ([@vmariano](https://api.github.com/users/vmariano)) - [#28](https://github.com/kpdecker/jsdiff/pull/28) - Implemented diffJson ([@papandreou](https://api.github.com/users/papandreou)) - [#27](https://github.com/kpdecker/jsdiff/issues/27) - Slow to execute over diffs with a large number of changes ([@termi](https://api.github.com/users/termi)) @@ -108,6 +445,7 @@ Compatibility notes: [Commits](https://github.com/kpdecker/jsdiff/compare/v1.1.0...v1.2.0) ## v1.1.0 - November 25th, 2014 + - [#33](https://github.com/kpdecker/jsdiff/pull/33) - AMD and global exports ([@ovcharik](https://api.github.com/users/ovcharik)) - [#32](https://github.com/kpdecker/jsdiff/pull/32) - Add support for component ([@vmariano](https://api.github.com/users/vmariano)) - [#31](https://github.com/kpdecker/jsdiff/pull/31) - Don't rely on Array.prototype.map ([@papandreou](https://api.github.com/users/papandreou)) @@ -115,6 +453,7 @@ Compatibility notes: [Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.8...v1.1.0) ## v1.0.8 - December 22nd, 2013 + - [#24](https://github.com/kpdecker/jsdiff/pull/24) - Handle windows newlines on non windows machines. ([@benogle](https://api.github.com/users/benogle)) - [#23](https://github.com/kpdecker/jsdiff/pull/23) - Prettied up the API formatting a little, and added basic node and web examples ([@airportyh](https://api.github.com/users/airportyh)) diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 000000000..dbd0ae50d --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,15 @@ +import pkg from './package.json' with { type: 'json' }; + +export default [ + // browser-friendly UMD build + { + input: 'libesm/index.js', + output: [ + { + name: 'Diff', + format: 'umd', + file: "./dist/diff.js" + } + ] + } +]; diff --git a/runtime.js b/runtime.js new file mode 100644 index 000000000..e90041a39 --- /dev/null +++ b/runtime.js @@ -0,0 +1,3 @@ +require('@babel/register')({ + ignore: ['libcjs', 'libesm', 'node_modules'] +}); diff --git a/src/convert/dmp.js b/src/convert/dmp.js deleted file mode 100644 index b411dc2de..000000000 --- a/src/convert/dmp.js +++ /dev/null @@ -1,19 +0,0 @@ -// See: http://code.google.com/p/google-diff-match-patch/wiki/API -export function convertChangesToDMP(changes) { - let ret = [], - change, - operation; - for (let i = 0; i < changes.length; i++) { - change = changes[i]; - if (change.added) { - operation = 1; - } else if (change.removed) { - operation = -1; - } else { - operation = 0; - } - - ret.push([operation, change.value]); - } - return ret; -} diff --git a/src/convert/dmp.ts b/src/convert/dmp.ts new file mode 100644 index 000000000..4a980a976 --- /dev/null +++ b/src/convert/dmp.ts @@ -0,0 +1,25 @@ +import type {ChangeObject} from '../types.js'; + +type DmpOperation = 1 | 0 | -1; + +/** + * converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library + */ +export function convertChangesToDMP(changes: ChangeObject[]): [DmpOperation, ValueT][] { + const ret: [DmpOperation, ValueT][] = []; + let change, + operation: DmpOperation; + for (let i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; +} diff --git a/src/convert/xml.js b/src/convert/xml.ts similarity index 63% rename from src/convert/xml.js rename to src/convert/xml.ts index 34aa46366..8f834c1ce 100644 --- a/src/convert/xml.js +++ b/src/convert/xml.ts @@ -1,7 +1,12 @@ -export function convertChangesToXML(changes) { - let ret = []; +import type {ChangeObject} from '../types.js'; + +/** + * converts a list of change objects to a serialized XML format + */ +export function convertChangesToXML(changes: ChangeObject[]): string { + const ret = []; for (let i = 0; i < changes.length; i++) { - let change = changes[i]; + const change = changes[i]; if (change.added) { ret.push(''); } else if (change.removed) { @@ -19,7 +24,7 @@ export function convertChangesToXML(changes) { return ret.join(''); } -function escapeHTML(s) { +function escapeHTML(s: string): string { let n = s; n = n.replace(/&/g, '&'); n = n.replace(/ extends Diff> { + tokenize(value: Array) { + return value.slice(); + } + + join(value: Array) { + return value; + } + + removeEmpty(value: Array) { + return value; + } +} + +export const arrayDiff = new ArrayDiff(); + +/** + * diffs two arrays of tokens, comparing each item for strict equality (===). + * @returns a list of change objects. + */ +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffCallbackNonabortable +): undefined; +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffArraysOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffArraysOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffArraysOptionsAbortable +): ChangeObject[] | undefined +export function diffArrays( + oldArr: T[], + newArr: T[], + options?: DiffArraysOptionsNonabortable +): ChangeObject[] +export function diffArrays( + oldArr: T[], + newArr: T[], + options?: any +): undefined | ChangeObject[] { + return arrayDiff.diff(oldArr, newArr, options); +} diff --git a/src/diff/base.js b/src/diff/base.js deleted file mode 100644 index 3296d3970..000000000 --- a/src/diff/base.js +++ /dev/null @@ -1,220 +0,0 @@ -export default function Diff() {} - -Diff.prototype = { - diff(oldString, newString, options = {}) { - let callback = options.callback; - if (typeof options === 'function') { - callback = options; - options = {}; - } - this.options = options; - - let self = this; - - function done(value) { - if (callback) { - setTimeout(function() { callback(undefined, value); }, 0); - return true; - } else { - return value; - } - } - - // Allow subclasses to massage the input prior to running - oldString = this.castInput(oldString); - newString = this.castInput(newString); - - oldString = this.removeEmpty(this.tokenize(oldString)); - newString = this.removeEmpty(this.tokenize(newString)); - - let newLen = newString.length, oldLen = oldString.length; - let editLength = 1; - let maxEditLength = newLen + oldLen; - let bestPath = [{ newPos: -1, components: [] }]; - - // Seed editLength = 0, i.e. the content starts with the same values - let oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); - if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { - // Identity per the equality and tokenizer - return done([{value: newString.join(''), count: newString.length}]); - } - - // Main worker method. checks all permutations of a given edit length for acceptance. - function execEditLength() { - for (let diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { - let basePath; - let addPath = bestPath[diagonalPath - 1], - removePath = bestPath[diagonalPath + 1], - oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; - if (addPath) { - // No one else is going to attempt to use this value, clear it - bestPath[diagonalPath - 1] = undefined; - } - - let canAdd = addPath && addPath.newPos + 1 < newLen, - canRemove = removePath && 0 <= oldPos && oldPos < oldLen; - if (!canAdd && !canRemove) { - // If this path is a terminal then prune - bestPath[diagonalPath] = undefined; - continue; - } - - // Select the diagonal that we want to branch from. We select the prior - // path whose position in the new string is the farthest from the origin - // and does not pass the bounds of the diff graph - if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { - basePath = clonePath(removePath); - self.pushComponent(basePath.components, undefined, true); - } else { - basePath = addPath; // No need to clone, we've pulled it from the list - basePath.newPos++; - self.pushComponent(basePath.components, true, undefined); - } - - oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); - - // If we have hit the end of both strings, then we are done - if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { - return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken)); - } else { - // Otherwise track this path as a potential candidate and continue. - bestPath[diagonalPath] = basePath; - } - } - - editLength++; - } - - // Performs the length of edit iteration. Is a bit fugly as this has to support the - // sync and async mode which is never fun. Loops over execEditLength until a value - // is produced. - if (callback) { - (function exec() { - setTimeout(function() { - // This should not happen, but we want to be safe. - /* istanbul ignore next */ - if (editLength > maxEditLength) { - return callback(); - } - - if (!execEditLength()) { - exec(); - } - }, 0); - }()); - } else { - while (editLength <= maxEditLength) { - let ret = execEditLength(); - if (ret) { - return ret; - } - } - } - }, - - pushComponent(components, added, removed) { - let last = components[components.length - 1]; - if (last && last.added === added && last.removed === removed) { - // We need to clone here as the component clone operation is just - // as shallow array clone - components[components.length - 1] = {count: last.count + 1, added: added, removed: removed }; - } else { - components.push({count: 1, added: added, removed: removed }); - } - }, - extractCommon(basePath, newString, oldString, diagonalPath) { - let newLen = newString.length, - oldLen = oldString.length, - newPos = basePath.newPos, - oldPos = newPos - diagonalPath, - - commonCount = 0; - while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { - newPos++; - oldPos++; - commonCount++; - } - - if (commonCount) { - basePath.components.push({count: commonCount}); - } - - basePath.newPos = newPos; - return oldPos; - }, - - equals(left, right) { - return left === right; - }, - removeEmpty(array) { - let ret = []; - for (let i = 0; i < array.length; i++) { - if (array[i]) { - ret.push(array[i]); - } - } - return ret; - }, - castInput(value) { - return value; - }, - tokenize(value) { - return value.split(''); - } -}; - -function buildValues(diff, components, newString, oldString, useLongestToken) { - let componentPos = 0, - componentLen = components.length, - newPos = 0, - oldPos = 0; - - for (; componentPos < componentLen; componentPos++) { - let component = components[componentPos]; - if (!component.removed) { - if (!component.added && useLongestToken) { - let value = newString.slice(newPos, newPos + component.count); - value = value.map(function(value, i) { - let oldValue = oldString[oldPos + i]; - return oldValue.length > value.length ? oldValue : value; - }); - - component.value = value.join(''); - } else { - component.value = newString.slice(newPos, newPos + component.count).join(''); - } - newPos += component.count; - - // Common case - if (!component.added) { - oldPos += component.count; - } - } else { - component.value = oldString.slice(oldPos, oldPos + component.count).join(''); - oldPos += component.count; - - // Reverse add and remove so removes are output first to match common convention - // The diffing algorithm is tied to add then remove output and this is the simplest - // route to get the desired output with minimal overhead. - if (componentPos && components[componentPos - 1].added) { - let tmp = components[componentPos - 1]; - components[componentPos - 1] = components[componentPos]; - components[componentPos] = tmp; - } - } - } - - // Special case handle for when one terminal is ignored. For this case we merge the - // terminal into the prior string and drop the change. - let lastComponent = components[componentLen - 1]; - if ((lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { - components[componentLen - 2].value += lastComponent.value; - components.pop(); - } - - return components; -} - -function clonePath(path) { - return { newPos: path.newPos, components: path.components.slice(0) }; -} diff --git a/src/diff/base.ts b/src/diff/base.ts new file mode 100644 index 000000000..ef2dd462f --- /dev/null +++ b/src/diff/base.ts @@ -0,0 +1,366 @@ +import type {ChangeObject, AllDiffOptions, AbortableDiffOptions, DiffCallbackNonabortable, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackAbortable, TimeoutOption, MaxEditLengthOption} from '../types.js'; + +/** + * Like a ChangeObject, but with no value and an extra `previousComponent` property. + * A linked list of these (linked via `.previousComponent`) is used internally in the code below to + * keep track of the state of the diffing algorithm, but gets converted to an array of + * ChangeObjects before being returned to the caller. + */ +interface DraftChangeObject { + added: boolean; + removed: boolean; + count: number; + previousComponent?: DraftChangeObject; + + // Only added in buildValues: + value?: any; +} + +interface Path { + oldPos: number; + lastComponent: DraftChangeObject | undefined +} + +export default class Diff< + TokenT, + ValueT extends Iterable = Iterable, + InputValueT = ValueT +> { + diff( + oldStr: InputValueT, + newStr: InputValueT, + options: DiffCallbackNonabortable + ): undefined; + diff( + oldStr: InputValueT, + newStr: InputValueT, + options: AllDiffOptions & AbortableDiffOptions & CallbackOptionAbortable + ): undefined + diff( + oldStr: InputValueT, + newStr: InputValueT, + options: AllDiffOptions & CallbackOptionNonabortable + ): undefined + diff( + oldStr: InputValueT, + newStr: InputValueT, + options: AllDiffOptions & AbortableDiffOptions + ): ChangeObject[] | undefined + diff( + oldStr: InputValueT, + newStr: InputValueT, + options?: AllDiffOptions + ): ChangeObject[] + diff( + oldStr: InputValueT, + newStr: InputValueT, + // Type below is not accurate/complete - see above for full possibilities - but it compiles + options: DiffCallbackNonabortable | AllDiffOptions & Partial> = {} + ): ChangeObject[] | undefined { + let callback: DiffCallbackAbortable | DiffCallbackNonabortable | undefined; + if (typeof options === 'function') { + callback = options; + options = {}; + } else if ('callback' in options) { + callback = options.callback; + } + // Allow subclasses to massage the input prior to running + const oldString = this.castInput(oldStr, options); + const newString = this.castInput(newStr, options); + + const oldTokens = this.removeEmpty(this.tokenize(oldString, options)); + const newTokens = this.removeEmpty(this.tokenize(newString, options)); + + return this.diffWithOptionsObj(oldTokens, newTokens, options, callback); + } + + private diffWithOptionsObj( + oldTokens: TokenT[], + newTokens: TokenT[], + options: AllDiffOptions & Partial & Partial, + callback: DiffCallbackAbortable | DiffCallbackNonabortable | undefined + ): ChangeObject[] | undefined { + const done = (value: ChangeObject[]) => { + value = this.postProcess(value, options); + if (callback) { + setTimeout(function() { callback(value); }, 0); + return undefined; + } else { + return value; + } + }; + + const newLen = newTokens.length, oldLen = oldTokens.length; + let editLength = 1; + let maxEditLength = newLen + oldLen; + if(options.maxEditLength != null) { + maxEditLength = Math.min(maxEditLength, options.maxEditLength); + } + const maxExecutionTime = options.timeout ?? Infinity; + const abortAfterTimestamp = Date.now() + maxExecutionTime; + + const bestPath: Path[] = [{ oldPos: -1, lastComponent: undefined }]; + + // Seed editLength = 0, i.e. the content starts with the same values + let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options); + if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + // Identity per the equality and tokenizer + return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens)); + } + + // Once we hit the right edge of the edit graph on some diagonal k, we can + // definitely reach the end of the edit graph in no more than k edits, so + // there's no point in considering any moves to diagonal k+1 any more (from + // which we're guaranteed to need at least k+1 more edits). + // Similarly, once we've reached the bottom of the edit graph, there's no + // point considering moves to lower diagonals. + // We record this fact by setting minDiagonalToConsider and + // maxDiagonalToConsider to some finite value once we've hit the edge of + // the edit graph. + // This optimization is not faithful to the original algorithm presented in + // Myers's paper, which instead pointlessly extends D-paths off the end of + // the edit graph - see page 7 of Myers's paper which notes this point + // explicitly and illustrates it with a diagram. This has major performance + // implications for some common scenarios. For instance, to compute a diff + // where the new text simply appends d characters on the end of the + // original text of length n, the true Myers algorithm will take O(n+d^2) + // time while this optimization needs only O(n+d) time. + let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; + + // Main worker method. checks all permutations of a given edit length for acceptance. + const execEditLength = () => { + for ( + let diagonalPath = Math.max(minDiagonalToConsider, -editLength); + diagonalPath <= Math.min(maxDiagonalToConsider, editLength); + diagonalPath += 2 + ) { + let basePath; + const removePath = bestPath[diagonalPath - 1], + addPath = bestPath[diagonalPath + 1]; + if (removePath) { + // No one else is going to attempt to use this value, clear it + // @ts-expect-error - perf optimisation. This type-violating value will never be read. + bestPath[diagonalPath - 1] = undefined; + } + + let canAdd = false; + if (addPath) { + // what newPos will be after we do an insertion: + const addPathNewPos = addPath.oldPos - diagonalPath; + canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; + } + + const canRemove = removePath && removePath.oldPos + 1 < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + // @ts-expect-error - perf optimisation. This type-violating value will never be read. + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the old string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) { + basePath = this.addToPath(addPath, true, false, 0, options); + } else { + basePath = this.addToPath(removePath, false, true, 1, options); + } + + newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options); + + if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + // If we have hit the end of both strings, then we are done + return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true; + } else { + bestPath[diagonalPath] = basePath; + if (basePath.oldPos + 1 >= oldLen) { + maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); + } + if (newPos + 1 >= newLen) { + minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); + } + } + } + + editLength++; + }; + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced, or until the edit length exceeds options.maxEditLength (if given), + // in which case it will return undefined. + if (callback) { + (function exec() { + setTimeout(function() { + if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { + return (callback as DiffCallbackAbortable)(undefined); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + }()); + } else { + while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { + const ret = execEditLength(); + if (ret) { + return ret as ChangeObject[]; + } + } + } + } + + private addToPath( + path: Path, + added: boolean, + removed: boolean, + oldPosInc: number, + options: AllDiffOptions + ): Path { + const last = path.lastComponent; + if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: {count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent } + }; + } else { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: {count: 1, added: added, removed: removed, previousComponent: last } + }; + } + } + + private extractCommon( + basePath: Path, + newTokens: TokenT[], + oldTokens: TokenT[], + diagonalPath: number, + options: AllDiffOptions + ): number { + const newLen = newTokens.length, + oldLen = oldTokens.length; + let oldPos = basePath.oldPos, + newPos = oldPos - diagonalPath, + commonCount = 0; + + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) { + newPos++; + oldPos++; + commonCount++; + if (options.oneChangePerToken) { + basePath.lastComponent = {count: 1, previousComponent: basePath.lastComponent, added: false, removed: false}; + } + } + + if (commonCount && !options.oneChangePerToken) { + basePath.lastComponent = {count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false}; + } + + basePath.oldPos = oldPos; + return newPos; + } + + equals(left: TokenT, right: TokenT, options: AllDiffOptions): boolean { + if (options.comparator) { + return options.comparator(left, right); + } else { + return left === right + || (!!options.ignoreCase && (left as string).toLowerCase() === (right as string).toLowerCase()); + } + } + + removeEmpty(array: TokenT[]): TokenT[] { + const ret: TokenT[] = []; + for (let i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + castInput(value: InputValueT, options: AllDiffOptions): ValueT { + return value as unknown as ValueT; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + tokenize(value: ValueT, options: AllDiffOptions): TokenT[] { + return Array.from(value); + } + + join(chars: TokenT[]): ValueT { + // Assumes ValueT is string, which is the case for most subclasses. + // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op) + // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF + // assume tokens and values are strings, but not completely - is weird and janky. + return (chars as string[]).join('') as unknown as ValueT; + } + + postProcess( + changeObjects: ChangeObject[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options: AllDiffOptions + ): ChangeObject[] { + return changeObjects; + } + + get useLongestToken(): boolean { + return false; + } + + private buildValues( + lastComponent: DraftChangeObject | undefined, + newTokens: TokenT[], + oldTokens: TokenT[] + ): ChangeObject[] { + // First we convert our linked list of components in reverse order to an + // array in the right order: + const components: DraftChangeObject[] = []; + let nextComponent; + while (lastComponent) { + components.push(lastComponent); + nextComponent = lastComponent.previousComponent; + delete lastComponent.previousComponent; + lastComponent = nextComponent; + } + components.reverse(); + + const componentLen = components.length; + let componentPos = 0, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + const component = components[componentPos]; + if (!component.removed) { + if (!component.added && this.useLongestToken) { + let value = newTokens.slice(newPos, newPos + component.count); + value = value.map(function(value, i) { + const oldValue = oldTokens[oldPos + i]; + return (oldValue as string).length > (value as string).length ? oldValue : value; + }); + + component.value = this.join(value); + } else { + component.value = this.join(newTokens.slice(newPos, newPos + component.count)); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count)); + oldPos += component.count; + } + } + + return components as ChangeObject[]; + } +} + diff --git a/src/diff/character.js b/src/diff/character.js deleted file mode 100644 index 6911e3ee0..000000000 --- a/src/diff/character.js +++ /dev/null @@ -1,4 +0,0 @@ -import Diff from './base'; - -export const characterDiff = new Diff(); -export function diffChars(oldStr, newStr, callback) { return characterDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/character.ts b/src/diff/character.ts new file mode 100644 index 000000000..269624840 --- /dev/null +++ b/src/diff/character.ts @@ -0,0 +1,46 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffCharsOptionsAbortable, DiffCharsOptionsNonabortable} from '../types.js'; + +class CharacterDiff extends Diff {} + +export const characterDiff = new CharacterDiff(); + +/** + * diffs two blocks of text, treating each character as a token. + * + * ("Characters" here means Unicode code points - the elements you get when you loop over a string with a `for ... of ...` loop.) + * + * @returns a list of change objects. + */ +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCharsOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCharsOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCharsOptionsAbortable +): ChangeObject[] | undefined +export function diffChars( + oldStr: string, + newStr: string, + options?: DiffCharsOptionsNonabortable +): ChangeObject[] +export function diffChars( + oldStr: string, + newStr: string, + options?: any +): undefined | ChangeObject[] { + return characterDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/css.js b/src/diff/css.js deleted file mode 100644 index e2e445a60..000000000 --- a/src/diff/css.js +++ /dev/null @@ -1,8 +0,0 @@ -import Diff from './base'; - -export const cssDiff = new Diff(); -cssDiff.tokenize = function(value) { - return value.split(/([{}:;,]|\s+)/); -}; - -export function diffCss(oldStr, newStr, callback) { return cssDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/css.ts b/src/diff/css.ts new file mode 100644 index 000000000..42080ba6d --- /dev/null +++ b/src/diff/css.ts @@ -0,0 +1,44 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffCssOptionsAbortable, DiffCssOptionsNonabortable} from '../types.js'; + +class CssDiff extends Diff { + tokenize(value: string) { + return value.split(/([{}:;,]|\s+)/); + } +} + +export const cssDiff = new CssDiff(); + +/** + * diffs two blocks of text, comparing CSS tokens. + * + * @returns a list of change objects. + */ +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCssOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCssOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCssOptionsAbortable +): ChangeObject[] | undefined +export function diffCss( + oldStr: string, + newStr: string, + options?: DiffCssOptionsNonabortable +): ChangeObject[] +export function diffCss(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return cssDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/json.js b/src/diff/json.js deleted file mode 100644 index 72c046a80..000000000 --- a/src/diff/json.js +++ /dev/null @@ -1,71 +0,0 @@ -import Diff from './base'; -import {lineDiff} from './line'; - -const objectPrototypeToString = Object.prototype.toString; - - -export const jsonDiff = new Diff(); -// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a -// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: -jsonDiff.useLongestToken = true; - -jsonDiff.tokenize = lineDiff.tokenize; -jsonDiff.castInput = function(value) { - return typeof value === 'string' ? value : JSON.stringify(canonicalize(value), undefined, ' '); -}; -jsonDiff.equals = function(left, right) { - return Diff.prototype.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); -}; - -export function diffJson(oldObj, newObj, callback) { return jsonDiff.diff(oldObj, newObj, callback); } - - -// This function handles the presence of circular references by bailing out when encountering an -// object that is already on the "stack" of items being processed. -export function canonicalize(obj, stack, replacementStack) { - stack = stack || []; - replacementStack = replacementStack || []; - - let i; - - for (i = 0; i < stack.length; i += 1) { - if (stack[i] === obj) { - return replacementStack[i]; - } - } - - let canonicalizedObj; - - if ('[object Array]' === objectPrototypeToString.call(obj)) { - stack.push(obj); - canonicalizedObj = new Array(obj.length); - replacementStack.push(canonicalizedObj); - for (i = 0; i < obj.length; i += 1) { - canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); - } - stack.pop(); - replacementStack.pop(); - } else if (typeof obj === 'object' && obj !== null) { - stack.push(obj); - canonicalizedObj = {}; - replacementStack.push(canonicalizedObj); - let sortedKeys = [], - key; - for (key in obj) { - /* istanbul ignore else */ - if (obj.hasOwnProperty(key)) { - sortedKeys.push(key); - } - } - sortedKeys.sort(); - for (i = 0; i < sortedKeys.length; i += 1) { - key = sortedKeys[i]; - canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); - } - stack.pop(); - replacementStack.pop(); - } else { - canonicalizedObj = obj; - } - return canonicalizedObj; -} diff --git a/src/diff/json.ts b/src/diff/json.ts new file mode 100644 index 000000000..898cd3f4e --- /dev/null +++ b/src/diff/json.ts @@ -0,0 +1,127 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffJsonOptionsAbortable, DiffJsonOptionsNonabortable} from '../types.js'; +import { tokenize } from './line.js'; + +class JsonDiff extends Diff { + get useLongestToken() { + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + return true; + } + + tokenize = tokenize; + + castInput(value: string | object, options: DiffJsonOptionsNonabortable | DiffJsonOptionsAbortable) { + const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; + + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, ' '); + } + + equals(left: string, right: string, options: DiffJsonOptionsNonabortable | DiffJsonOptionsAbortable) { + return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); + } +} + +export const jsonDiff = new JsonDiff(); + +/** + * diffs two JSON-serializable objects by first serializing them to prettily-formatted JSON and then treating each line of the JSON as a token. + * Object properties are ordered alphabetically in the serialized JSON, so the order of properties in the objects being compared doesn't affect the result. + * + * @returns a list of change objects. + */ +export function diffJson( + oldStr: string | object, + newStr: string | object, + options: DiffCallbackNonabortable +): undefined; +export function diffJson( + oldStr: string | object, + newStr: string | object, + options: DiffJsonOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffJson( + oldStr: string | object, + newStr: string | object, + options: DiffJsonOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffJson( + oldStr: string | object, + newStr: string | object, + options: DiffJsonOptionsAbortable +): ChangeObject[] | undefined +export function diffJson( + oldStr: string | object, + newStr: string | object, + options?: DiffJsonOptionsNonabortable +): ChangeObject[] +export function diffJson(oldStr: string | object, newStr: string | object, options?: any): undefined | ChangeObject[] { + return jsonDiff.diff(oldStr, newStr, options); +} + + +// This function handles the presence of circular references by bailing out when encountering an +// object that is already on the "stack" of items being processed. Accepts an optional replacer +export function canonicalize( + obj: any, + stack: Array | null, replacementStack: Array | null, + replacer: (k: string, v: any) => any, + key?: string +) { + stack = stack || []; + replacementStack = replacementStack || []; + + if (replacer) { + obj = replacer(key === undefined ? '' : key, obj); + } + + let i; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + let canonicalizedObj: any; + + if ('[object Array]' === Object.prototype.toString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i)); + } + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + + if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + const sortedKeys = []; + let key; + for (key in obj) { + /* istanbul ignore else */ + if (Object.prototype.hasOwnProperty.call(obj, key)) { + sortedKeys.push(key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + key = sortedKeys[i]; + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; +} diff --git a/src/diff/line.js b/src/diff/line.js deleted file mode 100644 index f9b4f4993..000000000 --- a/src/diff/line.js +++ /dev/null @@ -1,35 +0,0 @@ -import Diff from './base'; -import {generateOptions} from '../util/params'; - -export const lineDiff = new Diff(); -lineDiff.tokenize = function(value) { - let retLines = [], - linesAndNewlines = value.split(/(\n|\r\n)/); - - // Ignore the final empty token that occurs if the string ends with a new line - if (!linesAndNewlines[linesAndNewlines.length - 1]) { - linesAndNewlines.pop(); - } - - // Merge the content and line separators into single tokens - for (let i = 0; i < linesAndNewlines.length; i++) { - let line = linesAndNewlines[i]; - - if (i % 2 && !this.options.newlineIsToken) { - retLines[retLines.length - 1] += line; - } else { - if (this.options.ignoreWhitespace) { - line = line.trim(); - } - retLines.push(line); - } - } - - return retLines; -}; - -export function diffLines(oldStr, newStr, callback) { return lineDiff.diff(oldStr, newStr, callback); } -export function diffTrimmedLines(oldStr, newStr, callback) { - let options = generateOptions(callback, {ignoreWhitespace: true}); - return lineDiff.diff(oldStr, newStr, options); -} diff --git a/src/diff/line.ts b/src/diff/line.ts new file mode 100644 index 000000000..53de7c21d --- /dev/null +++ b/src/diff/line.ts @@ -0,0 +1,133 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffLinesOptionsAbortable, DiffLinesOptionsNonabortable} from '../types.js'; +import {generateOptions} from '../util/params.js'; + +class LineDiff extends Diff { + tokenize = tokenize; + + equals(left: string, right: string, options: DiffLinesOptionsAbortable | DiffLinesOptionsNonabortable) { + // If we're ignoring whitespace, we need to normalise lines by stripping + // whitespace before checking equality. (This has an annoying interaction + // with newlineIsToken that requires special handling: if newlines get their + // own token, then we DON'T want to trim the *newline* tokens down to empty + // strings, since this would cause us to treat whitespace-only line content + // as equal to a separator between lines, which would be weird and + // inconsistent with the documented behavior of the options.) + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes('\n')) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes('\n')) { + right = right.trim(); + } + } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { + if (left.endsWith('\n')) { + left = left.slice(0, -1); + } + if (right.endsWith('\n')) { + right = right.slice(0, -1); + } + } + return super.equals(left, right, options); + } +} + +export const lineDiff = new LineDiff(); + +/** + * diffs two blocks of text, treating each line as a token. + * @returns a list of change objects. + */ +export function diffLines( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable +): ChangeObject[] | undefined +export function diffLines( + oldStr: string, + newStr: string, + options?: DiffLinesOptionsNonabortable +): ChangeObject[] +export function diffLines(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return lineDiff.diff(oldStr, newStr, options); +} + +// Kept for backwards compatibility. This is a rather arbitrary wrapper method +// that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to +// have two ways to do exactly the same thing in the API, so we no longer +// document this one (library users should explicitly use `diffLines` with +// `ignoreWhitespace: true` instead) but we keep it around to maintain +// compatibility with code that used old versions. +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable +): ChangeObject[] | undefined +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options?: DiffLinesOptionsNonabortable +): ChangeObject[] +export function diffTrimmedLines(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + options = generateOptions(options, {ignoreWhitespace: true}); + return lineDiff.diff(oldStr, newStr, options); +} + +// Exported standalone so it can be used from jsonDiff too. +export function tokenize(value: string, options: DiffLinesOptionsAbortable | DiffLinesOptionsNonabortable) { + if(options.stripTrailingCr) { + // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior + value = value.replace(/\r\n/g, '\n'); + } + + const retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (let i = 0; i < linesAndNewlines.length; i++) { + const line = linesAndNewlines[i]; + + if (i % 2 && !options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + retLines.push(line); + } + } + + return retLines; +} diff --git a/src/diff/sentence.js b/src/diff/sentence.js deleted file mode 100644 index d855efd3e..000000000 --- a/src/diff/sentence.js +++ /dev/null @@ -1,9 +0,0 @@ -import Diff from './base'; - - -export const sentenceDiff = new Diff(); -sentenceDiff.tokenize = function(value) { - return value.split(/(\S.+?[.!?])(?=\s+|$)/); -}; - -export function diffSentences(oldStr, newStr, callback) { return sentenceDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/sentence.ts b/src/diff/sentence.ts new file mode 100644 index 000000000..1eeb8f0b0 --- /dev/null +++ b/src/diff/sentence.ts @@ -0,0 +1,91 @@ +import Diff from './base.js'; +import type { + ChangeObject, + CallbackOptionAbortable, + CallbackOptionNonabortable, + DiffCallbackNonabortable, + DiffSentencesOptionsAbortable, + DiffSentencesOptionsNonabortable +} from '../types.js'; + +function isSentenceEndPunct(char: string) { + return char == '.' || char == '!' || char == '?'; +} + +class SentenceDiff extends Diff { + tokenize(value: string) { + // If in future we drop support for environments that don't support lookbehinds, we can replace + // this entire function with: + // return value.split(/(?<=[.!?])(\s+|$)/); + // but until then, for similar reasons to the trailingWs function in string.ts, we are forced + // to do this verbosely "by hand" instead of using a regex. + const result = []; + let tokenStartI = 0; + for (let i = 0; i < value.length; i++) { + if (i == value.length - 1) { + result.push(value.slice(tokenStartI)); + break; + } + + if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) { + // We've hit a sentence break - i.e. a punctuation mark followed by whitespace. + // We now want to push TWO tokens to the result: + // 1. the sentence + result.push(value.slice(tokenStartI, i + 1)); + + // 2. the whitespace + i = tokenStartI = i + 1; + while (value[i + 1]?.match(/\s/)) { + i++; + } + result.push(value.slice(tokenStartI, i + 1)); + + // Then the next token (a sentence) starts on the character after the whitespace. + // (It's okay if this is off the end of the string - then the outer loop will terminate + // here anyway.) + tokenStartI = i + 1; + } + } + + return result; + } +} + +export const sentenceDiff = new SentenceDiff(); + +/** + * diffs two blocks of text, treating each sentence, and the whitespace between each pair of sentences, as a token. + * The characters `.`, `!`, and `?`, when followed by whitespace, are treated as marking the end of a sentence; nothing else besides the end of the string is considered to mark a sentence end. + * + * (For more sophisticated detection of sentence breaks, including support for non-English punctuation, consider instead tokenizing with an [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) with `granularity: 'sentence'` and passing the result to `diffArrays`.) + * + * @returns a list of change objects. + */ +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffSentencesOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffSentencesOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffSentencesOptionsAbortable +): ChangeObject[] | undefined +export function diffSentences( + oldStr: string, + newStr: string, + options?: DiffSentencesOptionsNonabortable +): ChangeObject[] +export function diffSentences(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return sentenceDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/word.js b/src/diff/word.js deleted file mode 100644 index 4c846abd5..000000000 --- a/src/diff/word.js +++ /dev/null @@ -1,54 +0,0 @@ -import Diff from './base'; -import {generateOptions} from '../util/params'; - -// Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode -// -// Ranges and exceptions: -// Latin-1 Supplement, 0080–00FF -// - U+00D7 × Multiplication sign -// - U+00F7 ÷ Division sign -// Latin Extended-A, 0100–017F -// Latin Extended-B, 0180–024F -// IPA Extensions, 0250–02AF -// Spacing Modifier Letters, 02B0–02FF -// - U+02C7 ˇ ˇ Caron -// - U+02D8 ˘ ˘ Breve -// - U+02D9 ˙ ˙ Dot Above -// - U+02DA ˚ ˚ Ring Above -// - U+02DB ˛ ˛ Ogonek -// - U+02DC ˜ ˜ Small Tilde -// - U+02DD ˝ ˝ Double Acute Accent -// Latin Extended Additional, 1E00–1EFF -const extendedWordChars = /^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u; - -const reWhitespace = /\S/; - -export const wordDiff = new Diff(); -wordDiff.equals = function(left, right) { - return left === right || (this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); -}; -wordDiff.tokenize = function(value) { - let tokens = value.split(/(\s+|\b)/); - - // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. - for (let i = 0; i < tokens.length - 1; i++) { - // If we have an empty string in the next field and we have only word chars before and after, merge - if (!tokens[i + 1] && tokens[i + 2] - && extendedWordChars.test(tokens[i]) - && extendedWordChars.test(tokens[i + 2])) { - tokens[i] += tokens[i + 2]; - tokens.splice(i + 1, 2); - i--; - } - } - - return tokens; -}; - -export function diffWords(oldStr, newStr, callback) { - let options = generateOptions(callback, {ignoreWhitespace: true}); - return wordDiff.diff(oldStr, newStr, options); -} -export function diffWordsWithSpace(oldStr, newStr, callback) { - return wordDiff.diff(oldStr, newStr, callback); -} diff --git a/src/diff/word.ts b/src/diff/word.ts new file mode 100644 index 000000000..3018964fe --- /dev/null +++ b/src/diff/word.ts @@ -0,0 +1,368 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffWordsOptionsAbortable, DiffWordsOptionsNonabortable} from '../types.js'; +import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs, leadingAndTrailingWs, segment } from '../util/string.js'; + +// Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode +// +// Chars/ranges counted as "word" characters by this regex are as follows: +// +// + U+00AD Soft hyphen +// + 00C0–00FF (letters with diacritics from the Latin-1 Supplement), except: +// - U+00D7 × Multiplication sign +// - U+00F7 ÷ Division sign +// + Latin Extended-A, 0100–017F +// + Latin Extended-B, 0180–024F +// + IPA Extensions, 0250–02AF +// + Spacing Modifier Letters, 02B0–02FF, except: +// - U+02C7 ˇ ˇ Caron +// - U+02D8 ˘ ˘ Breve +// - U+02D9 ˙ ˙ Dot Above +// - U+02DA ˚ ˚ Ring Above +// - U+02DB ˛ ˛ Ogonek +// - U+02DC ˜ ˜ Small Tilde +// - U+02DD ˝ ˝ Double Acute Accent +// + Latin Extended Additional, 1E00–1EFF +const extendedWordChars = 'a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}'; + +// Each token is one of the following: +// - A punctuation mark plus the surrounding whitespace +// - A word plus the surrounding whitespace +// - Pure whitespace (but only in the special case where the entire text +// is just whitespace) +// +// We have to include surrounding whitespace in the tokens because the two +// alternative approaches produce horribly broken results: +// * If we just discard the whitespace, we can't fully reproduce the original +// text from the sequence of tokens and any attempt to render the diff will +// get the whitespace wrong. +// * If we have separate tokens for whitespace, then in a typical text every +// second token will be a single space character. But this often results in +// the optimal diff between two texts being a perverse one that preserves +// the spaces between words but deletes and reinserts actual common words. +// See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640 +// for an example. +// +// Keeping the surrounding whitespace of course has implications for .equals +// and .join, not just .tokenize. + +// This regex does NOT fully implement the tokenization rules described above. +// Instead, it gives runs of whitespace their own "token". The tokenize method +// then handles stitching whitespace tokens onto adjacent word or punctuation +// tokens. +const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug'); + + +class WordDiff extends Diff { + equals(left: string, right: string, options: DiffWordsOptionsAbortable | DiffWordsOptionsNonabortable) { + if (options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + + return left.trim() === right.trim(); + } + + tokenize(value: string, options: DiffWordsOptionsAbortable | DiffWordsOptionsNonabortable = {}) { + let parts; + if (options.intlSegmenter) { + const segmenter: Intl.Segmenter = options.intlSegmenter; + if (segmenter.resolvedOptions().granularity != 'word') { + throw new Error('The segmenter passed must have a granularity of "word"'); + } + // We want `parts` to be an array whose elements alternate between being + // pure whitespace and being pure non-whitespace. This is ALMOST what the + // segments returned by a word-based Intl.Segmenter already look like, + // but not quite - see explanation in the docs of our custom segment() + // function. + parts = segment(value, segmenter); + } else { + parts = value.match(tokenizeIncludingWhitespace) || []; + } + const tokens: string[] = []; + let prevPart: string | null = null; + parts.forEach(part => { + if ((/\s/).test(part)) { + if (prevPart == null) { + tokens.push(part); + } else { + tokens.push(tokens.pop() + part); + } + } else if (prevPart != null && (/\s/).test(prevPart)) { + if (tokens[tokens.length - 1] == prevPart) { + tokens.push(tokens.pop() + part); + } else { + tokens.push(prevPart + part); + } + } else { + tokens.push(part); + } + + prevPart = part; + }); + return tokens; + } + + join(tokens: string[]) { + // Tokens being joined here will always have appeared consecutively in the + // same text, so we can simply strip off the leading whitespace from all the + // tokens except the first (and except any whitespace-only tokens - but such + // a token will always be the first and only token anyway) and then join them + // and the whitespace around words and punctuation will end up correct. + return tokens.map((token, i) => { + if (i == 0) { + return token; + } else { + return token.replace((/^\s+/), ''); + } + }).join(''); + } + + postProcess(changes: ChangeObject[], options: any) { + if (!changes || options.oneChangePerToken) { + return changes; + } + + let lastKeep: ChangeObject | null = null; + // Change objects representing any insertion or deletion since the last + // "keep" change object. There can be at most one of each. + let insertion: ChangeObject | null = null; + let deletion: ChangeObject | null = null; + changes.forEach(change => { + if (change.added) { + insertion = change; + } else if (change.removed) { + deletion = change; + } else { + if (insertion || deletion) { // May be false at start of text + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter); + } + lastKeep = change; + insertion = null; + deletion = null; + } + }); + if (insertion || deletion) { + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter); + } + return changes; + } +} + +export const wordDiff = new WordDiff(); + +/** + * diffs two blocks of text, treating each word and each punctuation mark as a token. + * Whitespace is ignored when computing the diff (but preserved as far as possible in the final change objects). + * + * @returns a list of change objects. + */ +export function diffWords( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffWords( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffWords( + oldStr: string, + newStr: string, + options: DiffWordsOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffWords( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable +): ChangeObject[] | undefined +export function diffWords( + oldStr: string, + newStr: string, + options?: DiffWordsOptionsNonabortable +): ChangeObject[] +export function diffWords(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + // This option has never been documented and never will be (it's clearer to + // just call `diffWordsWithSpace` directly if you need that behavior), but + // has existed in jsdiff for a long time, so we retain support for it here + // for the sake of backwards compatibility. + if (options?.ignoreWhitespace != null && !options.ignoreWhitespace) { + return diffWordsWithSpace(oldStr, newStr, options); + } + + return wordDiff.diff(oldStr, newStr, options); +} + +function dedupeWhitespaceInChangeObjects( + startKeep: ChangeObject | null, + deletion: ChangeObject | null, + insertion: ChangeObject | null, + endKeep: ChangeObject | null, + segmenter?: Intl.Segmenter +) { + // Before returning, we tidy up the leading and trailing whitespace of the + // change objects to eliminate cases where trailing whitespace in one object + // is repeated as leading whitespace in the next. + // Below are examples of the outcomes we want here to explain the code. + // I=insert, K=keep, D=delete + // 1. diffing 'foo bar baz' vs 'foo baz' + // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz' + // After cleanup, we want: K:'foo ' D:'bar ' K:'baz' + // + // 2. Diffing 'foo bar baz' vs 'foo qux baz' + // Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz' + // After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz' + // + // 3. Diffing 'foo\nbar baz' vs 'foo baz' + // Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz' + // After cleanup, we want K'foo' D:'\nbar' K:' baz' + // + // 4. Diffing 'foo baz' vs 'foo\nbar baz' + // Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz' + // After cleanup, we ideally want K'foo' I:'\nbar' K:' baz' + // but don't actually manage this currently (the pre-cleanup change + // objects don't contain enough information to make it possible). + // + // 5. Diffing 'foo bar baz' vs 'foo baz' + // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz' + // After cleanup, we want K:'foo ' D:' bar ' K:'baz' + // + // Our handling is unavoidably imperfect in the case where there's a single + // indel between keeps and the whitespace has changed. For instance, consider + // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change + // object to represent the insertion of the space character (which isn't even + // a token), we have no way to avoid losing information about the texts' + // original whitespace in the result we return. Still, we do our best to + // output something that will look sensible if we e.g. print it with + // insertions in green and deletions in red. + + // Between two "keep" change objects (or before the first or after the last + // change object), we can have either: + // * A "delete" followed by an "insert" + // * Just an "insert" + // * Just a "delete" + // We handle the three cases separately. + if (deletion && insertion) { + const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter); + const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter); + + if (startKeep) { + const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix); + startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix); + deletion.value = removePrefix(deletion.value, commonWsPrefix); + insertion.value = removePrefix(insertion.value, commonWsPrefix); + } + if (endKeep) { + const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix); + endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix); + deletion.value = removeSuffix(deletion.value, commonWsSuffix); + insertion.value = removeSuffix(insertion.value, commonWsSuffix); + } + } else if (insertion) { + // The whitespaces all reflect what was in the new text rather than + // the old, so we essentially have no information about whitespace + // insertion or deletion. We just want to dedupe the whitespace. + // We do that by having each change object keep its trailing + // whitespace and deleting duplicate leading whitespace where + // present. + if (startKeep) { + const ws = leadingWs(insertion.value, segmenter); + insertion.value = insertion.value.substring(ws.length); + } + if (endKeep) { + const ws = leadingWs(endKeep.value, segmenter); + endKeep.value = endKeep.value.substring(ws.length); + } + // otherwise we've got a deletion and no insertion + } else if (startKeep && endKeep) { + const newWsFull = leadingWs(endKeep.value, segmenter), + [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion!.value, segmenter); + + // Any whitespace that comes straight after startKeep in both the old and + // new texts, assign to startKeep and remove from the deletion. + const newWsStart = longestCommonPrefix(newWsFull, delWsStart); + deletion!.value = removePrefix(deletion!.value, newWsStart); + + // Any whitespace that comes straight before endKeep in both the old and + // new texts, and hasn't already been assigned to startKeep, assign to + // endKeep and remove from the deletion. + const newWsEnd = longestCommonSuffix( + removePrefix(newWsFull, newWsStart), + delWsEnd + ); + deletion!.value = removeSuffix(deletion!.value, newWsEnd); + endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd); + + // If there's any whitespace from the new text that HASN'T already been + // assigned, assign it to the start: + startKeep.value = replaceSuffix( + startKeep.value, + newWsFull, + newWsFull.slice(0, newWsFull.length - newWsEnd.length) + ); + } else if (endKeep) { + // We are at the start of the text. Preserve all the whitespace on + // endKeep, and just remove whitespace from the end of deletion to the + // extent that it overlaps with the start of endKeep. + const endKeepWsPrefix = leadingWs(endKeep.value, segmenter); + const deletionWsSuffix = trailingWs(deletion!.value, segmenter); + const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix); + deletion!.value = removeSuffix(deletion!.value, overlap); + } else if (startKeep) { + // We are at the END of the text. Preserve all the whitespace on + // startKeep, and just remove whitespace from the start of deletion to + // the extent that it overlaps with the end of startKeep. + const startKeepWsSuffix = trailingWs(startKeep.value, segmenter); + const deletionWsPrefix = leadingWs(deletion!.value, segmenter); + const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix); + deletion!.value = removePrefix(deletion!.value, overlap); + } +} + + +class WordsWithSpaceDiff extends Diff { + tokenize(value: string) { + // Slightly different to the tokenizeIncludingWhitespace regex used above in + // that this one treats each individual newline as a distinct token, rather + // than merging them into other surrounding whitespace. This was requested + // in https://github.com/kpdecker/jsdiff/issues/180 & + // https://github.com/kpdecker/jsdiff/issues/211 + const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug'); + return value.match(regex) || []; + } +} + +export const wordsWithSpaceDiff = new WordsWithSpaceDiff(); + +/** + * diffs two blocks of text, treating each word, punctuation mark, newline, or run of (non-newline) whitespace as a token. + * @returns a list of change objects + */ +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffWordsOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable +): ChangeObject[] | undefined +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options?: DiffWordsOptionsNonabortable +): ChangeObject[] +export function diffWordsWithSpace(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return wordsWithSpaceDiff.diff(oldStr, newStr, options); +} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index ae09ed1f5..000000000 --- a/src/index.js +++ /dev/null @@ -1,55 +0,0 @@ -/* See LICENSE file for terms of use */ - -/* - * Text diff implementation. - * - * This library supports the following APIS: - * JsDiff.diffChars: Character by character diff - * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace - * JsDiff.diffLines: Line based diff - * - * JsDiff.diffCss: Diff targeted at CSS content - * - * These methods are based on the implementation proposed in - * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). - * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 - */ -import Diff from './diff/base'; -import {diffChars} from './diff/character'; -import {diffWords, diffWordsWithSpace} from './diff/word'; -import {diffLines, diffTrimmedLines} from './diff/line'; -import {diffSentences} from './diff/sentence'; - -import {diffCss} from './diff/css'; -import {diffJson, canonicalize} from './diff/json'; - -import {applyPatch, applyPatches} from './patch/apply'; -import {parsePatch} from './patch/parse'; -import {structuredPatch, createTwoFilesPatch, createPatch} from './patch/create'; - -import {convertChangesToDMP} from './convert/dmp'; -import {convertChangesToXML} from './convert/xml'; - -export { - Diff, - - diffChars, - diffWords, - diffWordsWithSpace, - diffLines, - diffTrimmedLines, - diffSentences, - - diffCss, - diffJson, - - structuredPatch, - createTwoFilesPatch, - createPatch, - applyPatch, - applyPatches, - parsePatch, - convertChangesToDMP, - convertChangesToXML, - canonicalize -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..526f3a139 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,139 @@ +/* See LICENSE file for terms of use */ + +/* + * Text diff implementation. + * + * This library supports the following APIs: + * Diff.diffChars: Character by character diff + * Diff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * Diff.diffLines: Line based diff + * + * Diff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +import Diff from './diff/base.js'; +import {diffChars, characterDiff} from './diff/character.js'; +import {diffWords, diffWordsWithSpace, wordDiff, wordsWithSpaceDiff} from './diff/word.js'; +import {diffLines, diffTrimmedLines, lineDiff} from './diff/line.js'; +import {diffSentences, sentenceDiff} from './diff/sentence.js'; + +import {diffCss, cssDiff} from './diff/css.js'; +import {diffJson, canonicalize, jsonDiff} from './diff/json.js'; + +import {diffArrays, arrayDiff} from './diff/array.js'; + +import {applyPatch, applyPatches} from './patch/apply.js'; +import type {ApplyPatchOptions, ApplyPatchesOptions} from './patch/apply.js'; +import {parsePatch} from './patch/parse.js'; +import {reversePatch} from './patch/reverse.js'; +import { + structuredPatch, + createTwoFilesPatch, + createPatch, + formatPatch, + INCLUDE_HEADERS, + FILE_HEADERS_ONLY, + OMIT_HEADERS +} from './patch/create.js'; +import type { + StructuredPatchOptionsAbortable, + StructuredPatchOptionsNonabortable, + CreatePatchOptionsAbortable, + CreatePatchOptionsNonabortable, + HeaderOptions +} from './patch/create.js'; + +import {convertChangesToDMP} from './convert/dmp.js'; +import {convertChangesToXML} from './convert/xml.js'; +import type { + ChangeObject, + Change, + ArrayChange, + DiffArraysOptionsAbortable, + DiffArraysOptionsNonabortable, + DiffCharsOptionsAbortable, + DiffCharsOptionsNonabortable, + DiffLinesOptionsAbortable, + DiffLinesOptionsNonabortable, + DiffWordsOptionsAbortable, + DiffWordsOptionsNonabortable, + DiffSentencesOptionsAbortable, + DiffSentencesOptionsNonabortable, + DiffJsonOptionsAbortable, + DiffJsonOptionsNonabortable, + DiffCssOptionsAbortable, + DiffCssOptionsNonabortable, + StructuredPatch, + StructuredPatchHunk +} from './types.js'; + +export { + Diff, + + diffChars, + characterDiff, + diffWords, + wordDiff, + diffWordsWithSpace, + wordsWithSpaceDiff, + diffLines, + lineDiff, + diffTrimmedLines, + diffSentences, + sentenceDiff, + diffCss, + cssDiff, + diffJson, + jsonDiff, + diffArrays, + arrayDiff, + + structuredPatch, + createTwoFilesPatch, + createPatch, + formatPatch, + INCLUDE_HEADERS, + FILE_HEADERS_ONLY, + OMIT_HEADERS, + applyPatch, + applyPatches, + parsePatch, + reversePatch, + convertChangesToDMP, + convertChangesToXML, + canonicalize +}; + +export type { + ChangeObject, + Change, + ArrayChange, + DiffArraysOptionsAbortable, + DiffArraysOptionsNonabortable, + DiffCharsOptionsAbortable, + DiffCharsOptionsNonabortable, + DiffLinesOptionsAbortable, + DiffLinesOptionsNonabortable, + DiffWordsOptionsAbortable, + DiffWordsOptionsNonabortable, + DiffSentencesOptionsAbortable, + DiffSentencesOptionsNonabortable, + DiffJsonOptionsAbortable, + DiffJsonOptionsNonabortable, + DiffCssOptionsAbortable, + DiffCssOptionsNonabortable, + StructuredPatch, + StructuredPatchHunk, + + ApplyPatchOptions, + ApplyPatchesOptions, + + StructuredPatchOptionsAbortable, + StructuredPatchOptionsNonabortable, + CreatePatchOptionsAbortable, + CreatePatchOptionsNonabortable, + HeaderOptions +}; diff --git a/src/patch/apply.js b/src/patch/apply.js deleted file mode 100644 index c86cae261..000000000 --- a/src/patch/apply.js +++ /dev/null @@ -1,102 +0,0 @@ -import {parsePatch} from './parse'; - -export function applyPatch(source, uniDiff, options = {}) { - if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); - } - - if (Array.isArray(uniDiff)) { - if (uniDiff.length > 1) { - throw new Error('applyPatch only works with a single input.'); - } - - uniDiff = uniDiff[0]; - } - - // Apply the diff to the input - let lines = source.split('\n'), - hunks = uniDiff.hunks, - - compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), - errorCount = 0, - fuzzFactor = options.fuzzFactor || 0, - - removeEOFNL, - addEOFNL; - - for (let i = 0; i < hunks.length; i++) { - let hunk = hunks[i], - toPos = hunk.newStart - 1; - - // Sanity check the input string. Bail if we don't match. - for (let j = 0; j < hunk.lines.length; j++) { - let line = hunk.lines[j], - operation = line[0], - content = line.substr(1); - if (operation === ' ' || operation === '-') { - // Context sanity check - if (!compareLine(toPos + 1, lines[toPos], operation, content)) { - errorCount++; - - if (errorCount > fuzzFactor) { - return false; - } - } - } - - if (operation === ' ') { - toPos++; - } else if (operation === '-') { - lines.splice(toPos, 1); - /* istanbul ignore else */ - } else if (operation === '+') { - lines.splice(toPos, 0, content); - toPos++; - } else if (operation === '\\') { - let previousOperation = hunk.lines[j - 1] ? hunk.lines[j - 1][0] : null; - if (previousOperation === '+') { - removeEOFNL = true; - } else if (previousOperation === '-') { - addEOFNL = true; - } - } - } - } - - // Handle EOFNL insertion/removal - if (removeEOFNL) { - while (!lines[lines.length - 1]) { - lines.pop(); - } - } else if (addEOFNL) { - lines.push(''); - } - return lines.join('\n'); -} - -// Wrapper that supports multiple file patches via callbacks. -export function applyPatches(uniDiff, options) { - if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); - } - - let currentIndex = 0; - function processIndex() { - let index = uniDiff[currentIndex++]; - if (!index) { - options.complete(); - } - - options.loadFile(index, function(err, data) { - if (err) { - return options.complete(err); - } - - let updatedContent = applyPatch(data, index, options); - options.patched(index, updatedContent); - - setTimeout(processIndex, 0); - }); - } - processIndex(); -} diff --git a/src/patch/apply.ts b/src/patch/apply.ts new file mode 100644 index 000000000..e4fd21874 --- /dev/null +++ b/src/patch/apply.ts @@ -0,0 +1,366 @@ +import {hasOnlyWinLineEndings, hasOnlyUnixLineEndings} from '../util/string.js'; +import {isWin, isUnix, unixToWin, winToUnix} from './line-endings.js'; +import {parsePatch} from './parse.js'; +import distanceIterator from '../util/distance-iterator.js'; +import type { StructuredPatch } from '../types.js'; + +export interface ApplyPatchOptions { + /** + * Maximum Levenshtein distance (in lines deleted, added, or subtituted) between the context shown in a patch hunk and the lines found in the file. + * @default 0 + */ + fuzzFactor?: number, + /** + * If `true`, and if the file to be patched consistently uses different line endings to the patch (i.e. either the file always uses Unix line endings while the patch uses Windows ones, or vice versa), then `applyPatch` will behave as if the line endings in the patch were the same as those in the source file. + * (If `false`, the patch will usually fail to apply in such circumstances since lines deleted in the patch won't be considered to match those in the source file.) + * @default true + */ + autoConvertLineEndings?: boolean, + /** + * Callback used to compare to given lines to determine if they should be considered equal when patching. + * Defaults to strict equality but may be overridden to provide fuzzier comparison. + * Should return false if the lines should be rejected. + */ + compareLine?: (lineNumber: number, line: string, operation: string, patchContent: string) => boolean, +} + +interface ApplyHunkReturnType { + patchedLines: string[]; + oldLineLastI: number; +} + +/** + * attempts to apply a unified diff patch. + * + * Hunks are applied first to last. + * `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly. + * If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly. + * If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match. + * Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly. + * + * Once a hunk is successfully fitted, the process begins again with the next hunk. + * Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks. + * + * If a hunk cannot be successfully fitted *anywhere* with fewer than `fuzzFactor` mismatches, `applyPatch` fails and returns `false`. + * + * If a hunk is successfully fitted but not at the line number specified by the hunk header, all subsequent hunks have their target line number adjusted accordingly. + * (e.g. if the first hunk is applied 10 lines below where the hunk header said it should fit, `applyPatch` will *start* looking for somewhere to apply the second hunk 10 lines below where its hunk header says it goes.) + * + * If the patch was applied successfully, returns a string containing the patched text. + * If the patch could not be applied (because some hunks in the patch couldn't be fitted to the text in `source`), `applyPatch` returns false. + * + * @param patch a string diff or the output from the `parsePatch` or `structuredPatch` methods. + */ +export function applyPatch( + source: string, + patch: string | StructuredPatch | [StructuredPatch], + options: ApplyPatchOptions = {} +): string | false { + let patches: StructuredPatch[]; + if (typeof patch === 'string') { + patches = parsePatch(patch); + } else if (Array.isArray(patch)) { + patches = patch; + } else { + patches = [patch]; + } + + if (patches.length > 1) { + throw new Error('applyPatch only works with a single input.'); + } + + return applyStructuredPatch(source, patches[0], options); +} + +function applyStructuredPatch( + source: string, + patch: StructuredPatch, + options: ApplyPatchOptions = {} +): string | false { + if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) { + if (hasOnlyWinLineEndings(source) && isUnix(patch)) { + patch = unixToWin(patch); + } else if (hasOnlyUnixLineEndings(source) && isWin(patch)) { + patch = winToUnix(patch); + } + } + + // Apply the diff to the input + const lines = source.split('\n'), + hunks = patch.hunks, + compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), + fuzzFactor = options.fuzzFactor || 0; + let minLine = 0; + + if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) { + throw new Error('fuzzFactor must be a non-negative integer'); + } + + // Special case for empty patch. + if (!hunks.length) { + return source; + } + + // Before anything else, handle EOFNL insertion/removal. If the patch tells us to make a change + // to the EOFNL that is redundant/impossible - i.e. to remove a newline that's not there, or add a + // newline that already exists - then we either return false and fail to apply the patch (if + // fuzzFactor is 0) or simply ignore the problem and do nothing (if fuzzFactor is >0). + // If we do need to remove/add a newline at EOF, this will always be in the final hunk: + let prevLine = '', + removeEOFNL = false, + addEOFNL = false; + for (let i = 0; i < hunks[hunks.length - 1].lines.length; i++) { + const line = hunks[hunks.length - 1].lines[i]; + if (line[0] == '\\') { + if (prevLine[0] == '+') { + removeEOFNL = true; + } else if (prevLine[0] == '-') { + addEOFNL = true; + } + } + prevLine = line; + } + if (removeEOFNL) { + if (addEOFNL) { + // This means the final line gets changed but doesn't have a trailing newline in either the + // original or patched version. In that case, we do nothing if fuzzFactor > 0, and if + // fuzzFactor is 0, we simply validate that the source file has no trailing newline. + if (!fuzzFactor && lines[lines.length - 1] == '') { + return false; + } + } else if (lines[lines.length - 1] == '') { + lines.pop(); + } else if (!fuzzFactor) { + return false; + } + } else if (addEOFNL) { + if (lines[lines.length - 1] != '') { + lines.push(''); + } else if (!fuzzFactor) { + return false; + } + } + + /** + * Checks if the hunk can be made to fit at the provided location with at most `maxErrors` + * insertions, substitutions, or deletions, while ensuring also that: + * - lines deleted in the hunk match exactly, and + * - wherever an insertion operation or block of insertion operations appears in the hunk, the + * immediately preceding and following lines of context match exactly + * + * `toPos` should be set such that lines[toPos] is meant to match hunkLines[0]. + * + * If the hunk can be applied, returns an object with properties `oldLineLastI` and + * `replacementLines`. Otherwise, returns null. + */ + function applyHunk( + hunkLines: string[], + toPos: number, + maxErrors: number, + hunkLinesI: number = 0, + lastContextLineMatched: boolean = true, + patchedLines: string[] = [], + patchedLinesLength: number = 0 + ): ApplyHunkReturnType | null { + let nConsecutiveOldContextLines = 0; + let nextContextLineMustMatch = false; + for (; hunkLinesI < hunkLines.length; hunkLinesI++) { + const hunkLine = hunkLines[hunkLinesI], + operation = (hunkLine.length > 0 ? hunkLine[0] : ' '), + content = (hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine); + + if (operation === '-') { + if (compareLine(toPos + 1, lines[toPos], operation, content)) { + toPos++; + nConsecutiveOldContextLines = 0; + } else { + if (!maxErrors || lines[toPos] == null) { + return null; + } + patchedLines[patchedLinesLength] = lines[toPos]; + return applyHunk( + hunkLines, + toPos + 1, + maxErrors - 1, + hunkLinesI, + false, + patchedLines, + patchedLinesLength + 1 + ); + } + } + + if (operation === '+') { + if (!lastContextLineMatched) { + return null; + } + patchedLines[patchedLinesLength] = content; + patchedLinesLength++; + nConsecutiveOldContextLines = 0; + nextContextLineMustMatch = true; + } + + if (operation === ' ') { + nConsecutiveOldContextLines++; + patchedLines[patchedLinesLength] = lines[toPos]; + if (compareLine(toPos + 1, lines[toPos], operation, content)) { + patchedLinesLength++; + lastContextLineMatched = true; + nextContextLineMustMatch = false; + toPos++; + } else { + if (nextContextLineMustMatch || !maxErrors) { + return null; + } + + // Consider 3 possibilities in sequence: + // 1. lines contains a *substitution* not included in the patch context, or + // 2. lines contains an *insertion* not included in the patch context, or + // 3. lines contains a *deletion* not included in the patch context + // The first two options are of course only possible if the line from lines is non-null - + // i.e. only option 3 is possible if we've overrun the end of the old file. + return ( + lines[toPos] && ( + applyHunk( + hunkLines, + toPos + 1, + maxErrors - 1, + hunkLinesI + 1, + false, + patchedLines, + patchedLinesLength + 1 + ) || applyHunk( + hunkLines, + toPos + 1, + maxErrors - 1, + hunkLinesI, + false, + patchedLines, + patchedLinesLength + 1 + ) + ) || applyHunk( + hunkLines, + toPos, + maxErrors - 1, + hunkLinesI + 1, + false, + patchedLines, + patchedLinesLength + ) + ); + } + } + } + + // Before returning, trim any unmodified context lines off the end of patchedLines and reduce + // toPos (and thus oldLineLastI) accordingly. This allows later hunks to be applied to a region + // that starts in this hunk's trailing context. + patchedLinesLength -= nConsecutiveOldContextLines; + toPos -= nConsecutiveOldContextLines; + patchedLines.length = patchedLinesLength; + return { + patchedLines, + oldLineLastI: toPos - 1 + }; + } + + const resultLines: string[] = []; + + // Search best fit offsets for each hunk based on the previous ones + let prevHunkOffset = 0; + for (let i = 0; i < hunks.length; i++) { + const hunk = hunks[i]; + let hunkResult; + const maxLine = lines.length - hunk.oldLines + fuzzFactor; + let toPos: number | undefined; + for (let maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) { + toPos = hunk.oldStart + prevHunkOffset - 1; + const iterator = distanceIterator(toPos, minLine, maxLine); + for (; toPos !== undefined; toPos = iterator()) { + hunkResult = applyHunk(hunk.lines, toPos, maxErrors); + if (hunkResult) { + break; + } + } + if (hunkResult) { + break; + } + } + + if (!hunkResult) { + return false; + } + + // Copy everything from the end of where we applied the last hunk to the start of this hunk + for (let i = minLine; i < toPos!; i++) { + resultLines.push(lines[i]); + } + + // Add the lines produced by applying the hunk: + for (let i = 0; i < hunkResult.patchedLines.length; i++) { + const line = hunkResult.patchedLines[i]; + resultLines.push(line); + } + + // Set lower text limit to end of the current hunk, so next ones don't try + // to fit over already patched text + minLine = hunkResult.oldLineLastI + 1; + + // Note the offset between where the patch said the hunk should've applied and where we + // applied it, so we can adjust future hunks accordingly: + prevHunkOffset = toPos! + 1 - hunk.oldStart; + } + + // Copy over the rest of the lines from the old text + for (let i = minLine; i < lines.length; i++) { + resultLines.push(lines[i]); + } + + return resultLines.join('\n'); +} + +export interface ApplyPatchesOptions extends ApplyPatchOptions { + loadFile: (index: StructuredPatch, callback: (err: any, data: string) => void) => void, + patched: (index: StructuredPatch, content: string | false, callback: (err: any) => void) => void, + complete: (err?: any) => void, +} + +/** + * applies one or more patches. + * + * `patch` may be either an array of structured patch objects, or a string representing a patch in unified diff format (which may patch one or more files). + * + * This method will iterate over the contents of the patch and apply to data provided through callbacks. The general flow for each patch index is: + * + * - `options.loadFile(index, callback)` is called. The caller should then load the contents of the file and then pass that to the `callback(err, data)` callback. Passing an `err` will terminate further patch execution. + * - `options.patched(index, content, callback)` is called once the patch has been applied. `content` will be the return value from `applyPatch`. When it's ready, the caller should call `callback(err)` callback. Passing an `err` will terminate further patch execution. + * + * Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made. + */ +export function applyPatches(uniDiff: string | StructuredPatch[], options: ApplyPatchesOptions): void { + const spDiff: StructuredPatch[] = typeof uniDiff === 'string' ? parsePatch(uniDiff) : uniDiff; + + let currentIndex = 0; + function processIndex(): void { + const index = spDiff[currentIndex++]; + if (!index) { + return options.complete(); + } + + options.loadFile(index, function(err: any, data: string) { + if (err) { + return options.complete(err); + } + + const updatedContent = applyPatch(data, index, options); + options.patched(index, updatedContent, function(err: any) { + if (err) { + return options.complete(err); + } + + processIndex(); + }); + }); + } + processIndex(); +} diff --git a/src/patch/create.js b/src/patch/create.js deleted file mode 100644 index 0dbd0c9d3..000000000 --- a/src/patch/create.js +++ /dev/null @@ -1,123 +0,0 @@ -import {diffLines} from '../diff/line'; - -export function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - if (!options) { - options = { context: 4 }; - } - - const diff = diffLines(oldStr, newStr); - diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier - - function contextLines(lines) { - return lines.map(function(entry) { return ' ' + entry; }); - } - - let hunks = []; - let oldRangeStart = 0, newRangeStart = 0, curRange = [], - oldLine = 1, newLine = 1; - for (let i = 0; i < diff.length; i++) { - const current = diff[i], - lines = current.lines || current.value.replace(/\n$/, '').split('\n'); - current.lines = lines; - - if (current.added || current.removed) { - // If we have previous context, start with that - if (!oldRangeStart) { - const prev = diff[i - 1]; - oldRangeStart = oldLine; - newRangeStart = newLine; - - if (prev) { - curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; - oldRangeStart -= curRange.length; - newRangeStart -= curRange.length; - } - } - - // Output our changes - curRange.push(... lines.map(function(entry) { - return (current.added ? '+' : '-') + entry; - })); - - // Track the updated file position - if (current.added) { - newLine += lines.length; - } else { - oldLine += lines.length; - } - } else { - // Identical context lines. Track line changes - if (oldRangeStart) { - // Close out any changes that have been output (or join overlapping) - if (lines.length <= options.context * 2 && i < diff.length - 2) { - // Overlapping - curRange.push(... contextLines(lines)); - } else { - // end the range and output - let contextSize = Math.min(lines.length, options.context); - curRange.push(... contextLines(lines.slice(0, contextSize))); - - let hunk = { - oldStart: oldRangeStart, - oldLines: (oldLine - oldRangeStart + contextSize), - newStart: newRangeStart, - newLines: (newLine - newRangeStart + contextSize), - lines: curRange - }; - if (i >= diff.length - 2 && lines.length <= options.context) { - // EOF is inside this hunk - let oldEOFNewline = (/\n$/.test(oldStr)); - let newEOFNewline = (/\n$/.test(newStr)); - if (lines.length == 0 && !oldEOFNewline) { - // special case: old has no eol and no trailing context; no-nl can end up before adds - curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); - } else if (!oldEOFNewline || !newEOFNewline) { - curRange.push('\\ No newline at end of file'); - } - } - hunks.push(hunk); - - oldRangeStart = 0; - newRangeStart = 0; - curRange = []; - } - } - oldLine += lines.length; - newLine += lines.length; - } - } - - return { - oldFileName: oldFileName, newFileName: newFileName, - oldHeader: oldHeader, newHeader: newHeader, - hunks: hunks - }; -} - -export function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - const diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); - - const ret = []; - if (oldFileName == newFileName) { - ret.push('Index: ' + oldFileName); - } - ret.push('==================================================================='); - ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); - ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); - - for (let i = 0; i < diff.hunks.length; i++) { - const hunk = diff.hunks[i]; - ret.push( - '@@ -' + hunk.oldStart + ',' + hunk.oldLines - + ' +' + hunk.newStart + ',' + hunk.newLines - + ' @@' - ); - ret.push.apply(ret, hunk.lines); - } - - return ret.join('\n') + '\n'; -} - -export function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { - return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); -} diff --git a/src/patch/create.ts b/src/patch/create.ts new file mode 100644 index 000000000..52b5ed077 --- /dev/null +++ b/src/patch/create.ts @@ -0,0 +1,644 @@ +import {diffLines} from '../diff/line.js'; +import type { StructuredPatch, DiffLinesOptionsAbortable, DiffLinesOptionsNonabortable, AbortableDiffOptions, ChangeObject } from '../types.js'; + +/** + * Returns true if the filename contains characters that require C-style + * quoting (as used by Git and GNU diffutils in diff output). + */ +function needsQuoting(s: string): boolean { + for (let i = 0; i < s.length; i++) { + if (s[i] < '\x20' || s[i] > '\x7e' || s[i] === '"' || s[i] === '\\') { + return true; + } + } + return false; +} + +/** + * C-style quotes a filename, encoding special characters as escape sequences + * and non-ASCII bytes as octal escapes. This is the inverse of + * `parseQuotedFileName` in parse.ts. + * + * Non-ASCII bytes are encoded as UTF-8 before being emitted as octal escapes. + * This matches the behaviour of both Git and GNU diffutils, which always emit + * UTF-8 octal escapes regardless of the underlying filesystem encoding (e.g. + * Git for Windows converts from NTFS's UTF-16 to UTF-8 internally). + * + * If the filename doesn't need quoting, returns it as-is. + */ +function quoteFileNameIfNeeded(s: string): string { + if (!needsQuoting(s)) { + return s; + } + + let result = '"'; + const bytes = new TextEncoder().encode(s); + let i = 0; + while (i < bytes.length) { + const b = bytes[i]; + + // See https://en.wikipedia.org/wiki/Escape_sequences_in_C#Escape_sequences + if (b === 0x07) { + result += '\\a'; + } else if (b === 0x08) { + result += '\\b'; + } else if (b === 0x09) { + result += '\\t'; + } else if (b === 0x0a) { + result += '\\n'; + } else if (b === 0x0b) { + result += '\\v'; + } else if (b === 0x0c) { + result += '\\f'; + } else if (b === 0x0d) { + result += '\\r'; + } else if (b === 0x22) { + result += '\\"'; + } else if (b === 0x5c) { + result += '\\\\'; + } else if (b >= 0x20 && b <= 0x7e) { + // Just a printable ASCII character that is neither a double quote nor a + // backslash; no need to escape it. + result += String.fromCharCode(b); + } else { + // Either part of a non-ASCII character or a control character without a + // special escape sequence; needs escaping as a 3-digit octal escape + result += '\\' + b.toString(8).padStart(3, '0'); + } + i++; + } + result += '"'; + return result; +} + +type StructuredPatchCallbackAbortable = (patch: StructuredPatch | undefined) => void; +type StructuredPatchCallbackNonabortable = (patch: StructuredPatch) => void; + +export interface HeaderOptions { + includeIndex: boolean; + includeUnderline: boolean; + includeFileHeaders: boolean; +} + +export const INCLUDE_HEADERS = { + includeIndex: true, + includeUnderline: true, + includeFileHeaders: true +}; +export const FILE_HEADERS_ONLY = { + includeIndex: false, + includeUnderline: false, + includeFileHeaders: true +}; +export const OMIT_HEADERS = { + includeIndex: false, + includeUnderline: false, + includeFileHeaders: false +}; + +interface _StructuredPatchOptionsAbortable extends Pick { + /** + * describes how many lines of context should be included. + * You can set this to `Number.MAX_SAFE_INTEGER` or `Infinity` to include the entire file content in one hunk. + * @default 4 + */ + context?: number, + callback?: StructuredPatchCallbackAbortable, +} +export type StructuredPatchOptionsAbortable = _StructuredPatchOptionsAbortable & AbortableDiffOptions; +export interface StructuredPatchOptionsNonabortable extends Pick { + context?: number, + callback?: StructuredPatchCallbackNonabortable, +} +interface StructuredPatchCallbackOptionAbortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: StructuredPatchCallbackAbortable; +} +interface StructuredPatchCallbackOptionNonabortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: StructuredPatchCallbackNonabortable; +} + +// Purely an implementation detail of diffLinesResultToPatch, which mutates the result of diffLines +// for convenience of implementation +interface ChangeObjectPlusLines extends Partial> { + value: string; + lines?: string[]; +} + +/** + * returns an object with an array of hunk objects. + * + * This method is similar to createTwoFilesPatch, but returns a data structure suitable for further processing. + * @param oldFileName String to be output in the filename section of the patch for the removals + * @param newFileName String to be output in the filename section of the patch for the additions + * @param oldStr Original string value + * @param newStr New string value + * @param oldHeader Optional additional information to include in the old file header. + * @param newHeader Optional additional information to include in the new file header. + */ +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchCallbackNonabortable +): undefined; +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchOptionsAbortable & StructuredPatchCallbackOptionAbortable +): undefined +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchOptionsNonabortable & StructuredPatchCallbackOptionNonabortable +): undefined +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchOptionsAbortable +): StructuredPatch | undefined +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: StructuredPatchOptionsNonabortable +): StructuredPatch +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: StructuredPatchOptionsAbortable | StructuredPatchOptionsNonabortable | StructuredPatchCallbackNonabortable +): StructuredPatch | undefined { + let optionsObj: StructuredPatchOptionsAbortable | StructuredPatchOptionsNonabortable; + if (!options) { + optionsObj = {}; + } else if (typeof options === 'function') { + optionsObj = {callback: options}; + } else { + optionsObj = options; + } + + + if (typeof optionsObj.context === 'undefined') { + optionsObj.context = 4; + } + + // We copy this into its own variable to placate TypeScript, which thinks + // optionsObj.context might be undefined in the callbacks below. + const context = optionsObj.context; + + // @ts-expect-error (runtime check for something that is correctly a static type error) + if (optionsObj.newlineIsToken) { + throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); + } + + if (!optionsObj.callback) { + return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj as any)); + } else { + const {callback} = optionsObj; + diffLines( + oldStr, + newStr, + { + ...optionsObj, + callback: (diff) => { + const patch = diffLinesResultToPatch(diff); + // TypeScript is unhappy without the cast because it does not understand that `patch` may + // be undefined here only if `callback` is StructuredPatchCallbackAbortable: + (callback as any)(patch); + } + } + ); + } + + function diffLinesResultToPatch(diff: ChangeObjectPlusLines[] | undefined) { + // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays + // of lines containing trailing newline characters. We'll tidy up later... + + if(!diff) { + return; + } + + diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines: string[]) { + return lines.map(function(entry) { return ' ' + entry; }); + } + + const hunks = []; + let oldRangeStart = 0, newRangeStart = 0, curRange: string[] = [], + oldLine = 1, newLine = 1; + for (let i = 0; i < diff.length; i++) { + const current = diff[i], + lines = current.lines || splitLines(current.value); + current.lines = lines; + + if (current.added || current.removed) { + // If we have previous context, start with that + if (!oldRangeStart) { + const prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = context > 0 ? contextLines(prev.lines!.slice(-context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + for (const line of lines) { + curRange.push((current.added ? '+' : '-') + line); + } + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= context * 2 && i < diff.length - 2) { + // Overlapping + for (const line of contextLines(lines)) { + curRange.push(line); + } + } else { + // end the range and output + const contextSize = Math.min(lines.length, context); + for (const line of contextLines(lines.slice(0, contextSize))) { + curRange.push(line); + } + + const hunk = { + oldStart: oldRangeStart, + oldLines: (oldLine - oldRangeStart + contextSize), + newStart: newRangeStart, + newLines: (newLine - newRangeStart + contextSize), + lines: curRange + }; + hunks.push(hunk); + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add + // "\ No newline at end of file". + for (const hunk of hunks) { + for (let i = 0; i < hunk.lines.length; i++) { + if (hunk.lines[i].endsWith('\n')) { + hunk.lines[i] = hunk.lines[i].slice(0, -1); + } else { + hunk.lines.splice(i + 1, 0, '\\ No newline at end of file'); + i++; // Skip the line we just added, then continue iterating + } + } + } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + } +} + +/** + * creates a unified diff patch. + * + * @param patch either a single structured patch object (as returned by `structuredPatch`) or an + * array of them (as returned by `parsePatch`). + * @param headerOptions behaves the same as the `headerOptions` option of `createTwoFilesPatch`. + * Ignored for patches where `isGit` is `true`. + * + * When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's + * output: it emits a `diff --git` header, emits Git extended headers as appropriate based on + * properties like `isRename`, `isCreate`, `newMode`, etc, and will omit `---`/`+++` file + * headers for patches with no hunks (e.g. renames without content changes). + */ +export function formatPatch(patch: StructuredPatch | StructuredPatch[], headerOptions?: HeaderOptions): string { + if (!headerOptions) { + headerOptions = INCLUDE_HEADERS; + } + if (Array.isArray(patch)) { + if (patch.length > 1 && !headerOptions.includeFileHeaders && !patch.every(p => p.isGit)) { + throw new Error( + 'Cannot omit file headers on a multi-file patch. ' + + '(The result would be unparseable; how would a tool trying to apply ' + + 'the patch know which changes are to which file?)' + ); + } + return patch.map(p => formatPatch(p, headerOptions)).join('\n'); + } + + const ret = []; + + // Git patches have a fixed header format (diff --git, extended headers, + // and ---/+++ when hunks are present), so headerOptions is ignored. + if (patch.isGit) { + headerOptions = INCLUDE_HEADERS; + // Emit Git-style diff --git header and extended headers. + // Git never puts /dev/null in the "diff --git" line; for file + // creations/deletions it uses the real filename on both sides. + if (!patch.oldFileName) { + throw new Error('oldFileName must be specified for Git patches'); + } + if (!patch.newFileName) { + throw new Error('newFileName must be specified for Git patches'); + } + let gitOldName = patch.oldFileName; + let gitNewName = patch.newFileName; + if (patch.isCreate && gitOldName === '/dev/null') { + gitOldName = gitNewName.replace(/^b\//, 'a/'); + } else if (patch.isDelete && gitNewName === '/dev/null') { + gitNewName = gitOldName.replace(/^a\//, 'b/'); + } + ret.push('diff --git ' + quoteFileNameIfNeeded(gitOldName) + ' ' + quoteFileNameIfNeeded(gitNewName)); + if (patch.isDelete) { + ret.push('deleted file mode ' + (patch.oldMode ?? '100644')); + } + if (patch.isCreate) { + ret.push('new file mode ' + (patch.newMode ?? '100644')); + } + if (patch.oldMode && patch.newMode && !patch.isDelete && !patch.isCreate) { + ret.push('old mode ' + patch.oldMode); + ret.push('new mode ' + patch.newMode); + } + if (patch.isRename) { + ret.push('rename from ' + quoteFileNameIfNeeded((patch.oldFileName ?? '').replace(/^a\//, ''))); + ret.push('rename to ' + quoteFileNameIfNeeded((patch.newFileName ?? '').replace(/^b\//, ''))); + } + if (patch.isCopy) { + ret.push('copy from ' + quoteFileNameIfNeeded((patch.oldFileName ?? '').replace(/^a\//, ''))); + ret.push('copy to ' + quoteFileNameIfNeeded((patch.newFileName ?? '').replace(/^b\//, ''))); + } + } else { + if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== undefined) { + ret.push('Index: ' + patch.oldFileName); + } + if (headerOptions.includeUnderline) { + ret.push('==================================================================='); + } + } + + // Emit --- / +++ file headers. For Git patches with no hunks (e.g. + // pure renames, mode-only changes), Git omits these, so we do too. + const hasHunks = patch.hunks.length > 0; + if (headerOptions.includeFileHeaders && patch.oldFileName !== undefined && patch.newFileName !== undefined + && (!patch.isGit || hasHunks)) { + ret.push('--- ' + quoteFileNameIfNeeded(patch.oldFileName) + (patch.oldHeader ? '\t' + patch.oldHeader : '')); + ret.push('+++ ' + quoteFileNameIfNeeded(patch.newFileName) + (patch.newHeader ? '\t' + patch.newHeader : '')); + } + + for (let i = 0; i < patch.hunks.length; i++) { + const hunk = patch.hunks[i]; + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + const oldStart = hunk.oldLines === 0 ? hunk.oldStart - 1 : hunk.oldStart; + const newStart = hunk.newLines === 0 ? hunk.newStart - 1 : hunk.newStart; + ret.push( + '@@ -' + oldStart + ',' + hunk.oldLines + + ' +' + newStart + ',' + hunk.newLines + + ' @@' + ); + for (const line of hunk.lines) { + ret.push(line); + } + } + + return ret.join('\n') + '\n'; +} + +type CreatePatchCallbackAbortable = (patch: string | undefined) => void; +type CreatePatchCallbackNonabortable = (patch: string) => void; + +interface _CreatePatchOptionsAbortable extends Pick { + context?: number, + callback?: CreatePatchCallbackAbortable, + headerOptions?: HeaderOptions, +} +export type CreatePatchOptionsAbortable = _CreatePatchOptionsAbortable & AbortableDiffOptions; +export interface CreatePatchOptionsNonabortable extends Pick { + context?: number, + callback?: CreatePatchCallbackNonabortable, + headerOptions?: HeaderOptions, +} +interface CreatePatchCallbackOptionAbortable { + callback: CreatePatchCallbackAbortable; +} +interface CreatePatchCallbackOptionNonabortable { + callback: CreatePatchCallbackNonabortable; +} + +/** + * creates a unified diff patch by first computing a diff with `diffLines` and then serializing it to unified diff format. + * @param oldFileName String to be output in the filename section of the patch for the removals + * @param newFileName String to be output in the filename section of the patch for the additions + * @param oldStr Original string value + * @param newStr New string value + * @param oldHeader Optional additional information to include in the old file header. + * @param newHeader Optional additional information to include in the new file header. + */ +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchCallbackNonabortable +): undefined; +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable & CreatePatchCallbackOptionAbortable +): undefined +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsNonabortable & CreatePatchCallbackOptionNonabortable +): undefined +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable +): string | undefined +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsNonabortable +): string +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsAbortable | CreatePatchOptionsNonabortable | CreatePatchCallbackNonabortable +): string | undefined { + if (typeof options === 'function') { + options = {callback: options}; + } + + if (!options?.callback) { + const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options as any); + if (!patchObj) { + return; + } + return formatPatch(patchObj, options?.headerOptions); + } else { + const {callback} = options; + structuredPatch( + oldFileName, + newFileName, + oldStr, + newStr, + oldHeader, + newHeader, + { + ...options, + callback: patchObj => { + if (!patchObj) { + (callback as CreatePatchCallbackAbortable)(undefined); + } else { + callback(formatPatch(patchObj, options.headerOptions)); + } + } + } + ); + } +} + +/** + * creates a unified diff patch. + * + * Just like createTwoFilesPatch, but with oldFileName being equal to newFileName. + * @param fileName String to be output in the filename section of the patch + * @param oldStr Original string value + * @param newStr New string value + * @param oldHeader Optional additional information to include in the old file header. + * @param newHeader Optional additional information to include in the new file header. + */ +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchCallbackNonabortable +): undefined; +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable & CreatePatchCallbackOptionAbortable +): undefined +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsNonabortable & CreatePatchCallbackOptionNonabortable +): undefined +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable +): string | undefined +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsNonabortable +): string +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsAbortable | CreatePatchOptionsNonabortable | CreatePatchCallbackNonabortable +): string | undefined { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options as any); +} + +/** + * Split `text` into an array of lines, including the trailing newline character (where present) + */ +function splitLines(text: string): string[] { + const hasTrailingNl = text.endsWith('\n'); + const result = text.split('\n').map(line => line + '\n'); + if (hasTrailingNl) { + result.pop(); + } else { + result.push( + (result.pop() as string).slice(0, -1) + ); + } + return result; +} diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts new file mode 100644 index 000000000..d36c7a595 --- /dev/null +++ b/src/patch/line-endings.ts @@ -0,0 +1,77 @@ +import type { StructuredPatch } from '../types.js'; + +export function unixToWin(patch: StructuredPatch): StructuredPatch; +export function unixToWin(patches: StructuredPatch[]): StructuredPatch[]; +export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { + if (Array.isArray(patch)) { + // It would be cleaner if instead of the line below we could just write + // return patch.map(unixToWin) + // but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will + // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the + // result would be incompatible with the overload signatures. + // See bug report at https://github.com/microsoft/TypeScript/issues/61398. + return patch.map(p => unixToWin(p)); + } + + return { + ...patch, + hunks: patch.hunks.map(hunk => ({ + ...hunk, + lines: hunk.lines.map( + (line, i) => + (line.startsWith('\\') || line.endsWith('\r') || hunk.lines[i + 1]?.startsWith('\\')) + ? line + : line + '\r' + ) + })) + }; +} + +export function winToUnix(patch: StructuredPatch): StructuredPatch; +export function winToUnix(patches: StructuredPatch[]): StructuredPatch[]; +export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { + if (Array.isArray(patch)) { + // (See comment above equivalent line in unixToWin) + return patch.map(p => winToUnix(p)); + } + + return { + ...patch, + hunks: patch.hunks.map(hunk => ({ + ...hunk, + lines: hunk.lines.map(line => line.endsWith('\r') ? line.substring(0, line.length - 1) : line) + })) + }; +} + +/** + * Returns true if the patch consistently uses Unix line endings (or only involves one line and has + * no line endings). + */ +export function isUnix(patch: StructuredPatch | StructuredPatch[]): boolean { + if (!Array.isArray(patch)) { patch = [patch]; } + return !patch.some( + index => index.hunks.some( + hunk => hunk.lines.some( + line => !line.startsWith('\\') && line.endsWith('\r') + ) + ) + ); +} + +/** + * Returns true if the patch uses Windows line endings and only Windows line endings. + */ +export function isWin(patch: StructuredPatch | StructuredPatch[]): boolean { + if (!Array.isArray(patch)) { patch = [patch]; } + return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r')))) + && patch.every( + index => index.hunks.every( + hunk => hunk.lines.every( + (line, i) => line.startsWith('\\') || line.endsWith('\r') || hunk.lines[i + 1]?.startsWith('\\') + ) + ) + ); +} diff --git a/src/patch/merge.js b/src/patch/merge.js deleted file mode 100644 index d3b55bd2f..000000000 --- a/src/patch/merge.js +++ /dev/null @@ -1,349 +0,0 @@ -import {structuredPatch} from './create'; -import {parsePatch} from './parse'; - -import {arrayEqual, arrayStartsWith} from '../util/array'; - -export function calcLineCount(hunk) { - let conflicted = false; - - hunk.oldLines = 0; - hunk.newLines = 0; - - hunk.lines.forEach(function(line) { - if (typeof line !== 'string') { - conflicted = true; - return; - } - - if (line[0] === '+' || line[0] === ' ') { - hunk.newLines++; - } - if (line[0] === '-' || line[0] === ' ') { - hunk.oldLines++; - } - }); - - if (conflicted) { - delete hunk.oldLines; - delete hunk.newLines; - } -} - -export function merge(mine, theirs, base) { - mine = loadPatch(mine, base); - theirs = loadPatch(theirs, base); - - let ret = {}; - - // For index we just let it pass through as it doesn't have any necessary meaning. - // Leaving sanity checks on this to the API consumer that may know more about the - // meaning in their own context. - if (mine.index || theirs.index) { - ret.index = mine.index || theirs.index; - } - - if (mine.newFileName || theirs.newFileName) { - if (!fileNameChanged(mine)) { - // No header or no change in ours, use theirs (and ours if theirs does not exist) - ret.oldFileName = theirs.oldFileName || mine.oldFileName; - ret.newFileName = theirs.newFileName || mine.newFileName; - ret.oldHeader = theirs.oldHeader || mine.oldHeader; - ret.newHeader = theirs.newHeader || mine.newHeader; - } else if (!fileNameChanged(theirs)) { - // No header or no change in theirs, use ours - ret.oldFileName = mine.oldFileName; - ret.newFileName = mine.newFileName; - ret.oldHeader = mine.oldHeader; - ret.newHeader = mine.newHeader; - } else { - // Both changed... figure it out - ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName); - ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName); - ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader); - ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader); - } - } - - ret.hunks = []; - - let mineIndex = 0, - theirsIndex = 0, - mineOffset = 0, - theirsOffset = 0; - - while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) { - let mineCurrent = mine.hunks[mineIndex] || {oldStart: Infinity}, - theirsCurrent = theirs.hunks[theirsIndex] || {oldStart: Infinity}; - - if (hunkBefore(mineCurrent, theirsCurrent)) { - // This patch does not overlap with any of the others, yay. - ret.hunks.push(cloneHunk(mineCurrent, mineOffset)); - mineIndex++; - theirsOffset += mineCurrent.newLines - mineCurrent.oldLines; - } else if (hunkBefore(theirsCurrent, mineCurrent)) { - // This patch does not overlap with any of the others, yay. - ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset)); - theirsIndex++; - mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines; - } else { - // Overlap, merge as best we can - let mergedHunk = { - oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart), - oldLines: 0, - newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset), - newLines: 0, - lines: [] - }; - mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines); - theirsIndex++; - mineIndex++; - - ret.hunks.push(mergedHunk); - } - } - - return ret; -} - -function loadPatch(param, base) { - if (typeof param === 'string') { - if (/^@@/m.test(param) || (/^Index:/m.test(param))) { - return parsePatch(param)[0]; - } - - if (!base) { - throw new Error('Must provide a base reference or pass in a patch'); - } - return structuredPatch(undefined, undefined, base, param); - } - - return param; -} - -function fileNameChanged(patch) { - return patch.newFileName && patch.newFileName !== patch.oldFileName; -} - -function selectField(index, mine, theirs) { - if (mine === theirs) { - return mine; - } else { - index.conflict = true; - return {mine, theirs}; - } -} - -function hunkBefore(test, check) { - return test.oldStart < check.oldStart - && (test.oldStart + test.oldLines) < check.oldStart; -} - -function cloneHunk(hunk, offset) { - return { - oldStart: hunk.oldStart, oldLines: hunk.oldLines, - newStart: hunk.newStart + offset, newLines: hunk.newLines, - lines: hunk.lines - }; -} - -function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) { - // This will generally result in a conflicted hunk, but there are cases where the context - // is the only overlap where we can successfully merge the content here. - let mine = {offset: mineOffset, lines: mineLines, index: 0}, - their = {offset: theirOffset, lines: theirLines, index: 0}; - - // Handle any leading content - insertLeading(hunk, mine, their); - insertLeading(hunk, their, mine); - - // Now in the overlap content. Scan through and select the best changes from each. - while (mine.index < mine.lines.length && their.index < their.lines.length) { - let mineCurrent = mine.lines[mine.index], - theirCurrent = their.lines[their.index]; - - if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') - && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) { - // Both modified ... - mutualChange(hunk, mine, their); - } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') { - // Mine inserted - hunk.lines.push(... collectChange(mine)); - } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') { - // Theirs inserted - hunk.lines.push(... collectChange(their)); - } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') { - // Mine removed or edited - removal(hunk, mine, their); - } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') { - // Their removed or edited - removal(hunk, their, mine, true); - } else if (mineCurrent === theirCurrent) { - // Context identity - hunk.lines.push(mineCurrent); - mine.index++; - their.index++; - } else { - // Context mismatch - conflict(hunk, collectChange(mine), collectChange(their)); - } - } - - // Now push anything that may be remaining - insertTrailing(hunk, mine); - insertTrailing(hunk, their); - - calcLineCount(hunk); -} - -function mutualChange(hunk, mine, their) { - let myChanges = collectChange(mine), - theirChanges = collectChange(their); - - if (allRemoves(myChanges) && allRemoves(theirChanges)) { - // Special case for remove changes that are supersets of one another - if (arrayStartsWith(myChanges, theirChanges) - && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) { - hunk.lines.push(... myChanges); - return; - } else if (arrayStartsWith(theirChanges, myChanges) - && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) { - hunk.lines.push(... theirChanges); - return; - } - } else if (arrayEqual(myChanges, theirChanges)) { - hunk.lines.push(... myChanges); - return; - } - - conflict(hunk, myChanges, theirChanges); -} - -function removal(hunk, mine, their, swap) { - let myChanges = collectChange(mine), - theirChanges = collectContext(their, myChanges); - if (theirChanges.merged) { - hunk.lines.push(... theirChanges.merged); - } else { - conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges); - } -} - -function conflict(hunk, mine, their) { - hunk.conflict = true; - hunk.lines.push({ - conflict: true, - mine: mine, - theirs: their - }); -} - -function insertLeading(hunk, insert, their) { - while (insert.offset < their.offset && insert.index < insert.lines.length) { - let line = insert.lines[insert.index++]; - hunk.lines.push(line); - insert.offset++; - } -} -function insertTrailing(hunk, insert) { - while (insert.index < insert.lines.length) { - let line = insert.lines[insert.index++]; - hunk.lines.push(line); - } -} - -function collectChange(state) { - let ret = [], - operation = state.lines[state.index][0]; - while (state.index < state.lines.length) { - let line = state.lines[state.index]; - - // Group additions that are immediately after subtractions and treat them as one "atomic" modify change. - if (operation === '-' && line[0] === '+') { - operation = '+'; - } - - if (operation === line[0]) { - ret.push(line); - state.index++; - } else { - break; - } - } - - return ret; -} -function collectContext(state, matchChanges) { - let changes = [], - merged = [], - matchIndex = 0, - contextChanges = false, - conflicted = false; - while (matchIndex < matchChanges.length - && state.index < state.lines.length) { - let change = state.lines[state.index], - match = matchChanges[matchIndex]; - - // Once we've hit our add, then we are done - if (match[0] === '+') { - break; - } - - contextChanges = contextChanges || change[0] !== ' '; - - merged.push(match); - matchIndex++; - - // Consume any additions in the other block as a conflict to attempt - // to pull in the remaining context after this - if (change[0] === '+') { - conflicted = true; - - while (change[0] === '+') { - changes.push(change); - change = state.lines[++state.index]; - } - } - - if (match.substr(1) === change.substr(1)) { - changes.push(change); - state.index++; - } else { - conflicted = true; - } - } - - if ((matchChanges[matchIndex] || '')[0] === '+' - && contextChanges) { - conflicted = true; - } - - if (conflicted) { - return changes; - } - - while (matchIndex < matchChanges.length) { - merged.push(matchChanges[matchIndex++]); - } - - return { - merged, - changes - }; -} - -function allRemoves(changes) { - return changes.reduce(function(prev, change) { - return prev && change[0] === '-'; - }, true); -} -function skipRemoveSuperset(state, removeChanges, delta) { - for (let i = 0; i < delta; i++) { - let changeContent = removeChanges[removeChanges.length - delta + i].substr(1); - if (state.lines[state.index + i] !== ' ' + changeContent) { - return false; - } - } - - state.index += delta; - return true; -} diff --git a/src/patch/parse.js b/src/patch/parse.js deleted file mode 100644 index e00619a6d..000000000 --- a/src/patch/parse.js +++ /dev/null @@ -1,126 +0,0 @@ -export function parsePatch(uniDiff, options = {}) { - let diffstr = uniDiff.split('\n'), - list = [], - i = 0; - - function parseIndex() { - let index = {}; - list.push(index); - - // Ignore any leading junk - while (i < diffstr.length) { - if ((/^Index:/.test(diffstr[i])) || (/^@@/.test(diffstr[i]))) { - break; - } - i++; - } - - let header = (/^Index: (.*)/.exec(diffstr[i])); - if (header) { - index.index = header[1]; - i++; - - if (/^===/.test(diffstr[i])) { - i++; - } - - parseFileHeader(index); - parseFileHeader(index); - } else { - // Ignore erant header components that might occur at the start of the file - parseFileHeader({}); - parseFileHeader({}); - } - - index.hunks = []; - - while (i < diffstr.length) { - if (/^Index:/.test(diffstr[i])) { - break; - } else if (/^@@/.test(diffstr[i])) { - index.hunks.push(parseHunk()); - } else if (diffstr[i] && options.strict) { - // Ignore unexpected content unless in strict mode - throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(diffstr[i])); - } else { - i++; - } - } - } - - // Parses the --- and +++ headers, if none are found, no lines - // are consumed. - function parseFileHeader(index) { - let fileHeader = (/^(\-\-\-|\+\+\+)\s(\S+)\s?(.*)/.exec(diffstr[i])); - if (fileHeader) { - let keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; - index[keyPrefix + 'FileName'] = fileHeader[2]; - index[keyPrefix + 'Header'] = fileHeader[3]; - - i++; - } - } - - // Parses a hunk - // This assumes that we are at the start of a hunk. - function parseHunk() { - let chunkHeaderIndex = i, - chunkHeaderLine = diffstr[i++], - chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); - - let hunk = { - oldStart: +chunkHeader[1], - oldLines: +chunkHeader[2] || 1, - newStart: +chunkHeader[3], - newLines: +chunkHeader[4] || 1, - lines: [] - }; - - let addCount = 0, - removeCount = 0; - for (; i < diffstr.length; i++) { - let operation = diffstr[i][0]; - - if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { - hunk.lines.push(diffstr[i]); - - if (operation === '+') { - addCount++; - } else if (operation === '-') { - removeCount++; - } else if (operation === ' ') { - addCount++; - removeCount++; - } - } else { - break; - } - } - - // Handle the empty block count case - if (!addCount && hunk.newLines === 1) { - hunk.newLines = 0; - } - if (!removeCount && hunk.oldLines === 1) { - hunk.oldLines = 0; - } - - // Perform optional sanity checking - if (options.strict) { - if (addCount !== hunk.newLines) { - throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); - } - if (removeCount !== hunk.oldLines) { - throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); - } - } - - return hunk; - } - - while (i < diffstr.length) { - parseIndex(); - } - - return list; -} diff --git a/src/patch/parse.ts b/src/patch/parse.ts new file mode 100755 index 000000000..d894f3025 --- /dev/null +++ b/src/patch/parse.ts @@ -0,0 +1,525 @@ +import type { StructuredPatch } from '../types.js'; + +/** + * Parses a unified diff format patch into a structured patch object. + * + * `parsePatch` has some understanding of Git's particular dialect of unified diff format. + * When parsing a Git patch, each index in the result may contain additional + * fields (`isRename`, `isBinary`, etc) not included in the data structure returned by + * `structuredPatch`; see the `StructuredPatch` interface for a full list. + * + * @return a JSON object representation of the patch, suitable for use with the `applyPatch` + * method. This parses to the same structure returned by `structuredPatch`, except that + * `oldFileName` and `newFileName` may be `undefined` if the patch doesn't contain enough + * information to determine them (e.g. a hunk-only patch with no file headers). + */ +export function parsePatch(uniDiff: string): StructuredPatch[] { + const diffstr = uniDiff.split(/\n/), + list: Partial[] = []; + let i = 0; + + // These helper functions identify line types that can appear between files + // in a multi-file patch. Keeping them in one place avoids subtle + // inconsistencies from having the same regexes duplicated in multiple places. + + // Matches `diff --git ...` lines specifically. + function isGitDiffHeader(line: string): boolean { + return (/^diff --git /).test(line); + } + + // Matches lines that denote the start of a new diff's section in a + // multi-file patch: `diff --git ...`, `Index: ...`, or `diff -r ...`. + function isDiffHeader(line: string): boolean { + return isGitDiffHeader(line) + || (/^Index:\s/).test(line) + || (/^diff(?: -r \w+)+\s/).test(line); + } + + // Matches `--- ...` and `+++ ...` file header lines. + function isFileHeader(line: string): boolean { + return (/^(---|\+\+\+)\s/).test(line); + } + + // Matches `@@ ...` hunk header lines. + function isHunkHeader(line: string): boolean { + return (/^@@\s/).test(line); + } + + function parseIndex() { + const index: Partial = {}; + index.hunks = []; + list.push(index); + + // Parse diff metadata + let seenDiffHeader = false; + while (i < diffstr.length) { + const line = diffstr[i]; + + // File header (---, +++) or hunk header (@@) found; end parsing diff metadata + if (isFileHeader(line) || isHunkHeader(line)) { + break; + } + + // The next two branches handle recognized diff headers. Note that + // isDiffHeader deliberately does NOT match arbitrary `diff` + // commands like `diff -u -p -r1.1 -r1.2`, because in some + // formats (e.g. CVS diffs) such lines appear as metadata within + // a single file's header section, after an `Index:` line. See the + // diffx documentation (https://diffx.org) for examples. + // + // In both branches: if we've already seen a diff header for *this* + // file and now we encounter another one, it must belong to the + // next file, so break. + + if (isGitDiffHeader(line)) { + if (seenDiffHeader) { + return; + } + seenDiffHeader = true; + index.isGit = true; + + // Parse the old and new filenames from the `diff --git` header and + // tentatively set oldFileName and newFileName from them. These may + // be overridden below by `rename from` / `rename to` or `copy from` / + // `copy to` extended headers, or by --- and +++ lines. But for Git + // diffs that lack all of those (e.g. mode-only changes, binary + // file changes without rename), these are the only filenames we + // get. + // parseGitDiffHeader returns null if the header can't be parsed + // (e.g. unterminated quoted filename, or unexpected format). In + // that case we skip setting filenames here; they may still be + // set from --- / +++ or rename from / rename to lines below. + const paths = parseGitDiffHeader(line); + if (paths) { + index.oldFileName = paths.oldFileName; + index.newFileName = paths.newFileName; + } + + // Consume Git extended headers (`old mode`, `new mode`, `rename from`, + // `rename to`, `similarity index`, `index`, `Binary files ... differ`, + // etc.) + i++; + while (i < diffstr.length) { + const extLine = diffstr[i]; + + // Stop consuming extended headers if we hit a file header, + // hunk header, or another diff header. + if (isFileHeader(extLine) || isHunkHeader(extLine) || isDiffHeader(extLine)) { + break; + } + + // Parse `rename from` / `rename to` lines - these give us + // unambiguous filenames. These lines don't include the + // a/ and b/ prefixes that appear in the `diff --git` header + // and --- / +++ lines, so we add them for consistency. + // Git C-style quotes filenames containing special characters + // (tabs, newlines, backslashes, double quotes), so we must + // unquote them when present. + const renameFromMatch = (/^rename from (.*)/).exec(extLine); + if (renameFromMatch) { + index.oldFileName = 'a/' + unquoteIfQuoted(renameFromMatch[1]); + index.isRename = true; + } + const renameToMatch = (/^rename to (.*)/).exec(extLine); + if (renameToMatch) { + index.newFileName = 'b/' + unquoteIfQuoted(renameToMatch[1]); + index.isRename = true; + } + + // Parse copy from / copy to lines similarly + const copyFromMatch = (/^copy from (.*)/).exec(extLine); + if (copyFromMatch) { + index.oldFileName = 'a/' + unquoteIfQuoted(copyFromMatch[1]); + index.isCopy = true; + } + const copyToMatch = (/^copy to (.*)/).exec(extLine); + if (copyToMatch) { + index.newFileName = 'b/' + unquoteIfQuoted(copyToMatch[1]); + index.isCopy = true; + } + + const newFileModeMatch = (/^new file mode (\d+)/).exec(extLine); + if (newFileModeMatch) { + index.isCreate = true; + index.newMode = newFileModeMatch[1]; + } + const deletedFileModeMatch = (/^deleted file mode (\d+)/).exec(extLine); + if (deletedFileModeMatch) { + index.isDelete = true; + index.oldMode = deletedFileModeMatch[1]; + } + const oldModeMatch = (/^old mode (\d+)/).exec(extLine); + if (oldModeMatch) { + index.oldMode = oldModeMatch[1]; + } + const newModeMatch = (/^new mode (\d+)/).exec(extLine); + if (newModeMatch) { + index.newMode = newModeMatch[1]; + } + + if ((/^Binary files /).test(extLine)) { + index.isBinary = true; + } + + i++; + } + continue; + } else if (isDiffHeader(line)) { + if (seenDiffHeader) { + return; + } + seenDiffHeader = true; + + // For Mercurial-style headers like + // diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore + // or Index: headers like + // Index: something with multiple words + // we extract the trailing filename as the index. + // + // TODO: It seems awkward that we indiscriminately trim off + // trailing whitespace here. Theoretically, couldn't that + // be meaningful - e.g. if the patch represents a diff of a + // file whose name ends with a space? Seems wrong to nuke + // it. But this behaviour has been around since v2.2.1 in + // 2015, so if it's going to change, it should be done + // cautiously and in a new major release, for + // backwards-compat reasons. + // -- ExplodingCabbage + const headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line); + if (headerMatch) { + index.index = line.substring(headerMatch[0].length).trim(); + } + } + + i++; + } + + // Parse file headers if they are defined. Unified diff requires them, but + // there's no technical issues to have an isolated hunk without file header + parseFileHeader(index); + parseFileHeader(index); + + // If we got one file header but not the other, that's a malformed patch. + if ((index.oldFileName === undefined) !== (index.newFileName === undefined)) { + throw new Error( + 'Missing ' + (index.oldFileName !== undefined ? '"+++ ..."' : '"--- ..."') + + ' file header for ' + (index.oldFileName ?? index.newFileName) + ); + } + + while (i < diffstr.length) { + const line = diffstr[i]; + if (isDiffHeader(line) || isFileHeader(line) || (/^===================================================================/).test(line)) { + break; + } else if (isHunkHeader(line)) { + index.hunks.push(parseHunk()); + } else { + // Skip blank lines and any other unrecognized content between + // or after hunks. Real-world examples of such content include: + // - `Only in : ` from GNU `diff -r` + // - `Property changes on:` sections from `svn diff` + // - Trailing prose or commentary in email patches + // GNU `patch` tolerates all of these, and so do we. + i++; + } + } + } + + /** + * Parses the old and new filenames from a `diff --git` header line. + * + * The format is: + * diff --git a/ b/ + * + * When filenames contain special characters (including newlines, tabs, + * backslashes, or double quotes), Git quotes them with C-style escaping: + * diff --git "a/file\twith\ttabs.txt" "b/file\twith\ttabs.txt" + * + * When filenames don't contain special characters and the old and new names + * are the same, we can unambiguously split on ` b/` by finding where the + * two halves (including their a/ and b/ prefixes) yield matching bare names. + * + * A pathological case exists in which we cannot reliably determine the paths + * from the `diff --git` header. This case is when the following are true: + * - the old and new file paths differ + * - they are both unquoted (i.e. contain no special characters) + * - at least one of the underlying file paths includes the substring ` b/` + * In this scenario, we do not know which occurrence of ` b/` indicates the + * start of the new file path, so the header is inherently ambiguous. We thus + * select a possible interpretation arbitrarily and return that. + * + * Fortunately, this ambiguity should never matter, because in any patch + * genuinely output by Git in which this pathological scenario occurs, there + * must also be `rename from`/`rename to` or `copy from`/`copy to` extended + * headers present below the `diff --git` header. `parseIndex` will parse + * THOSE headers, from which we CAN unambiguously determine the filenames, + * and will discard the result returned by this function. + * + * Returns null if the header can't be parsed at all — e.g. a quoted filename + * has an unterminated quote, or if the unquoted header doesn't match the + * expected `a/... b/...` format. In that case, the caller (parseIndex) + * skips setting oldFileName/newFileName from this header, but they may + * still be set later from `---`/`+++` lines or `rename from`/`rename to` + * extended headers; if none of those are present either, they'll remain + * undefined in the output. + */ + function parseGitDiffHeader(line: string): { oldFileName: string, newFileName: string } | null { + // Strip the "diff --git " prefix + const rest = line.substring('diff --git '.length); + + // Handle quoted paths: "a/path" "b/path" + // Git quotes paths when they contain characters like newlines, tabs, + // backslashes, or double quotes (but notably not spaces). + if (rest.startsWith('"')) { + const oldPath = parseQuotedFileName(rest); + if (oldPath === null) { return null; } + const afterOld = rest.substring(oldPath.rawLength + 1); // +1 for space + let newFileName: string; + if (afterOld.startsWith('"')) { + const newPath = parseQuotedFileName(afterOld); + if (newPath === null) { return null; } + newFileName = newPath.fileName; + } else { + newFileName = afterOld; + } + return { + oldFileName: oldPath.fileName, + newFileName + }; + } + + // Check if the second path is quoted + // e.g. diff --git a/simple "b/renamed\nnewline.txt" + const quoteIdx = rest.indexOf('"'); + if (quoteIdx > 0) { + const oldFileName = rest.substring(0, quoteIdx - 1); + const newPath = parseQuotedFileName(rest.substring(quoteIdx)); + if (newPath === null) { return null; } + return { + oldFileName, + newFileName: newPath.fileName + }; + } + + // Unquoted paths. Try to find the split point. + // The format is: a/ b/ + // + // Note the potential ambiguity caused by the possibility of the file paths + // themselves containing the substring ` b/`, plus the pathological case + // described in the comment above. + // + // Strategy: find all occurrences of " b/" and split on the middle + // one. When old and new names are the same (which is the only case where + // we can't rely on extended headers later in the patch so HAVE to get + // this right), this will always be the correct split. + if (rest.startsWith('a/')) { + const splits = []; + let idx = 0; + while (true) { + idx = rest.indexOf(' b/', idx + 1); + if (idx === -1) { break; } + splits.push(idx); + } + if (splits.length > 0) { + const mid = splits[Math.floor(splits.length / 2)]; + return { + oldFileName: rest.substring(0, mid), + newFileName: rest.substring(mid + 1) + }; + } + } + + // Fallback: can't parse, return null + return null; + } + + /** + * If `s` starts with a double quote, unquotes it using C-style escape + * rules (as used by Git). Otherwise returns `s` as-is. + */ + function unquoteIfQuoted(s: string): string { + if (s.startsWith('"')) { + const parsed = parseQuotedFileName(s); + if (parsed) { + return parsed.fileName; + } + } + return s; + } + + /** + * Parses a C-style quoted filename as used by Git or GNU `diff -u`. + * Returns the unescaped filename and the raw length consumed (including quotes). + */ + function parseQuotedFileName(s: string): { fileName: string, rawLength: number } | null { + if (!s.startsWith('"')) { return null; } + let result = ''; + let j = 1; // skip opening quote + while (j < s.length) { + if (s[j] === '"') { + return { fileName: result, rawLength: j + 1 }; + } + if (s[j] === '\\' && j + 1 < s.length) { + j++; + switch (s[j]) { + case 'a': result += '\x07'; break; + case 'b': result += '\b'; break; + case 'f': result += '\f'; break; + case 'n': result += '\n'; break; + case 'r': result += '\r'; break; + case 't': result += '\t'; break; + case 'v': result += '\v'; break; + case '\\': result += '\\'; break; + case '"': result += '"'; break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': { + // C-style octal escapes represent raw bytes. Collect + // consecutive octal-escaped bytes and decode as UTF-8. + // Validate that we have a full 3-digit octal escape + if (j + 2 >= s.length || s[j + 1] < '0' || s[j + 1] > '7' || s[j + 2] < '0' || s[j + 2] > '7') { + return null; + } + const bytes = [parseInt(s.substring(j, j + 3), 8)]; + j += 3; + while (s[j] === '\\' && s[j + 1] >= '0' && s[j + 1] <= '7') { + if (j + 3 >= s.length || s[j + 2] < '0' || s[j + 2] > '7' || s[j + 3] < '0' || s[j + 3] > '7') { + return null; + } + bytes.push(parseInt(s.substring(j + 1, j + 4), 8)); + j += 4; + } + result += new TextDecoder('utf-8').decode(new Uint8Array(bytes)); + continue; // j already points at the next character + } + // Note that in C, there are also three kinds of hex escape sequences: + // - \xhh + // - \uhhhh + // - \Uhhhhhhhh + // We do not bother to parse them here because, so far as we know, + // they are never emitted by any tools that generate unified diff + // format diffs, and so for now jsdiff does not consider them legal. + default: return null; + } + } else { + result += s[j]; + } + j++; + } + // Unterminated quote + return null; + } + + // Parses the --- and +++ headers, if none are found, no lines + // are consumed. + function parseFileHeader(index: Partial) { + const fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]); + if (fileHeaderMatch) { + const prefix = fileHeaderMatch[1], + data = diffstr[i].substring(3).trim().split('\t', 2), + header = (data[1] || '').trim(); + let fileName = data[0]; + if (fileName.startsWith('"')) { + fileName = unquoteIfQuoted(fileName); + } else { + fileName = fileName.replace(/\\\\/g, '\\'); + } + if (prefix === '---') { + index.oldFileName = fileName; + index.oldHeader = header; + } else { + index.newFileName = fileName; + index.newHeader = header; + } + + i++; + } + } + + // Parses a hunk + // This assumes that we are at the start of a hunk. + function parseHunk() { + const chunkHeaderIndex = i, + chunkHeaderLine = diffstr[i++], + chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); + + const hunk = { + oldStart: +chunkHeader[1], + oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], + newStart: +chunkHeader[3], + newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4], + lines: [] as string[] + }; + + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if (hunk.oldLines === 0) { + hunk.oldStart += 1; + } + if (hunk.newLines === 0) { + hunk.newStart += 1; + } + + let addCount = 0, + removeCount = 0; + for ( + ; + i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || diffstr[i]?.startsWith('\\')); + i++ + ) { + const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0]; + if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { + hunk.lines.push(diffstr[i]); + + if (operation === '+') { + addCount++; + } else if (operation === '-') { + removeCount++; + } else if (operation === ' ') { + addCount++; + removeCount++; + } + } else { + throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`); + } + } + + // Handle the empty block count case + if (!addCount && hunk.newLines === 1) { + hunk.newLines = 0; + } + if (!removeCount && hunk.oldLines === 1) { + hunk.oldLines = 0; + } + + // Perform sanity checking + if (addCount !== hunk.newLines) { + throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + if (removeCount !== hunk.oldLines) { + throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + + // Check for extra hunk-body-like lines after the declared line counts + // were exhausted. If the very next line starts with ' ', '+', or '-', + // the hunk's line counts were probably wrong — unless it's a file + // header (--- or +++), which legitimately appears immediately after a + // hunk in multi-file diffs without Index lines. + if (i < diffstr.length && diffstr[i] && (/^[+ -]/).test(diffstr[i]) + && !isFileHeader(diffstr[i])) { + throw new Error( + 'Hunk at line ' + (chunkHeaderIndex + 1) + + ' has more lines than expected (expected ' + + hunk.oldLines + ' old lines and ' + hunk.newLines + ' new lines)' + ); + } + + return hunk; + } + + while (i < diffstr.length) { + parseIndex(); + } + + return list as StructuredPatch[]; +} diff --git a/src/patch/reverse.ts b/src/patch/reverse.ts new file mode 100644 index 000000000..f21a841c6 --- /dev/null +++ b/src/patch/reverse.ts @@ -0,0 +1,93 @@ +import type { StructuredPatch } from '../types.js'; + +function swapPrefix(fileName: string | undefined): string | undefined { + if (fileName === undefined || fileName === '/dev/null') { + return fileName; + } + if (fileName.startsWith('a/')) { + return 'b/' + fileName.slice(2); + } + if (fileName.startsWith('b/')) { + return 'a/' + fileName.slice(2); + } + return fileName; +} + +/** + * Returns a new structured patch which when applied will undo the original `patch`. + * + * When `patch` is a Git-style patch, `reversePatch` handles extended header information (relating + * to renames, file modes, etc.) to the extent that doing so is possible, but note one fundamental + * limitation: the correct inverse of a patch featuring `copy from`/`copy to` headers cannot, in + * general, be determined based on the information contained in the patch alone, and so + * `reversePatch`'s output when passed such a patch will usually be rejected by `git apply`. (The + * correct inverse would be a patch that deletes the newly-created file, but for Git to apply such + * a patch, the patch must explicitly delete every line of content in the file too, and that + * content cannot be determined from the original patch on its own. `reversePatch` therefore does + * the only vaguely reasonable thing it can do in this scenario: it outputs a patch with a + * `deleted file mode` header - indicating that the file should be deleted - but no hunks.) + * + * @param patch either a single structured patch object (as returned by `structuredPatch`) or an + * array of them (as returned by `parsePatch`). + */ +export function reversePatch(structuredPatch: StructuredPatch): StructuredPatch; +export function reversePatch(structuredPatch: StructuredPatch[]): StructuredPatch[]; +export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { + if (Array.isArray(structuredPatch)) { + // (See comment in unixToWin for why we need the pointless-looking anonymous function here) + return structuredPatch.map(patch => reversePatch(patch)).reverse(); + } + + const reversed: StructuredPatch = { + ...structuredPatch, + oldFileName: structuredPatch.isGit ? swapPrefix(structuredPatch.newFileName) : structuredPatch.newFileName, + oldHeader: structuredPatch.newHeader, + newFileName: structuredPatch.isGit ? swapPrefix(structuredPatch.oldFileName) : structuredPatch.oldFileName, + newHeader: structuredPatch.oldHeader, + oldMode: structuredPatch.newMode, + newMode: structuredPatch.oldMode, + isCreate: structuredPatch.isDelete, + isDelete: structuredPatch.isCreate, + hunks: structuredPatch.hunks.map(hunk => { + return { + oldLines: hunk.newLines, + oldStart: hunk.newStart, + newLines: hunk.oldLines, + newStart: hunk.oldStart, + lines: hunk.lines.map(l => { + if (l.startsWith('-')) { return `+${l.slice(1)}`; } + if (l.startsWith('+')) { return `-${l.slice(1)}`; } + return l; + }) + }; + }) + }; + + if (structuredPatch.isCopy) { + // Reversing a copy means deleting the file that was created by the copy. + // The "old" file in the reversed patch is the copy destination (which + // exists and should be removed), and the "new" file is /dev/null. + // + // Note: we clear the hunks because the original copy's hunks describe + // the diff between the source and destination, not the full content of + // the destination file, so they can't be meaningfully reversed into a + // deletion hunk. This means the resulting patch is not something + // `git apply` will accept (it requires deletion patches to include a + // hunk removing every line). Producing a correct deletion hunk would + // require knowing the full content of the copy destination, which we + // don't have. Consumers that need a `git apply`-compatible patch will + // need to resolve the full file content themselves. + reversed.newFileName = '/dev/null'; + reversed.newHeader = undefined; + reversed.isDelete = true; + delete reversed.isCreate; + delete reversed.isCopy; + delete reversed.isRename; + reversed.hunks = []; + } + // Reversing a rename is just a rename in the opposite direction; + // isRename stays set and the filenames are already swapped above. + + return reversed; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..b5a843d1b --- /dev/null +++ b/src/types.ts @@ -0,0 +1,289 @@ +export interface ChangeObject { + /** + * The concatenated content of all the tokens represented by this change object - i.e. generally the text that is either added, deleted, or common, as a single string. + * In cases where tokens are considered common but are non-identical (e.g. because an option like `ignoreCase` or a custom `comparator` was used), the value from the *new* string will be provided here. + */ + value: ValueT; + /** + * true if the value was inserted into the new string, otherwise false + */ + added: boolean; + /** + * true if the value was removed from the old string, otherwise false + */ + removed: boolean; + /** + * How many tokens (e.g. chars for `diffChars`, lines for `diffLines`) the value in the change object consists of + */ + count: number; +} + +// Name "Change" is used here for consistency with the previous type definitions from +// DefinitelyTyped. I would *guess* this is probably the single most common type for people to +// explicitly reference by name in their own code, so keeping its name consistent is valuable even +// though the names of many other types are inconsistent with the old DefinitelyTyped names. +export type Change = ChangeObject; +export type ArrayChange = ChangeObject; + +export interface CommonDiffOptions { + /** + * If `true`, the array of change objects returned will contain one change object per token (e.g. one per line if calling `diffLines`), instead of runs of consecutive tokens that are all added / all removed / all conserved being combined into a single change object. + */ + oneChangePerToken?: boolean, +} + +export interface TimeoutOption { + /** + * A number of milliseconds after which the diffing algorithm will abort and return `undefined`. + * Supported by the same functions as `maxEditLength`. + */ + timeout: number; +} + +export interface MaxEditLengthOption { + /** + * A number specifying the maximum edit distance to consider between the old and new texts. + * You can use this to limit the computational cost of diffing large, very different texts by giving up early if the cost will be huge. + * This option can be passed either to diffing functions (`diffLines`, `diffChars`, etc) or to patch-creation function (`structuredPatch`, `createPatch`, etc), all of which will indicate that the max edit length was reached by returning `undefined` instead of whatever they'd normally return. + */ + maxEditLength: number; +} + +export type AbortableDiffOptions = TimeoutOption | MaxEditLengthOption; + +export type DiffCallbackNonabortable = (result: ChangeObject[]) => void; +export type DiffCallbackAbortable = (result: ChangeObject[] | undefined) => void; + +export interface CallbackOptionNonabortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: DiffCallbackNonabortable +} +export interface CallbackOptionAbortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: DiffCallbackAbortable +} + +interface DiffArraysOptions extends CommonDiffOptions { + comparator?: (a: T, b: T) => boolean, +} +export interface DiffArraysOptionsNonabortable extends DiffArraysOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffArraysOptionsAbortable = DiffArraysOptions & AbortableDiffOptions & Partial> + + +interface DiffCharsOptions extends CommonDiffOptions { + /** + * If `true`, the uppercase and lowercase forms of a character are considered equal. + * @default false + */ + ignoreCase?: boolean; +} +export interface DiffCharsOptionsNonabortable extends DiffCharsOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffCharsOptionsAbortable = DiffCharsOptions & AbortableDiffOptions & Partial> + +interface DiffLinesOptions extends CommonDiffOptions { + /** + * `true` to remove all trailing CR (`\r`) characters before performing the diff. + * This helps to get a useful diff when diffing UNIX text files against Windows text files. + * @default false + */ + stripTrailingCr?: boolean, + /** + * `true` to treat the newline character at the end of each line as its own token. + * This allows for changes to the newline structure to occur independently of the line content and to be treated as such. + * In general this is the more human friendly form of `diffLines`; the default behavior with this option turned off is better suited for patches and other computer friendly output. + * + * Note that while using `ignoreWhitespace` in combination with `newlineIsToken` is not an error, results may not be as expected. + * With `ignoreWhitespace: true` and `newlineIsToken: false`, changing a completely empty line to contain some spaces is treated as a non-change, but with `ignoreWhitespace: true` and `newlineIsToken: true`, it is treated as an insertion. + * This is because the content of a completely blank line is not a token at all in `newlineIsToken` mode. + * + * @default false + */ + newlineIsToken?: boolean, + /** + * `true` to ignore a missing newline character at the end of the last line when comparing it to other lines. + * (By default, the line `'b\n'` in text `'a\nb\nc'` is not considered equal to the line `'b'` in text `'a\nb'`; this option makes them be considered equal.) + * Ignored if `ignoreWhitespace` or `newlineIsToken` are also true. + * @default false + */ + ignoreNewlineAtEof?: boolean, + /** + * `true` to ignore leading and trailing whitespace characters when checking if two lines are equal. + * @default false + */ + ignoreWhitespace?: boolean, +} +export interface DiffLinesOptionsNonabortable extends DiffLinesOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffLinesOptionsAbortable = DiffLinesOptions & AbortableDiffOptions & Partial> + + +interface DiffWordsOptions extends CommonDiffOptions { + /** + * Same as in `diffChars`. + * @default false + */ + ignoreCase?: boolean + + /** + * An optional [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) object (which must have a `granularity` of `'word'`) for `diffWords` to use to split the text into words. + * + * Note that this is (deliberately) incorrectly typed as `any` to avoid users whose `lib` & `target` settings in tsconfig.json are older than es2022 getting type errors when they build about `Intl.Segmenter` not existing. + * This is kind of ugly, since it makes the type declarations worse for users who genuinely use this feature, but seemed worth it to avoid the majority of the library's users (who probably do not use this particular option) getting confusing errors and being forced to change their `lib` to es2022 (even if their own code doesn't use any es2022 functions). + * + * By default, `diffWords` does not use an `Intl.Segmenter`, just some regexes for splitting text into words. This will tend to give worse results than `Intl.Segmenter` would, but ensures the results are consistent across environments; `Intl.Segmenter` behaviour is only loosely specced and the implementations in browsers could in principle change dramatically in future. If you want to use `diffWords` with an `Intl.Segmenter` but ensure it behaves the same whatever environment you run it in, use an `Intl.Segmenter` polyfill instead of the JavaScript engine's native `Intl.Segmenter` implementation. + * + * Using an `Intl.Segmenter` should allow better word-level diffing of non-English text than the default behaviour. For instance, `Intl.Segmenter`s can generally identify via built-in dictionaries which sequences of adjacent Chinese characters form words, allowing word-level diffing of Chinese. By specifying a language when instantiating the segmenter (e.g. `new Intl.Segmenter('sv', {granularity: 'word'})`) you can also support language-specific rules, like treating Swedish's colon separated contractions (like *k:a* for *kyrka*) as single words; by default this would be seen as two words separated by a colon. + */ + intlSegmenter?: any, +} +export interface DiffWordsOptionsNonabortable extends DiffWordsOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffWordsOptionsAbortable = DiffWordsOptions & AbortableDiffOptions & Partial> + + +interface DiffSentencesOptions extends CommonDiffOptions {} +export interface DiffSentencesOptionsNonabortable extends DiffSentencesOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffSentencesOptionsAbortable = DiffSentencesOptions & AbortableDiffOptions & Partial> + + +interface DiffJsonOptions extends CommonDiffOptions { + /** + * A value to replace `undefined` with. Ignored if a `stringifyReplacer` is provided. + */ + undefinedReplacement?: any, + /** + * A custom replacer function. + * Operates similarly to the `replacer` parameter to [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter), but must be a function. + */ + stringifyReplacer?: (k: string, v: any) => any, +} +export interface DiffJsonOptionsNonabortable extends DiffJsonOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffJsonOptionsAbortable = DiffJsonOptions & AbortableDiffOptions & Partial> + + +interface DiffCssOptions extends CommonDiffOptions {} +export interface DiffCssOptionsNonabortable extends DiffCssOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffCssOptionsAbortable = DiffCssOptions & AbortableDiffOptions & Partial> + + +/** + * Note that this contains the union of ALL options accepted by any of the built-in diffing + * functions. The README notes which options are usable which functions. Using an option with a + * diffing function that doesn't support it might yield unreasonable results. + */ +export type AllDiffOptions = + DiffArraysOptions & + DiffCharsOptions & + DiffWordsOptions & + DiffLinesOptions & + DiffJsonOptions; + +export interface StructuredPatch { + oldFileName: string | undefined, + newFileName: string | undefined, + oldHeader: string | undefined, + newHeader: string | undefined, + hunks: StructuredPatchHunk[], + index?: string, + /** + * Set to true when the patch was parsed from a Git-style diff (one with a + * `diff --git` header). Controls whether `formatPatch` emits a `diff --git` + * header (instead of `Index:` / underline headers) when formatting the patch. + */ + isGit?: boolean, + /** + * Set to true when parsing a Git diff that includes `rename from`/`rename to` + * extended headers, indicating the file was renamed (and the old file no + * longer exists). Consumers applying the patch should delete the old file. + */ + isRename?: boolean, + /** + * Set to true when parsing a Git diff that includes `copy from`/`copy to` + * extended headers, indicating the file was copied (and the old file still + * exists). Consumers applying the patch should NOT delete the old file. + */ + isCopy?: boolean, + /** + * Set to true when parsing a Git diff that includes a `new file mode` extended + * header, indicating the file was newly created. + */ + isCreate?: boolean, + /** + * Set to true when parsing a Git diff that includes a `deleted file mode` + * extended header, indicating the file was deleted. + */ + isDelete?: boolean, + /** + * The file mode (e.g. `'100644'`, `'100755'`) of the old file, parsed from + * Git extended headers (`old mode` or `deleted file mode`). + */ + oldMode?: string, + /** + * The file mode (e.g. `'100644'`, `'100755'`) of the new file, parsed from + * Git extended headers (`new mode` or `new file mode`). + */ + newMode?: string, + /** + * Set to true when parsing a Git diff that includes a + * `Binary files ... differ` line, indicating a binary file change. + * Binary patches have no hunks, so the patch content alone is not + * sufficient to apply the change; consumers should handle this case + * specially (e.g. by warning the user or fetching the binary content + * separately). + */ + isBinary?: boolean, +} + +export interface StructuredPatchHunk { + oldStart: number, + oldLines: number, + newStart: number, + newLines: number, + lines: string[], +} diff --git a/src/util/array.js b/src/util/array.ts similarity index 67% rename from src/util/array.js rename to src/util/array.ts index 5de3cab9d..602714aab 100644 --- a/src/util/array.js +++ b/src/util/array.ts @@ -1,4 +1,4 @@ -export function arrayEqual(a, b) { +export function arrayEqual(a: any[], b: any[]): boolean { if (a.length !== b.length) { return false; } @@ -6,7 +6,7 @@ export function arrayEqual(a, b) { return arrayStartsWith(a, b); } -export function arrayStartsWith(array, start) { +export function arrayStartsWith(array: any[], start: any[]): boolean { if (start.length > array.length) { return false; } diff --git a/src/util/distance-iterator.ts b/src/util/distance-iterator.ts new file mode 100644 index 000000000..e2fe316cc --- /dev/null +++ b/src/util/distance-iterator.ts @@ -0,0 +1,46 @@ +// Iterator that traverses in the range of [min, max], stepping +// by distance from a given start position. I.e. for [0, 4], with +// start of 2, this will iterate 2, 3, 1, 4, 0. +export default function(start: number, minLine: number, maxLine: number): () => number | undefined { + let wantForward = true, + backwardExhausted = false, + forwardExhausted = false, + localOffset = 1; + + return function iterator(): number | undefined { + if (wantForward && !forwardExhausted) { + if (backwardExhausted) { + localOffset++; + } else { + wantForward = false; + } + + // Check if trying to fit beyond text length, and if not, check it fits + // after offset location (or desired location on first iteration) + if (start + localOffset <= maxLine) { + return start + localOffset; + } + + forwardExhausted = true; + } + + if (!backwardExhausted) { + if (!forwardExhausted) { + wantForward = true; + } + + // Check if trying to fit before text beginning, and if not, check it fits + // before offset location + if (minLine <= start - localOffset) { + return start - localOffset++; + } + + backwardExhausted = true; + return iterator(); + } + + // We tried to fit hunk before text beginning and beyond text length, then + // hunk can't fit on the text. Return undefined + return undefined; + }; +} diff --git a/src/util/params.js b/src/util/params.js deleted file mode 100644 index 07e03e59e..000000000 --- a/src/util/params.js +++ /dev/null @@ -1,13 +0,0 @@ -export function generateOptions(options, defaults) { - if (typeof options === 'function') { - defaults.callback = options; - } else if (options) { - for (let name in options) { - /* istanbul ignore else */ - if (options.hasOwnProperty(name)) { - defaults[name] = options[name]; - } - } - } - return defaults; -} diff --git a/src/util/params.ts b/src/util/params.ts new file mode 100644 index 000000000..09911cbc7 --- /dev/null +++ b/src/util/params.ts @@ -0,0 +1,16 @@ +export function generateOptions( + options: {[key: string]: any} | ((_: unknown) => void), + defaults: any +): object { + if (typeof options === 'function') { + defaults.callback = options; + } else if (options) { + for (const name in options) { + /* istanbul ignore else */ + if (Object.prototype.hasOwnProperty.call(options, name)) { + defaults[name] = options[name]; + } + } + } + return defaults; +} diff --git a/src/util/string.ts b/src/util/string.ts new file mode 100644 index 000000000..112dcf9fd --- /dev/null +++ b/src/util/string.ts @@ -0,0 +1,195 @@ +export function longestCommonPrefix(str1: string, str2: string): string { + let i; + for (i = 0; i < str1.length && i < str2.length; i++) { + if (str1[i] != str2[i]) { + return str1.slice(0, i); + } + } + return str1.slice(0, i); +} + +export function longestCommonSuffix(str1: string, str2: string): string { + let i; + + // Unlike longestCommonPrefix, we need a special case to handle all scenarios + // where we return the empty string since str1.slice(-0) will return the + // entire string. + if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) { + return ''; + } + + for (i = 0; i < str1.length && i < str2.length; i++) { + if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) { + return str1.slice(-i); + } + } + return str1.slice(-i); +} + +export function replacePrefix(string: string, oldPrefix: string, newPrefix: string): string { + if (string.slice(0, oldPrefix.length) != oldPrefix) { + throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`); + } + return newPrefix + string.slice(oldPrefix.length); +} + +export function replaceSuffix(string: string, oldSuffix: string, newSuffix: string): string { + if (!oldSuffix) { + return string + newSuffix; + } + + if (string.slice(-oldSuffix.length) != oldSuffix) { + throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`); + } + return string.slice(0, -oldSuffix.length) + newSuffix; +} + +export function removePrefix(string: string, oldPrefix: string): string { + return replacePrefix(string, oldPrefix, ''); +} + +export function removeSuffix(string: string, oldSuffix: string): string { + return replaceSuffix(string, oldSuffix, ''); +} + +export function maximumOverlap(string1: string, string2: string): string { + return string2.slice(0, overlapCount(string1, string2)); +} + +// Nicked from https://stackoverflow.com/a/60422853/1709587 +function overlapCount(a: string, b: string): number { + // Deal with cases where the strings differ in length + let startA = 0; + if (a.length > b.length) { startA = a.length - b.length; } + let endB = b.length; + if (a.length < b.length) { endB = a.length; } + // Create a back-reference for each index + // that should be followed in case of a mismatch. + // We only need B to make these references: + const map = Array(endB); + let k = 0; // Index that lags behind j + map[0] = 0; + for (let j = 1; j < endB; j++) { + if (b[j] == b[k]) { + map[j] = map[k]; // skip over the same character (optional optimisation) + } else { + map[j] = k; + } + while (k > 0 && b[j] != b[k]) { k = map[k]; } + if (b[j] == b[k]) { k++; } + } + // Phase 2: use these references while iterating over A + k = 0; + for (let i = startA; i < a.length; i++) { + while (k > 0 && a[i] != b[k]) { k = map[k]; } + if (a[i] == b[k]) { k++; } + } + return k; +} + + +/** + * Returns true if the string consistently uses Windows line endings. + */ +export function hasOnlyWinLineEndings(string: string): boolean { + return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/); +} + +/** + * Returns true if the string consistently uses Unix line endings. + */ +export function hasOnlyUnixLineEndings(string: string): boolean { + return !string.includes('\r\n') && string.includes('\n'); +} + +/** + * Split a string into segments using a word segmenter, merging consecutive + * segments if they are both whitespace segments. Whitespace segments can + * appear adjacent to one another for two reasons: + * - newlines always get their own segment + * - where a diacritic is attached to a whitespace character in the text, the + * segment ends after the diacritic, so e.g. " \u0300 " becomes two segments. + * This function therefore runs the segmenter's .segment() method and then + * merges consecutive segments of whitespace into a single part. + */ +export function segment(string: string, segmenter: Intl.Segmenter): string[] { + const parts = []; + for (const segmentObj of Array.from(segmenter.segment(string))) { + const segment = segmentObj.segment; + if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment)) { + parts[parts.length - 1] += segment; + } else { + parts.push(segment); + } + } + return parts; +} + +// The functions below take a `segmenter` argument so that, when called from +// diffWords when it is using a segmenter, they can use a notion of what +// constitutes "whitespace" that is consistent with the segmenter. +// +// USUALLY this will be identical to the result of the non-segmenter-based +// logic, but it differs in at least one case: when whitespace characters are +// modified by diacritics. A word segmenter considers these diacritics to be +// part of the whitespace, whereas our non-segmenter-based logic does not. +// +// Because the segmenter-based approach necessarily requires segmenting the +// entire string, we offer a leadingAndTrailingWs function to allow getting the +// whitespace prefix AND whitespace suffix with a single call to the segmenter, +// for efficiency's sake. + +export function trailingWs(string: string, segmenter?: Intl.Segmenter): string { + if (segmenter) { + return leadingAndTrailingWs(string, segmenter)[1]; + } + + // Yes, this looks overcomplicated and dumb - why not replace the whole function with + // return string.match(/\s*$/)[0] + // you ask? Because: + // 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing + // this would cause this function to take O(n²) time in the worst case (specifically when + // there is a massive run of NON-TRAILING whitespace in `string`), and + // 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible + // with old Safari versions that we'd like to not break if possible (see + // https://github.com/kpdecker/jsdiff/pull/550) + // It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a + // better way that doesn't result in broken behaviour. + let i; + for (i = string.length - 1; i >= 0; i--) { + if (!string[i].match(/\s/)) { + break; + } + } + return string.substring(i + 1); +} + +export function leadingWs(string: string, segmenter?: Intl.Segmenter): string { + if (segmenter) { + return leadingAndTrailingWs(string, segmenter)[0]; + } + + // Thankfully the annoying considerations described in trailingWs don't apply here: + const match = string.match(/^\s*/); + return match ? match[0] : ''; +} + +export function leadingAndTrailingWs( + string: string, + segmenter?: Intl.Segmenter +): [string, string] { + if (!segmenter) { + return [leadingWs(string), trailingWs(string)]; + } + + if (segmenter.resolvedOptions().granularity != 'word') { + throw new Error('The segmenter passed must have a granularity of "word"'); + } + + const segments = segment(string, segmenter); + const firstSeg = segments[0]; + const lastSeg = segments[segments.length - 1]; + const head = (/\s/).test(firstSeg) ? firstSeg : ''; + const tail = (/\s/).test(lastSeg) ? lastSeg : ''; + return [head, tail]; +} diff --git a/style.css b/style.css deleted file mode 100644 index 2047e2df8..000000000 --- a/style.css +++ /dev/null @@ -1,81 +0,0 @@ -* { - margin: 0; - padding: 0; -} -html, body { - background: #EEE; - font: 12px sans-serif; -} -body { - padding-top: 1.8em; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -html, body, table, tbody, tr, td { - height: 100% -} -table { - table-layout: fixed; - width: 100%; -} -td { - width: 33%; - padding: 3px 4px; - border: 1px solid transparent; - vertical-align: top; - font: 1em monospace; - text-align: left; - white-space: pre-wrap; -} -h1 { - display: inline; - font-size: 100%; -} -del { - text-decoration: none; - color: #b30000; - background: #fadad7; -} -ins { - background: #eaf2c2; - color: #406619; - text-decoration: none; -} - -#settings { - position: absolute; - top: 0; - left: 5px; - right: 5px; - height: 2em; - line-height: 2em; -} -#settings label { - margin-left: 1em; -} - -.source { - position: absolute; - right: 1%; - top: .2em; -} - -[contentEditable] { - background: #F9F9F9; - border-color: #BBB #D9D9D9 #DDD; - border-radius: 4px; - -webkit-user-modify: read-write-plaintext-only; - outline: none; -} -[contentEditable]:focus { - background: #FFF; - border-color: #6699cc; - box-shadow: 0 0 4px #2175c9; -} - -@-moz-document url-prefix() { - body { - height: 99%; /* Hide scroll bar in Firefox */ - } -} diff --git a/tasks/util/git.js b/tasks/util/git.js deleted file mode 100644 index c46e3a700..000000000 --- a/tasks/util/git.js +++ /dev/null @@ -1,13 +0,0 @@ -var childProcess = require('child_process'); - -module.exports = { - add: function(path, callback) { - childProcess.exec('git add -f ' + path, {}, function(err) { - if (err) { - throw new Error('git.add: ' + err.message); - } - - callback(); - }); - } -}; diff --git a/tasks/version.js b/tasks/version.js deleted file mode 100644 index 5f4c3a109..000000000 --- a/tasks/version.js +++ /dev/null @@ -1,40 +0,0 @@ -var async = require('async'), - git = require('./util/git'), - semver = require('semver'); - -module.exports = function(grunt) { - grunt.registerTask('version', 'Updates the current release version', function() { - var done = this.async(), - pkg = grunt.config('pkg'), - version = grunt.option('ver'); - - if (!semver.valid(version)) { - throw new Error('Must provide a version number (Ex: --ver=1.0.0):\n\t' + version + '\n\n'); - } - - pkg.version = version; - grunt.config('pkg', pkg); - - grunt.log.writeln('Updating to version ' + version); - - async.each([ - ['components/bower.json', (/"version":.*/), '"version": "' + version + '",'], - ['components/component.json', (/"version":.*/), '"version": "' + version + '",'] - ], - function(args, callback) { - replace.apply(undefined, args); - grunt.log.writeln(' - ' + args[0]); - git.add(args[0], callback); - }, - function() { - grunt.task.run(['release']); - done(); - }); - }); - - function replace(path, regex, value) { - var content = grunt.file.read(path); - content = content.replace(regex, value); - grunt.file.write(path, content); - } -}; diff --git a/test-d/diffCharsOverloads.test-d.ts b/test-d/diffCharsOverloads.test-d.ts new file mode 100644 index 000000000..bb29e9943 --- /dev/null +++ b/test-d/diffCharsOverloads.test-d.ts @@ -0,0 +1,34 @@ +import {expectType} from 'tsd'; +import {ChangeObject, diffChars} from '../libesm/index.js'; + +const result1 = diffChars('foo', 'bar', {ignoreCase: true}); +expectType[]>(result1); + +const result2 = diffChars('foo', 'bar'); +expectType[]>(result2); + +const result3 = diffChars('foo', 'bar', {timeout: 100}); +expectType[] | undefined>(result3); + +const result4 = diffChars('foo', 'bar', {maxEditLength: 100}); +expectType[] | undefined>(result4); + +const result5 = diffChars('foo', 'bar', cbResult => { + expectType[]>(cbResult) +}); +expectType(result5); + +const result6 = diffChars('foo', 'bar', { + callback: cbResult => { + expectType[]>(cbResult); + } +}); +expectType(result6); + +const result7 = diffChars('foo', 'bar', { + timeout: 100, + callback: cbResult => { + expectType[] | undefined>(cbResult) + } +}); +expectType(result7); diff --git a/test-d/originalDefinitelyTypedTests.test-d.ts b/test-d/originalDefinitelyTypedTests.test-d.ts new file mode 100644 index 000000000..9e56efab0 --- /dev/null +++ b/test-d/originalDefinitelyTypedTests.test-d.ts @@ -0,0 +1,186 @@ +/** + * This file was copied from + * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/diff/diff-tests.ts + * then tweaked to work with tsd. + */ + +import {expectType} from 'tsd'; +import Diff, { Change, StructuredPatch } from "../libesm/index.js"; + +const one = "beep boop"; +const other = "beep boob blah"; + +let changes = Diff.diffChars(one, other); +examineChanges(changes); + +expectType(Diff.diffChars(one, other, { + callback: (value) => { + expectType(value); + }, +})); +expectType(Diff.diffChars(one, other, (value) => { + expectType(value); +})); +Diff.diffWords("吾輩は猫である。名前はまだ無い。", "吾輩は猫である。名前はたぬき。", { + intlSegmenter: new Intl.Segmenter("ja-JP", { granularity: "word" }), +}); +expectType( + Diff.diffLines( + "line\nold value\nline", + "line\nnew value\nline", + { + stripTrailingCr: true, + ignoreNewlineAtEof: true, + maxEditLength: 1, + oneChangePerToken: true, + }, + ) +); +expectType( + Diff.createPatch("filename", "A", "a", undefined, undefined, { + callback: (value) => { + expectType(value); + }, + }) +); + +const diffArraysResult = Diff.diffArrays(["a", "b", "c"], ["a", "c", "d"]); +diffArraysResult.forEach(result => { + expectType(result.added); + expectType(result.removed); + expectType(result.value); + expectType(result.count); +}); + +interface DiffObj { + value: number; +} +const a: DiffObj = { value: 0 }; +const b: DiffObj = { value: 1 }; +const c: DiffObj = { value: 2 }; +const d: DiffObj = { value: 3 }; +const arrayOptions: Diff.DiffArraysOptionsNonabortable = { + comparator: (left, right) => { + return left.value === right.value; + }, +}; +const arrayChanges = Diff.diffArrays([a, b, c], [a, b, d], arrayOptions); +arrayChanges.forEach(result => { + expectType(result.added) + expectType(result.removed) + expectType(result.value) + expectType(result.count) +}); + +// -------------------------- + +class LineDiffWithoutWhitespace extends Diff.Diff { + tokenize(value: string): any { + return value.split(/^/m); + } + + equals(left: string, right: string): boolean { + return left.trim() === right.trim(); + } +} + +const obj = new LineDiffWithoutWhitespace(); +changes = obj.diff(one, other); +examineChanges(changes); + +function examineChanges(diff: Diff.Change[]) { + diff.forEach(part => { + expectType(part.added); + expectType(part.removed); + expectType(part.value); + expectType(part.count); + }); +} + +function verifyPatchMethods(oldStr: string, newStr: string, uniDiff: Diff.StructuredPatch) { + const verifyPatch = Diff.parsePatch( + Diff.createTwoFilesPatch("oldFile.ts", "newFile.ts", oldStr, newStr, "old", "new", { + context: 1, + stripTrailingCr: true, + }), + ); + + if ( + JSON.stringify(verifyPatch[0], Object.keys(verifyPatch[0]).sort()) + !== JSON.stringify(uniDiff, Object.keys(uniDiff).sort()) + ) { + throw new Error("Patch did not match uniDiff"); + } +} +function verifyApplyMethods(oldStr: string, newStr: string, uniDiffStr: string) { + const uniDiff = Diff.parsePatch(uniDiffStr)[0]; + const verifyApply = [Diff.applyPatch(oldStr, uniDiff), Diff.applyPatch(oldStr, [uniDiff])]; + const options: Diff.ApplyPatchesOptions = { + loadFile(index, callback) { + expectType(index); + callback(undefined, one); + }, + patched(index, content) { + expectType(index); + if (content !== false) { + verifyApply.push(content); + } + }, + complete(err) { + if (err) { + throw err; + } + + verifyApply.forEach(result => { + if (result !== newStr) { + throw new Error("Result did not match newStr"); + } + }); + }, + compareLine(_, line, operator, patchContent) { + if (operator === " ") { + return true; + } + return line === patchContent; + }, + fuzzFactor: 0, + }; + Diff.applyPatches([uniDiff], options); + Diff.applyPatches(uniDiffStr, options); +} + +const uniDiffPatch = Diff.structuredPatch("oldFile.ts", "newFile.ts", one, other, "old", "new", { + context: 1, +}); +verifyPatchMethods(one, other, uniDiffPatch); + +const formatted: string = Diff.formatPatch(uniDiffPatch); + +const uniDiffStr = Diff.createPatch("file.ts", one, other, "old", "new", { context: 1 }); +verifyApplyMethods(one, other, uniDiffStr); + +const file1 = "line1\nline2\nline3\nline4\n"; +const file2 = "line1\nline2\nline5\nline4\n"; +const patch = Diff.structuredPatch("file1", "file2", file1, file2); +expectType(patch); +const reversedPatch = Diff.reversePatch(patch); +expectType(reversedPatch) +const verifyPatch = Diff.parsePatch( + Diff.createTwoFilesPatch("oldFile.ts", "newFile.ts", "old content", "new content", "old", "new", { + context: 1, + }), +); +expectType(verifyPatch) + +const wordDiff = new Diff.Diff(); +wordDiff.equals = function(left, right, options) { + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes("\n")) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes("\n")) { + right = right.trim(); + } + } + return Diff.Diff.prototype.equals.call(this, left, right, options); +}; diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 8f53c7a75..000000000 --- a/test/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "rules": { - // Disabling for tests, for now. - "no-unused-expressions": 0, - "no-path-concat": 0, - - "no-console": 0 - } -} \ No newline at end of file diff --git a/test/convert/dmp.js b/test/convert/dmp.js index f6ab6965d..e5591294e 100644 --- a/test/convert/dmp.js +++ b/test/convert/dmp.js @@ -1,12 +1,12 @@ -import {convertChangesToDMP} from '../../lib/convert/dmp'; -import {diffWords} from '../../lib/diff/word'; +import {convertChangesToDMP} from '../../libesm/convert/dmp.js'; +import {diffChars} from '../../libesm/diff/character.js'; import {expect} from 'chai'; describe('convertToDMP', function() { it('should output diff-match-patch format', function() { - const diffResult = diffWords('New Value ', 'New ValueMoreData '); + const diffResult = diffChars('New Value ', 'New ValueMoreData '); - expect(convertChangesToDMP(diffResult)).to.eql([[0, 'New '], [-1, 'Value'], [1, 'ValueMoreData'], [0, ' ']]); + expect(convertChangesToDMP(diffResult)).to.eql([[0, 'New '], [1, ' '], [0, 'Value'], [1, 'MoreData'], [0, ' '], [-1, ' ']]); }); }); diff --git a/test/diff/array.js b/test/diff/array.js new file mode 100644 index 000000000..35c56ba87 --- /dev/null +++ b/test/diff/array.js @@ -0,0 +1,112 @@ +import {diffArrays} from '../../libesm/diff/array.js'; + +import {expect} from 'chai'; + +describe('diff/array', function() { + describe('#diffArrays', function() { + it('Should diff arrays', function() { + const a = {a: 0}, b = {b: 1}, c = {c: 2}; + const diffResult = diffArrays([a, b, c], [a, c, b]); + expect(diffResult).to.deep.equals([ + {count: 1, value: [a], removed: false, added: false}, + {count: 1, value: [b], removed: true, added: false}, + {count: 1, value: [c], removed: false, added: false}, + {count: 1, value: [b], removed: false, added: true} + ]); + }); + it('should diff falsey values', function() { + const a = false; + const b = 0; + const c = ''; + // Example sequences from Myers 1986 + const arrayA = [a, b, c, a, b, b, a]; + const arrayB = [c, b, a, b, a, c]; + const diffResult = diffArrays(arrayA, arrayB); + expect(diffResult).to.deep.equals([ + {count: 2, value: [a, b], removed: true, added: false}, + {count: 1, value: [c], removed: false, added: false}, + {count: 1, value: [b], removed: false, added: true}, + {count: 2, value: [a, b], removed: false, added: false}, + {count: 1, value: [b], removed: true, added: false}, + {count: 1, value: [a], removed: false, added: false}, + {count: 1, value: [c], removed: false, added: true} + ]); + }); + describe('anti-aliasing', function() { + // Test apparent contract that no chunk value is ever an input argument. + const value = [0, 1, 2]; + const expected = [ + {count: value.length, value: value, removed: false, added: false} + ]; + + const input = value.slice(); + const diffResult = diffArrays(input, input); + it('returns correct deep result for identical inputs', function() { + expect(diffResult).to.deep.equals(expected); + }); + it('does not return the input array', function() { + expect(diffResult[0].value).to.not.equal(input); + }); + + const input1 = value.slice(); + const input2 = value.slice(); + const diffResult2 = diffArrays(input1, input2); + it('returns correct deep result for equivalent inputs', function() { + expect(diffResult2).to.deep.equals(expected); + }); + it('does not return the first input array', function() { + expect(diffResult2[0].value).to.not.equal(input1); + }); + it('does not return the second input array', function() { + expect(diffResult2[0].value).to.not.equal(input2); + }); + }); + it('Should diff arrays with comparator', function() { + const a = {a: 0}, b = {a: 1}, c = {a: 2}, d = {a: 3}; + function comparator(left, right) { + return left.a === right.a; + } + const diffResult = diffArrays([a, b, c], [a, b, d], { comparator: comparator }); + expect(diffResult).to.deep.equals([ + {count: 2, value: [a, b], removed: false, added: false}, + {count: 1, value: [c], removed: true, added: false}, + {count: 1, value: [d], removed: false, added: true} + ]); + }); + it('Should pass old/new tokens as the left/right comparator args respectively', function() { + diffArrays( + ['a', 'b', 'c'], + ['x', 'y', 'z'], + { + comparator: function(left, right) { + expect(left).to.be.oneOf(['a', 'b', 'c']); + expect(right).to.be.oneOf(['x', 'y', 'z']); + return left === right; + } + } + ); + }); + it('Should terminate early if execution time exceeds `timeout` ms', function() { + // To test this, we also pass a comparator that hot sleeps as a way to + // artificially slow down execution so we reach the timeout. + function comparator(left, right) { + const start = Date.now(); + // Hot-sleep for 10ms + while (Date.now() < start + 10) { + // Do nothing + } + return left === right; + } + + // It will require 14 comparisons (140ms) to diff these arrays: + const arr1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']; + const arr2 = ['a', 'b', 'c', 'd', 'x', 'y', 'z']; + + // So with a timeout of 50ms, we are guaranteed failure: + expect(diffArrays(arr1, arr2, {comparator, timeout: 50})).to.be.undefined; + + // But with a longer timeout, we expect success: + expect(diffArrays(arr1, arr2, {comparator, timeout: 1000})).not.to.be.undefined; + }); + }); +}); diff --git a/test/diff/character.js b/test/diff/character.js index e0fb8be5a..e999566c5 100644 --- a/test/diff/character.js +++ b/test/diff/character.js @@ -1,13 +1,68 @@ -import {diffChars} from '../../lib/diff/character'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffChars} from '../../libesm/diff/character.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; describe('diff/character', function() { describe('#diffChars', function() { it('Should diff chars', function() { - const diffResult = diffChars('New Value.', 'New ValueMoreData.'); - expect(convertChangesToXML(diffResult)).to.equal('New ValueMoreData.'); + const diffResult = diffChars('Old Value.', 'New ValueMoreData.'); + expect(convertChangesToXML(diffResult)).to.equal('OldNew ValueMoreData.'); + }); + + describe('oneChangePerToken option', function() { + it('emits one change per character', function() { + const diffResult = diffChars('Old Value.', 'New ValueMoreData.', {oneChangePerToken: true}); + expect(diffResult.length).to.equal(21); + expect(convertChangesToXML(diffResult)).to.equal('OldNew ValueMoreData.'); + }); + + it('correctly handles the case where the texts are identical', function() { + const diffResult = diffChars('foo bar baz qux', 'foo bar baz qux', {oneChangePerToken: true}); + expect(diffResult).to.deep.equal( + ['f', 'o', 'o', ' ', 'b', 'a', 'r', ' ', 'b', 'a', 'z', ' ', 'q', 'u', 'x'].map( + char => ({value: char, count: 1, added: false, removed: false}) + ) + ); + }); + }); + + it('should treat a code point that consists of two UTF-16 code units as a single character, not two', function() { + const diffResult = diffChars('𝟘𝟙𝟚𝟛', '𝟘𝟙𝟚𝟜𝟝𝟞'); + expect(diffResult.length).to.equal(3); + expect(diffResult[2].count).to.equal(3); + expect(convertChangesToXML(diffResult)).to.equal('𝟘𝟙𝟚𝟛𝟜𝟝𝟞'); + }); + + describe('case insensitivity', function() { + it("is considered when there's no difference", function() { + const diffResult = diffChars('New Value.', 'New value.', {ignoreCase: true}); + expect(convertChangesToXML(diffResult)).to.equal('New value.'); + }); + + it("is considered when there's a difference", function() { + const diffResult = diffChars('New Values.', 'New value.', {ignoreCase: true}); + expect(convertChangesToXML(diffResult)).to.equal('New values.'); + }); + }); + + it('should not be susceptible to race conditions in async mode when called with different options', function(done) { + // (regression test for https://github.com/kpdecker/jsdiff/issues/477) + diffChars('wibblywobbly', 'WIBBLYWOBBLY', {ignoreCase: false, callback: (diffResult) => { + expect(convertChangesToXML(diffResult)).to.equal('wibblywobblyWIBBLYWOBBLY'); + done(); + }}); + + // Historically, doing this while async execution of the previous + // diffChars call was ongoing would overwrite this.options and make the + // ongoing diff become case-insensitive partway through execution. + diffChars('whatever', 'whatever', {ignoreCase: true}); + diffChars('whatever', 'whatever', {ignoreCase: true, callback: () => {}}); + }); + + it('should return undefined when called in async mode', function() { + expect(diffChars('whatever', 'whatever', {callback: () => {}})).to.be.undefined; + expect(diffChars('whatever', 'whatever else', {callback: () => {}})).to.be.undefined; }); }); }); diff --git a/test/diff/css.js b/test/diff/css.js index 2b7277a22..c7636db57 100644 --- a/test/diff/css.js +++ b/test/diff/css.js @@ -1,5 +1,5 @@ -import {diffCss} from '../../lib/diff/css'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffCss} from '../../libesm/diff/css.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; diff --git a/test/diff/json.js b/test/diff/json.js index 433d78d4c..4f7255a0d 100644 --- a/test/diff/json.js +++ b/test/diff/json.js @@ -1,5 +1,5 @@ -import {diffJson, canonicalize} from '../../lib/diff/json'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffJson, canonicalize} from '../../libesm/diff/json.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; @@ -10,19 +10,20 @@ describe('diff/json', function() { {a: 123, b: 456, c: 789}, {a: 123, b: 456} )).to.eql([ - { count: 3, value: '{\n "a": 123,\n "b": 456,\n' }, - { count: 1, value: ' "c": 789\n', added: undefined, removed: true }, - { count: 1, value: '}' } + { count: 3, value: '{\n "a": 123,\n "b": 456,\n', removed: false, added: false }, + { count: 1, value: ' "c": 789\n', added: false, removed: true }, + { count: 1, value: '}', removed: false, added: false } ]); }); + it('should accept objects with different order', function() { expect(diffJson( {a: 123, b: 456, c: 789}, {b: 456, a: 123} )).to.eql([ - { count: 3, value: '{\n "a": 123,\n "b": 456,\n' }, - { count: 1, value: ' "c": 789\n', added: undefined, removed: true }, - { count: 1, value: '}' } + { count: 3, value: '{\n "a": 123,\n "b": 456,\n', removed: false, added: false }, + { count: 1, value: ' "c": 789\n', added: false, removed: true }, + { count: 1, value: '}', removed: false, added: false } ]); }); @@ -31,9 +32,49 @@ describe('diff/json', function() { {a: 123, b: 456, c: [1, 2, {foo: 'bar'}, 4]}, {a: 123, b: 456, c: [1, {foo: 'bar'}, 4]} )).to.eql([ - { count: 5, value: '{\n "a": 123,\n "b": 456,\n "c": [\n 1,\n' }, - { count: 1, value: ' 2,\n', added: undefined, removed: true }, - { count: 6, value: ' {\n "foo": "bar"\n },\n 4\n ]\n}' } + { count: 5, value: '{\n "a": 123,\n "b": 456,\n "c": [\n 1,\n', removed: false, added: false }, + { count: 1, value: ' 2,\n', added: false, removed: true }, + { count: 6, value: ' {\n "foo": "bar"\n },\n 4\n ]\n}', removed: false, added: false } + ]); + }); + + it('should accept dates', function() { + expect(diffJson( + {a: new Date(123), b: new Date(456), c: new Date(789)}, + {a: new Date(124), b: new Date(456)} + )).to.eql([ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "a": "1970-01-01T00:00:00.123Z",\n', added: false, removed: true }, + { count: 1, value: ' "a": "1970-01-01T00:00:00.124Z",\n', added: true, removed: false }, + { count: 1, value: ' "b": "1970-01-01T00:00:00.456Z",\n', removed: false, added: false }, + { count: 1, value: ' "c": "1970-01-01T00:00:00.789Z"\n', added: false, removed: true }, + { count: 1, value: '}', removed: false, added: false } + ]); + }); + + it('should accept undefined keys', function() { + expect(diffJson( + {a: 123, b: 456, c: null}, + {a: 123, b: 456} + )).to.eql([ + { count: 3, value: '{\n "a": 123,\n "b": 456,\n', removed: false, added: false }, + { count: 1, value: ' "c": null\n', added: false, removed: true }, + { count: 1, value: '}', removed: false, added: false } + ]); + expect(diffJson( + {a: 123, b: 456, c: undefined}, + {a: 123, b: 456} + )).to.eql([ + { count: 4, value: '{\n "a": 123,\n "b": 456\n}', removed: false, added: false } + ]); + expect(diffJson( + {a: 123, b: 456, c: undefined}, + {a: 123, b: 456}, + {undefinedReplacement: null} + )).to.eql([ + { count: 3, value: '{\n "a": 123,\n "b": 456,\n', removed: false, added: false }, + { count: 1, value: ' "c": null\n', added: false, removed: true }, + { count: 1, value: '}', removed: false, added: false } ]); }); @@ -42,9 +83,9 @@ describe('diff/json', function() { JSON.stringify({a: 123, b: 456, c: 789}, undefined, ' '), JSON.stringify({a: 123, b: 456}, undefined, ' ') )).to.eql([ - { count: 3, value: '{\n "a": 123,\n "b": 456,\n' }, - { count: 1, value: ' "c": 789\n', added: undefined, removed: true }, - { count: 1, value: '}' } + { count: 3, value: '{\n "a": 123,\n "b": 456,\n', removed: false, added: false }, + { count: 1, value: ' "c": 789\n', added: false, removed: true }, + { count: 1, value: '}', removed: false, added: false } ]); }); @@ -76,35 +117,155 @@ describe('diff/json', function() { describe('#canonicalize', function() { it('should put the keys in canonical order', function() { - expect(getKeys(canonicalize({b: 456, a: 123}))).to.eql(['a', 'b']); + expect(Object.keys(canonicalize({b: 456, a: 123}))).to.eql(['a', 'b']); }); it('should dive into nested objects', function() { const canonicalObj = canonicalize({b: 456, a: {d: 123, c: 456}}); - expect(getKeys(canonicalObj.a)).to.eql(['c', 'd']); + expect(Object.keys(canonicalObj.a)).to.eql(['c', 'd']); }); it('should dive into nested arrays', function() { const canonicalObj = canonicalize({b: 456, a: [789, {d: 123, c: 456}]}); - expect(getKeys(canonicalObj.a[1])).to.eql(['c', 'd']); + expect(Object.keys(canonicalObj.a[1])).to.eql(['c', 'd']); }); it('should handle circular references correctly', function() { const obj = {b: 456}; obj.a = obj; const canonicalObj = canonicalize(obj); - expect(getKeys(canonicalObj)).to.eql(['a', 'b']); - expect(getKeys(canonicalObj.a)).to.eql(['a', 'b']); + expect(Object.keys(canonicalObj)).to.eql(['a', 'b']); + expect(Object.keys(canonicalObj.a)).to.eql(['a', 'b']); + }); + + it('should accept a custom JSON.stringify() replacer function', function() { + expect(diffJson( + {a: 123}, + {a: /foo/} + )).to.eql([ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": {}\n', added: true, removed: false }, + { count: 1, value: '}', removed: false, added: false } + ]); + + expect(diffJson( + {a: 123}, + {a: /foo/gi}, + {stringifyReplacer: (k, v) => v instanceof RegExp ? v.toString() : v} + )).to.eql([ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": "/foo/gi"\n', added: true, removed: false }, + { count: 1, value: '}', removed: false, added: false } + ]); + + expect(diffJson( + {a: 123}, + {a: new Error('ohaider')}, + {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} + )).to.eql([ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": "Error: ohaider"\n', added: true, removed: false }, + { count: 1, value: '}', removed: false, added: false } + ]); + + expect(diffJson( + {a: 123}, + {a: [new Error('ohaider')]}, + {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} + )).to.eql([ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 3, value: ' "a": [\n "Error: ohaider"\n ]\n', added: true, removed: false }, + { count: 1, value: '}', removed: false, added: false } + ]); + }); + + it('should only run each value through stringifyReplacer once', function() { + expect( + diffJson( + {foo: '123ab'}, + {foo: '123xy'}, + {stringifyReplacer: (k, v) => typeof v === 'string' ? v.slice(0, v.length - 1) : v} + ) + ).to.deep.equal( + [ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "foo": "123a"\n', added: false, removed: true }, + { count: 1, value: ' "foo": "123x"\n', added: true, removed: false }, + { count: 1, value: '}', removed: false, added: false } + ] + ); + }); + + it("should pass the same 'key' values to the replacer as JSON.stringify would", function() { + const calls = [], + obj1 = {a: ['q', 'r', 's', {t: []}]}, + obj2 = {a: ['x', 'y', 'z', {bla: []}]}; + diffJson( + obj1, + obj2, + {stringifyReplacer: (k, v) => { + calls.push([k, v]); + return v; + }} + ); + + // We run the same objects through JSON.stringify just to make unambiguous when reading this + // test that we're checking for the same key/value pairs that JSON.stringify would pass to + // the replacer. + const jsonStringifyCalls = []; + JSON.stringify( + obj1, + (k, v) => { + jsonStringifyCalls.push([k, v]); + return v; + } + ); + JSON.stringify( + obj2, + (k, v) => { + jsonStringifyCalls.push([k, v]); + return v; + } + ); + + expect(jsonStringifyCalls).to.deep.equal([ + ['', {a: ['q', 'r', 's', {t: []}]}], + ['a', ['q', 'r', 's', {t: []}]], + ['0', 'q'], + ['1', 'r'], + ['2', 's'], + ['3', {t: []}], + ['t', []], + ['', {a: ['x', 'y', 'z', {bla: []}]}], + ['a', ['x', 'y', 'z', {bla: []}]], + ['0', 'x'], + ['1', 'y'], + ['2', 'z'], + ['3', {bla: []}], + ['bla', []] + ]); + + expect(calls).to.deep.equal(jsonStringifyCalls); + }); + + it("doesn't throw on Object.create(null)", function() { + let diff; + expect(function() { + diff = diffJson( + Object.assign(Object.create(null), {a: 123}), + {b: 456} + ); + }).not.to['throw'](); + expect(diff).to.eql([ + { count: 1, value: '{\n', removed: false, added: false }, + { count: 1, value: ' "a": 123\n', removed: true, added: false }, + { count: 1, value: ' "b": 456\n', removed: false, added: true }, + { count: 1, value: '}', removed: false, added: false } + ]); }); }); }); - -function getKeys(obj) { - const keys = []; - for (let key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys; -} diff --git a/test/diff/line.js b/test/diff/line.js index 5c2c76578..cdaf08d60 100644 --- a/test/diff/line.js +++ b/test/diff/line.js @@ -1,5 +1,5 @@ -import {diffLines, diffTrimmedLines} from '../../lib/diff/line'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffLines, diffTrimmedLines} from '../../libesm/diff/line.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; @@ -12,7 +12,7 @@ describe('diff/line', function() { 'line\nnew value\nline'); expect(convertChangesToXML(diffResult)).to.equal('line\nold value\nnew value\nline'); }); - it('should the same lines in diff', function() { + it('should treat identical lines as equal', function() { const diffResult = diffLines( 'line\nvalue\nline', 'line\nvalue\nline'); @@ -46,6 +46,78 @@ describe('diff/line', function() { ''); expect(convertChangesToXML(diffResult)).to.equal('line\n\nold value \n\nline'); }); + + it('Should prefer to do deletions before insertions, like Unix diff does', function() { + const diffResult = diffLines('a\nb\nc\nd\n', 'a\nc\nb\nd\n'); + + // There are two possible diffs with equal edit distance here; either we + // can delete the "b" and insert it again later, or we can insert a "c" + // before the "b" and then delete the original "c" later. + // For consistency with the convention of other diff tools, we want to + // prefer the diff where we delete and then later insert over the one + // where we insert and then later delete. + expect(convertChangesToXML(diffResult)).to.equal('a\nb\nc\nb\nd\n'); + + const diffResult2 = diffLines('a\nc\nb\nd\n', 'a\nb\nc\nd\n'); + expect(convertChangesToXML(diffResult2)).to.equal('a\nc\nb\nc\nd\n'); + }); + + describe('given options.maxEditLength', function() { + it('terminates early', function() { + const diffResult = diffLines( + 'line\nold value\nline', + 'line\nnew value\nline', { maxEditLength: 1 }); + expect(diffResult).to.be.undefined; + }); + it('terminates early - async', function(done) { + function callback(diffResult) { + expect(diffResult).to.be.undefined; + done(); + } + diffLines( + 'line\nold value\nline', + 'line\nnew value\nline', { callback, maxEditLength: 1 }); + }); + }); + + describe('given options.maxEditLength === 0', function() { + it('returns normally if the strings are identical', function() { + const diffResult = diffLines( + 'foo\nbar\nbaz\nqux\n', + 'foo\nbar\nbaz\nqux\n', + { maxEditLength: 0 } + ); + expect(convertChangesToXML(diffResult)).to.equal('foo\nbar\nbaz\nqux\n'); + }); + + it('terminates early if there is even a single change', function() { + const diffResult = diffLines( + 'foo\nbar\nbaz\nqux\n', + 'fox\nbar\nbaz\nqux\n', + { maxEditLength: 0 } + ); + expect(diffResult).to.be.undefined; + }); + }); + }); + + describe('oneChangePerToken option', function() { + it('emits one change per line', function() { + const diffResult = diffLines( + 'foo\nbar\nbaz\nqux\n', + 'fox\nbar\nbaz\nqux\n', + { oneChangePerToken: true } + ); + expect(diffResult).to.deep.equal( + [ + {value: 'foo\n', count: 1, added: false, removed: true}, + {value: 'fox\n', count: 1, added: true, removed: false}, + {value: 'bar\n', count: 1, added: false, removed: false}, + {value: 'baz\n', count: 1, added: false, removed: false}, + {value: 'qux\n', count: 1, added: false, removed: false} + ] + ); + }); }); // Trimmed Line Diff @@ -56,7 +128,7 @@ describe('diff/line', function() { 'line\nnew value\nline'); expect(convertChangesToXML(diffResult)).to.equal('line\nold value\nnew value\nline'); }); - it('should the same lines in diff', function() { + it('should treat identical lines as equal', function() { const diffResult = diffTrimmedLines( 'line\nvalue\nline', 'line\nvalue\nline'); @@ -64,28 +136,135 @@ describe('diff/line', function() { }); it('should ignore leading and trailing whitespace', function() { - const diffResult = diffTrimmedLines( + const diffResult1 = diffTrimmedLines( 'line\nvalue \nline', 'line\nvalue\nline'); - expect(convertChangesToXML(diffResult)).to.equal('line\nvalue\nline'); + expect(convertChangesToXML(diffResult1)).to.equal('line\nvalue\nline'); + + const diffResult2 = diffTrimmedLines( + 'line\nvalue\nline', + 'line\nvalue \nline'); + expect(convertChangesToXML(diffResult2)).to.equal('line\nvalue \nline'); + + const diffResult3 = diffTrimmedLines( + 'line\n value\nline', + 'line\nvalue\nline'); + expect(convertChangesToXML(diffResult3)).to.equal('line\nvalue\nline'); + + const diffResult4 = diffTrimmedLines( + 'line\nvalue\nline', + 'line\n value\nline'); + expect(convertChangesToXML(diffResult4)).to.equal('line\n value\nline'); + }); + + it('should not ignore the insertion or deletion of lines of whitespace at the end', function() { + const finalChange = diffLines('foo\nbar\n', 'foo\nbar\n \n \n \n', {ignoreWhitespace: true}).pop(); + expect(finalChange.count).to.equal(3); + expect(finalChange.added).to.equal(true); + + const finalChange2 = diffLines('foo\nbar\n\n', 'foo\nbar\n', {ignoreWhitespace: true}).pop(); + expect(finalChange2.removed).to.equal(true); + }); + + it('should keep leading and trailing whitespace in the output', function() { + function stringify(value) { + return JSON.stringify(value, null, 2); + } + const diffResult = diffTrimmedLines( + stringify([10, 20, 30]), + stringify({ data: [10, 42, 30] })); + expect(diffResult.filter(change => !change.removed).map(change => change.value).join('')).to.equal(`{ + "data": [ + 10, + 42, + 30 + ] +}`); + expect(convertChangesToXML(diffResult)).to.equal([ + '[\n', + '{\n', + ' "data": [\n', + ' 10,\n', + ' 20,\n', + ' 42,\n', + ' 30\n', + ' ]\n', + '}' + ].join('').replace(/"/g, '"')); + }); + + it('should not consider adding whitespace to an empty line an insertion', function() { + const diffResult = diffTrimmedLines('foo\n\nbar', 'foo\n \nbar'); + expect(convertChangesToXML(diffResult)).to.equal('foo\n \nbar'); }); it('should handle windows line endings', function() { const diffResult = diffTrimmedLines( 'line\r\nold value \r\nline', 'line\r\nnew value\r\nline'); - expect(convertChangesToXML(diffResult)).to.equal('line\r\nold value\r\nnew value\r\nline'); + expect(convertChangesToXML(diffResult)).to.equal('line\r\nold value \r\nnew value\r\nline'); + }); + + it('should be compatible with newlineIsToken', function() { + const diffResult = diffTrimmedLines( + 'line1\nline2\n \nline4\n \n', + 'line1\nline2\n\n\nline4\n \n', + {newlineIsToken: true} + ); + expect(convertChangesToXML(diffResult)).to.equal('line1\nline2\n \n\nline4\n \n'); + }); + + it('supports async mode by passing a function as the options argument', function(done) { + diffTrimmedLines( + 'line\r\nold value \r\nline', + 'line \r\nnew value\r\nline', + function(diffResult) { + expect(convertChangesToXML(diffResult)).to.equal( + 'line \r\nold value \r\nnew value\r\nline' + ); + done(); + } + ); }); }); describe('#diffLinesNL', function() { expect(diffLines('restaurant', 'restaurant\n', {newlineIsToken: true})).to.eql([ - {value: 'restaurant', count: 1}, - {value: '\n', count: 1, added: true, removed: undefined} + {value: 'restaurant', count: 1, added: false, removed: false}, + {value: '\n', count: 1, added: true, removed: false} ]); expect(diffLines('restaurant', 'restaurant\nhello', {newlineIsToken: true})).to.eql([ - {value: 'restaurant', count: 1}, - {value: '\nhello', count: 2, added: true, removed: undefined} + {value: 'restaurant', count: 1, added: false, removed: false}, + {value: '\nhello', count: 2, added: true, removed: false} ]); }); + + describe('Strip trailing CR', function() { + expect(diffLines('line\nline', 'line\r\nline', {stripTrailingCr: true})).to.eql([ + {value: 'line\nline', count: 2, added: false, removed: false} + ]); + }); + + it('ignores absence of newline on the last line of file wrt equality when ignoreNewlineAtEof', function() { + // Example taken directly from https://github.com/kpdecker/jsdiff/issues/324 + expect(diffLines('a\nb\nc', 'a\nb')).to.eql( + [ + { count: 1, added: false, removed: false, value: 'a\n' }, + { count: 2, added: false, removed: true, value: 'b\nc' }, + { count: 1, added: true, removed: false, value: 'b' } + ] + ); + expect(diffLines('a\nb\nc', 'a\nb', { ignoreNewlineAtEof: true })).to.eql( + [ + { count: 2, added: false, removed: false, value: 'a\nb' }, + { count: 1, added: false, removed: true, value: 'c' } + ] + ); + expect(diffLines('a\nb', 'a\nb\nc', { ignoreNewlineAtEof: true })).to.eql( + [ + { count: 2, added: false, removed: false, value: 'a\nb\n' }, + { count: 1, added: true, removed: false, value: 'c' } + ] + ); + }); }); diff --git a/test/diff/sentence.js b/test/diff/sentence.js index 81c5edc34..a1d34e0e7 100644 --- a/test/diff/sentence.js +++ b/test/diff/sentence.js @@ -1,9 +1,43 @@ -import {diffSentences} from '../../lib/diff/sentence'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffSentences, sentenceDiff} from '../../libesm/diff/sentence.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; describe('diff/sentence', function() { + describe('tokenize', function() { + it('should split on whitespace after a punctuation mark, and keep the whitespace as a token', function() { + expect(sentenceDiff.removeEmpty(sentenceDiff.tokenize(''))).to.eql([]); + + expect(sentenceDiff.removeEmpty(sentenceDiff.tokenize( + 'Foo bar baz! Qux wibbly wobbly bla? \n\tYayayaya!Yayayaya!Ya! Yes!!!!! Blub' + ))).to.eql([ + 'Foo bar baz!', + ' ', + 'Qux wibbly wobbly bla?', + ' \n\t', + 'Yayayaya!Yayayaya!Ya!', + ' ', + 'Yes!!!!!', + ' ', + 'Blub' + ]); + + expect(sentenceDiff.removeEmpty(sentenceDiff.tokenize( + '! Hello there.' + ))).to.eql([ + '!', + ' ', + 'Hello there.' + ]); + + expect(sentenceDiff.removeEmpty(sentenceDiff.tokenize( + ' foo bar baz.' + ))).to.eql([ + ' foo bar baz.' + ]); + }); + }); + describe('#diffSentences', function() { it('Should diff Sentences', function() { const diffResult = diffSentences('New Value.', 'New ValueMoreData.'); diff --git a/test/diff/word.js b/test/diff/word.js index ebac87cca..4f90a7e59 100644 --- a/test/diff/word.js +++ b/test/diff/word.js @@ -1,33 +1,257 @@ -import {wordDiff, diffWords, diffWordsWithSpace} from '../../lib/diff/word'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {wordDiff, diffWords, diffWordsWithSpace} from '../../libesm/diff/word.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; describe('WordDiff', function() { - describe('#diffWords', function() { - it('should diff whitespace', function() { - const diffResult = diffWords('New Value', 'New ValueMoreData'); - expect(convertChangesToXML(diffResult)).to.equal('New ValueValueMoreData'); + describe('#tokenize', function() { + it('should give each word & punctuation mark its own token, including leading and trailing whitespace', function() { + const string = 'foo bar baz jurídica wir üben bla\t\t \txyzáxyz \n\n\n animá-los\r\n\r\n(wibbly wobbly)().'; + const expectedResult = [ + 'foo ', + ' bar ', + ' baz ', + ' jurídica ', + ' wir ', + ' üben ', + ' bla\t\t \t', + '\t\t \txyzáxyz \n\n\n ', + ' \n\n\n animá', + '-', + 'los\r\n\r\n', + '\r\n\r\n(', + 'wibbly ', + ' wobbly', + ')', + '(', + ')', + '.' + ]; + expect(wordDiff.tokenize(string)).to.deep.equal(expectedResult); + expect(wordDiff.tokenize( + string, + { intlSegmenter: new Intl.Segmenter('en', { granularity: 'word' }) } + )).to.deep.equal(expectedResult); }); - it('should diff multiple whitespace values', function() { - const diffResult = diffWords('New Value ', 'New ValueMoreData '); - expect(convertChangesToXML(diffResult)).to.equal('New ValueValueMoreData '); + // Test for bug reported at https://github.com/kpdecker/jsdiff/issues/553 + it('should treat numbers as part of a word if not separated by whitespace or punctuation', () => { + expect( + wordDiff.tokenize( + 'Tea Too, also known as T2, had revenue of 57m AUD in 2012-13.' + ) + ).to.deep.equal([ + 'Tea ', + ' Too', + ', ', + ' also ', + ' known ', + ' as ', + ' T2', + ', ', + ' had ', + ' revenue ', + ' of ', + ' 57m ', + ' AUD ', + ' in ', + ' 2012', + '-', + '13', + '.' + ]); + }); + + // Test for various behaviours discussed at + // https://github.com/kpdecker/jsdiff/issues/634#issuecomment-3381707327 + // In particular we are testing that: + // 1. single code points representing accented characters (most of range + // U+00C0 thru U+00FF) are treated as word characters + // 2. soft hyphens are treated as part of the word they appear in + // 3. the multiplication and division signs are punctuation + // 4. currency signs are punctuation + // 5. section symbol is punctuation + // 6. reserved trademark symbol is punctuation + // 7. fractions are punctuation + // The behaviour being tested for in points 4 thru 7 above is of debatable + // correctness; it is not totally obvious whether we SHOULD treat those + // things as punctuation characters or as word characters. Nonetheless, we + // have this test to help document the current behaviour. + it('should handle the 0080-00FF range the way we expect', () => { + expect( + wordDiff.tokenize( + 'My daugh\u00adter, Am\u00E9lie, is 1½ years old and works for ' + + 'Google® for £6 per hour (equivalently £6÷60=£0.10 per minute, or ' + + '£6×8=£48 per day), in violation of § 123 of the Child Labour Act.' + ) + ).to.deep.equal([ + 'My ', + ' daugh\u00adter', + ', ', + ' Am\u00E9lie', + ', ', + ' is ', + ' 1', + '½ ', + ' years ', + ' old ', + ' and ', + ' works ', + ' for ', + ' Google', + '® ', + ' for ', + ' £', + '6 ', + ' per ', + ' hour ', + ' (', + 'equivalently ', + ' £', + '6', + '÷', + '60', + '=', + '£', + '0', + '.', + '10 ', + ' per ', + ' minute', + ', ', + ' or ', + ' £', + '6', + '×', + '8', + '=', + '£', + '48 ', + ' per ', + ' day', + ')', + ', ', + ' in ', + ' violation ', + ' of ', + ' § ', + ' 123 ', + ' of ', + ' the ', + ' Child ', + ' Labour ', + ' Act', + '.' + ]); + }); + }); + + describe('#diffWords', function() { + it("should ignore whitespace changes between tokens that aren't added or deleted", function() { + const diffResult = diffWords('New Value', 'New \n \t Value'); + expect(convertChangesToXML(diffResult)).to.equal('New \n \t Value'); }); - // Diff on word boundary - it('should diff on word boundaries', function() { - let diffResult = diffWords('New :Value:Test', 'New ValueMoreData '); - expect(convertChangesToXML(diffResult)).to.equal('New :Value:TestValueMoreData '); + describe('whitespace changes that border inserted/deleted tokens should be included in the diff as far as is possible...', function() { + it('(add+del at end of text)', function() { + const diffResult = diffWords('New Value ', 'New ValueMoreData '); + expect(convertChangesToXML(diffResult)).to.equal('New Value ValueMoreData '); + }); + + it('(add+del in middle of text)', function() { + const diffResult = diffWords('New Value End', 'New ValueMoreData End'); + expect(convertChangesToXML(diffResult)).to.equal('New Value ValueMoreData End'); + }); + + it('(add+del at start of text)', function() { + const diffResult = diffWords('\tValue End', ' ValueMoreData End'); + expect(convertChangesToXML(diffResult)).to.equal('\tValue ValueMoreData End'); + }); + + it('(add at start of text)', function() { + const diffResult = diffWords('\t Value', 'More Value'); + // Preferable would be: + // 'More Value' + // But this is hard to achieve without adding something like the + // .oldValue property I contemplate in + // https://github.com/kpdecker/jsdiff/pull/219#issuecomment-1858246490 + // to change objects returned by the base diffing algorithm. The CO + // cleanup done by diffWords simply doesn't have enough information to + // return the ideal result otherwise. + expect(convertChangesToXML(diffResult)).to.equal('More Value'); + }); + + it('(del at start of text)', function() { + const diffResult = diffWords('More Value', '\t Value'); + expect(convertChangesToXML(diffResult)).to.equal('More \t Value'); + }); + + it('(add in middle of text)', function() { + const diffResult = diffWords('Even Value', 'Even More Value'); + // Preferable would be: + // 'Even More Value' + // But this is hard to achieve without adding something like the + // .oldValue property I contemplate in + // https://github.com/kpdecker/jsdiff/pull/219#issuecomment-1858246490 + // to change objects returned by the base diffing algorithm. The CO + // cleanup done by diffWords simply doesn't have enough information to + // return the ideal result otherwise. + expect(convertChangesToXML(diffResult)).to.equal('Even More Value'); + }); + + it('(del in middle of text)', function() { + const diffResult = diffWords('Even More Value', 'Even Value'); + expect(convertChangesToXML(diffResult)).to.equal('Even More Value'); + + // Rules around how to split up the whitespace between the start and + // end "keep" change objects are subtle, as shown by the three examples + // below: + const diffResult2 = diffWords('foo\nbar baz', 'foo baz'); + expect(convertChangesToXML(diffResult2)).to.equal('foo\nbar baz'); + + const diffResult3 = diffWords('foo bar baz', 'foo baz'); + expect(convertChangesToXML(diffResult3)).to.equal('foo bar baz'); + + const diffResult4 = diffWords('foo\nbar baz', 'foo\n baz'); + expect(convertChangesToXML(diffResult4)).to.equal('foo\nbar baz'); + }); + + it('(add at end of text)', function() { + const diffResult = diffWords('Foo\n', 'Foo Bar\n'); + // Preferable would be: + // 'Foo Bar\n' + // But this is hard to achieve without adding something like the + // .oldValue property I contemplate in + // https://github.com/kpdecker/jsdiff/pull/219#issuecomment-1858246490 + // to change objects returned by the base diffing algorithm. The CO + // cleanup done by diffWords simply doesn't have enough information to + // return the ideal result otherwise. + expect(convertChangesToXML(diffResult)).to.equal('Foo Bar\n'); + }); + + it('(del at end of text)', function() { + const diffResult = diffWords('Foo Bar', 'Foo '); + expect(convertChangesToXML(diffResult)).to.equal('Foo Bar'); + }); + }); - diffResult = diffWords('New Value:Test', 'New Value:MoreData '); - expect(convertChangesToXML(diffResult)).to.equal('New Value:TestMoreData '); + it('should skip postprocessing of change objects in one-change-object-per-token mode', function() { + const diffResult = diffWords('Foo Bar', 'Foo Baz', {oneChangePerToken: true}); + expect(convertChangesToXML(diffResult)).to.equal( + 'Foo Bar Baz' + ); + }); - diffResult = diffWords('New Value-Test', 'New Value:MoreData '); - expect(convertChangesToXML(diffResult)).to.equal('New Value-Test:MoreData '); + it('should respect options.ignoreCase', function() { + const diffResult = diffWords('foo bar baz', 'FOO BAR QUX', {ignoreCase: true}); + expect(convertChangesToXML(diffResult)).to.equal( + 'FOO BAR bazQUX' + ); + }); - diffResult = diffWords('New Value', 'New Value:MoreData '); - expect(convertChangesToXML(diffResult)).to.equal('New Value:MoreData '); + it('should treat punctuation characters as tokens', function() { + let diffResult = diffWords('New:Value:Test', 'New,Value,More,Data '); + expect(convertChangesToXML(diffResult)).to.equal('New:,Value:Test,More,Data '); }); // Diff without changes @@ -55,113 +279,179 @@ describe('WordDiff', function() { expect(convertChangesToXML(diffResult)).to.equal('New Value'); }); - // With without anchor (the Heckel algorithm error case) - it('should diff when there is no anchor value', function() { - const diffResult = diffWords('New Value New Value', 'Value Value New New'); - expect(convertChangesToXML(diffResult)).to.equal('NewValue Value New ValueNew'); - }); - - it('should token unicode characters safely', function() { - expect(wordDiff.removeEmpty(wordDiff.tokenize('jurídica'))).to.eql(['jurídica']); - expect(wordDiff.removeEmpty(wordDiff.tokenize('wir üben'))).to.eql(['wir', ' ', 'üben']); - }); - it('should include count with identity cases', function() { - expect(diffWords('foo', 'foo')).to.eql([{value: 'foo', count: 1}]); - expect(diffWords('foo bar', 'foo bar')).to.eql([{value: 'foo bar', count: 3}]); + expect(diffWords('foo', 'foo')).to.eql([{value: 'foo', count: 1, removed: false, added: false}]); + expect(diffWords('foo bar', 'foo bar')).to.eql([{value: 'foo bar', count: 2, removed: false, added: false}]); }); it('should include count with empty cases', function() { - expect(diffWords('foo', '')).to.eql([{value: 'foo', count: 1, added: undefined, removed: true}]); - expect(diffWords('foo bar', '')).to.eql([{value: 'foo bar', count: 3, added: undefined, removed: true}]); + expect(diffWords('foo', '')).to.eql([{value: 'foo', count: 1, added: false, removed: true}]); + expect(diffWords('foo bar', '')).to.eql([{value: 'foo bar', count: 2, added: false, removed: true}]); - expect(diffWords('', 'foo')).to.eql([{value: 'foo', count: 1, added: true, removed: undefined}]); - expect(diffWords('', 'foo bar')).to.eql([{value: 'foo bar', count: 3, added: true, removed: undefined}]); + expect(diffWords('', 'foo')).to.eql([{value: 'foo', count: 1, added: true, removed: false}]); + expect(diffWords('', 'foo bar')).to.eql([{value: 'foo bar', count: 2, added: true, removed: false}]); }); it('should ignore whitespace', function() { - expect(diffWords('hase igel fuchs', 'hase igel fuchs')).to.eql([{ count: 5, value: 'hase igel fuchs' }]); - expect(diffWords('hase igel fuchs', 'hase igel fuchs\n')).to.eql([{ count: 5, value: 'hase igel fuchs\n' }]); - expect(diffWords('hase igel fuchs\n', 'hase igel fuchs')).to.eql([{ count: 5, value: 'hase igel fuchs\n' }]); - expect(diffWords('hase igel fuchs', 'hase igel\nfuchs')).to.eql([{ count: 5, value: 'hase igel\nfuchs' }]); - expect(diffWords('hase igel\nfuchs', 'hase igel fuchs')).to.eql([{ count: 5, value: 'hase igel fuchs' }]); + expect(diffWords('hase igel fuchs', 'hase igel fuchs')).to.eql([{ count: 3, value: 'hase igel fuchs', removed: false, added: false }]); + expect(diffWords('hase igel fuchs', 'hase igel fuchs\n')).to.eql([{ count: 3, value: 'hase igel fuchs\n', removed: false, added: false }]); + expect(diffWords('hase igel fuchs\n', 'hase igel fuchs')).to.eql([{ count: 3, value: 'hase igel fuchs', removed: false, added: false }]); + expect(diffWords('hase igel fuchs', 'hase igel\nfuchs')).to.eql([{ count: 3, value: 'hase igel\nfuchs', removed: false, added: false }]); + expect(diffWords('hase igel\nfuchs', 'hase igel fuchs')).to.eql([{ count: 3, value: 'hase igel fuchs', removed: false, added: false }]); }); - it('should diff whitespace with flag', function() { - const diffResult = diffWords('New Value', 'New ValueMoreData', {ignoreWhitespace: false}); - expect(convertChangesToXML(diffResult)).to.equal('New Value ValueMoreData'); - }); - }); + it('should diff with only whitespace', function() { + let diffResult = diffWords('', ' '); + expect(convertChangesToXML(diffResult)).to.equal(' '); - describe('#diffWords - async', function() { - it('should diff whitespace', function(done) { - diffWords('New Value', 'New ValueMoreData', function(err, diffResult) { - expect(err).to.be.undefined; - expect(convertChangesToXML(diffResult)).to.equal('New ValueValueMoreData'); - done(); - }); + diffResult = diffWords(' ', ''); + expect(convertChangesToXML(diffResult)).to.equal(' '); }); - it('should diff multiple whitespace values', function(done) { - diffWords('New Value ', 'New ValueMoreData ', function(err, diffResult) { - expect(err).to.be.undefined; - expect(convertChangesToXML(diffResult)).to.equal('New ValueValueMoreData '); + it('should support async mode', function(done) { + diffWords('New Value', 'New \n \t Value', function(diffResult) { + expect(convertChangesToXML(diffResult)).to.equal('New \n \t Value'); done(); }); }); - // Diff on word boundary - it('should diff on word boundaries', function(done) { - diffWords('New :Value:Test', 'New ValueMoreData ', function(err, diffResult) { - expect(err).to.be.undefined; - expect(convertChangesToXML(diffResult)).to.equal('New :Value:TestValueMoreData '); - done(); - }); + it('calls #diffWordsWithSpace if you pass ignoreWhitespace: false', function() { + const diffResult = diffWords( + 'foo bar', + 'foo\tbar', + {ignoreWhitespace: false} + ); + expect(convertChangesToXML(diffResult)).to.equal('foo \tbar'); }); - // Diff without changes - it('should handle identity', function(done) { - diffWords('New Value', 'New Value', function(err, diffResult) { - expect(err).to.be.undefined; - expect(convertChangesToXML(diffResult)).to.equal('New Value'); - done(); - }); - }); - it('should handle empty', function(done) { - diffWords('', '', function(err, diffResult) { - expect(err).to.be.undefined; - expect(convertChangesToXML(diffResult)).to.equal(''); - done(); - }); + it('supports tokenizing with an Intl.Segmenter', () => { + // Example 1: Diffing Chinese text with no spaces. + // a. "She (她) has (有) many (很多) tables (桌子)" + // b. "Mei (梅) has (有) many (很多) sons (儿子)" + // We want to see that diffWords will get the word counts right and won't try to treat the + // trailing 子 as common to both texts (since it's part of a different word each time). + const chineseSegmenter = new Intl.Segmenter('zh', {granularity: 'word'}); + const diffResult = diffWords('她有很多桌子。', '梅有很多儿子。', {intlSegmenter: chineseSegmenter}); + expect(diffResult).to.deep.equal([ + { count: 1, added: false, removed: true, value: '她' }, + { count: 1, added: true, removed: false, value: '梅' }, + { count: 2, added: false, removed: false, value: '有很多' }, + { count: 1, added: false, removed: true, value: '桌子' }, + { count: 1, added: true, removed: false, value: '儿子' }, + { count: 1, added: false, removed: false, value: '。' } + ]); + + // Example 2: Should understand that a colon in the middle of a word is not a word break in + // Finnish (see https://stackoverflow.com/a/76402021/1709587) + const finnishSegmenter = new Intl.Segmenter('fi', {granularity: 'word'}); + expect(convertChangesToXML(diffWords( + 'USA:n nykyinen presidentti', + 'USA ja sen presidentti', + {intlSegmenter: finnishSegmenter} + ))).to.equal('USA:n nykyinenUSA ja sen presidentti'); + + // Example 3: Some English text, including contractions, long runs of arbitrary space, + // and punctuation, and using case insensitive mode, just to show all normal behaviour of + // diffWords still works with a segmenter + const englishSegmenter = new Intl.Segmenter('en', {granularity: 'word'}); + expect(convertChangesToXML(diffWords( + "There wasn't time \n \t for all that. He thought...", + "There isn't time \n \t left for all that, he thinks.", + {intlSegmenter: englishSegmenter, ignoreCase: true} + ))).to.equal( + "There wasn'tisn't time \n \t left " + + 'for all that., he thoughtthinks...' + ); }); - it('should diff has identical content', function(done) { - diffWords('New Value', 'New Value', function(err, diffResult) { - expect(err).to.be.undefined; - expect(convertChangesToXML(diffResult)).to.equal('New Value'); - done(); - }); + + it('rejects attempts to use a non-word Intl.Segmenter', () => { + const segmenter = new Intl.Segmenter('en', {granularity: 'grapheme'}); + expect(() => { + diffWords('foo', 'bar', {intlSegmenter: segmenter}); + }).to['throw']('The segmenter passed must have a granularity of "word"'); }); - // Empty diffs - it('should diff empty new content', function(done) { - diffWords('New Value', '', function(err, diffResult) { - expect(diffResult.length).to.equal(1); - expect(convertChangesToXML(diffResult)).to.equal('New Value'); - done(); - }); + it("doesn't blow up when using an Intl.Segmenter on a text with a double newline", () => { + // Regression test for https://github.com/kpdecker/jsdiff/issues/630 + const englishSegmenter = new Intl.Segmenter('en', {granularity: 'word'}); + expect(convertChangesToXML(diffWords( + 'A\n\nX', + 'B\n\nX', + {intlSegmenter: englishSegmenter} + ))).to.equal( + 'AB\n\nX' + ); }); - it('should diff empty old content', function(done) { - diffWords('', 'New Value', function(err, diffResult) { - expect(convertChangesToXML(diffResult)).to.equal('New Value'); - done(); - }); + + it('handles diacritics on whitespace differently in Segmenter mode vs normal mode', () => { + // Regression test for https://github.com/kpdecker/jsdiff/issues/664 + const oldString = 'abc \u0300X def'; + const newString = 'abc \u0300Y ghi'; + + expect(diffWords(oldString, newString)).to.deep.equal([ + { + count: 2, + added: false, + removed: false, + value: 'abc \u0300' + }, + { + count: 2, + added: false, + removed: true, + value: 'X def' + }, + { + count: 2, + added: true, + removed: false, + value: 'Y ghi' + } + ]); + + expect(diffWords(oldString, newString, { intlSegmenter: new Intl.Segmenter('en', { granularity: 'word' }) })).to.deep.equal([ + { + // Note this is ONE token in segmenter mode, because ' \u0300' is + // considered pure whitespace + count: 1, + added: false, + removed: false, + value: 'abc \u0300' + }, + { + count: 2, + added: false, + removed: true, + value: 'X def' + }, + { + count: 2, + added: true, + removed: false, + value: 'Y ghi' + } + ]); }); - // With without anchor (the Heckel algorithm error case) - it('should diff when there is no anchor value', function(done) { - diffWords('New Value New Value', 'Value Value New New', function(err, diffResult) { - expect(convertChangesToXML(diffResult)).to.equal('NewValue Value New ValueNew'); - done(); - }); + it('handles orphaned diacritics after newlines acceptably', () => { + // Oddly enough, an Intl.Segmenter in word mode seems to think that + // diacritics can modify spaces, but not newlines. So a diacritic + // modifier character after a newline is always a standalone segment. + // This test sanity-checks that we behave reasonably when encountering + // such segments. + expect( + diffWords( + 'abc \n\u0300 X \n\u0300def', + 'abc \n\u0300 Y def', + { intlSegmenter: new Intl.Segmenter('en', { granularity: 'word' }) } + ) + ).to.deep.equal( + [ + { count: 2, added: false, removed: false, value: 'abc \ǹ ' }, + { count: 2, added: false, removed: true, value: 'X \ǹ' }, + { count: 1, added: true, removed: false, value: 'Y ' }, + { count: 1, added: false, removed: false, value: 'def' } + ] + ); }); }); @@ -173,14 +463,76 @@ describe('WordDiff', function() { it('should diff multiple whitespace values', function() { const diffResult = diffWordsWithSpace('New Value ', 'New ValueMoreData '); - expect(convertChangesToXML(diffResult)).to.equal('New ValueMoreData Value '); + expect(convertChangesToXML(diffResult)).to.equal('New Value ValueMoreData '); + }); + + it('should inserts values in parenthesis', function() { + const diffResult = diffWordsWithSpace('()', '(word)'); + expect(convertChangesToXML(diffResult)).to.equal('(word)'); + }); + + it('should inserts values in brackets', function() { + const diffResult = diffWordsWithSpace('[]', '[word]'); + expect(convertChangesToXML(diffResult)).to.equal('[word]'); + }); + + it('should inserts values in curly braces', function() { + const diffResult = diffWordsWithSpace('{}', '{word}'); + expect(convertChangesToXML(diffResult)).to.equal('{word}'); + }); + + it('should inserts values in quotes', function() { + const diffResult = diffWordsWithSpace("''", "'word'"); + expect(convertChangesToXML(diffResult)).to.equal("'word'"); + }); + + it('should inserts values in double quotes', function() { + const diffResult = diffWordsWithSpace('""', '"word"'); + expect(convertChangesToXML(diffResult)).to.equal('"word"'); + }); + + it('should treat newline as separate token (issues #180, #211)', function() { + // #180 + const diffResult1 = diffWordsWithSpace('foo\nbar', 'foo\n\n\nbar'); + expect(convertChangesToXML(diffResult1)).to.equal('foo\n\n\nbar'); + // #211 + const diffResult2 = diffWordsWithSpace('A\n\nB\n', 'A\nB\n'); + expect(convertChangesToXML(diffResult2)).to.equal('A\n\nB\n'); + // Windows-style newlines should also get a single token + const diffResult3 = diffWordsWithSpace('foo\r\nbar', 'foo \r\n\r\n\r\nbar'); + expect(convertChangesToXML(diffResult3)).to.equal('foo \r\n\r\n\r\nbar'); + const diffResult4 = diffWordsWithSpace('A\r\n\r\nB\r\n', 'A\r\nB\r\n'); + expect(convertChangesToXML(diffResult4)).to.equal('A\r\n\r\nB\r\n'); }); it('should perform async operations', function(done) { - diffWordsWithSpace('New Value ', 'New ValueMoreData ', function(err, diffResult) { - expect(convertChangesToXML(diffResult)).to.equal('New ValueMoreData Value '); + diffWordsWithSpace('New Value ', 'New ValueMoreData ', function(diffResult) { + expect(convertChangesToXML(diffResult)).to.equal('New Value ValueMoreData '); done(); }); }); + + // With without anchor (the Heckel algorithm error case) + it('should diff when there is no anchor value', function() { + const diffResult = diffWordsWithSpace('New Value New Value', 'Value Value New New'); + expect(convertChangesToXML(diffResult)).to.equal('NewValue Value New ValueNew'); + }); + + it('should handle empty', function() { + const diffResult = diffWordsWithSpace('', ''); + expect(convertChangesToXML(diffResult)).to.equal(''); + }); + + describe('case insensitivity', function() { + it("is considered when there's a difference", function() { + const diffResult = diffWordsWithSpace('new value', 'New ValueMoreData', {ignoreCase: true}); + expect(convertChangesToXML(diffResult)).to.equal('New value ValueMoreData'); + }); + + it("is considered when there's no difference", function() { + const diffResult = diffWordsWithSpace('new value', 'New Value', {ignoreCase: true}); + expect(convertChangesToXML(diffResult)).to.equal('New Value'); + }); + }); }); }); diff --git a/test/index.js b/test/index.js index 42ad2d975..c125cfe99 100644 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -import * as Diff from '../lib'; +import * as Diff from 'diff'; import {expect} from 'chai'; @@ -16,6 +16,8 @@ describe('root exports', function() { expect(Diff.diffCss).to.exist; expect(Diff.diffJson).to.exist; + expect(Diff.diffArrays).to.exist; + expect(Diff.structuredPatch).to.exist; expect(Diff.createTwoFilesPatch).to.exist; expect(Diff.createPatch).to.exist; diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index d03dc610f..000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---require babel/register ---reporter spec diff --git a/test/patch/apply.js b/test/patch/apply.js old mode 100644 new mode 100755 index d15d8d0fd..c789488ec --- a/test/patch/apply.js +++ b/test/patch/apply.js @@ -1,13 +1,14 @@ -import {applyPatch, applyPatches} from '../../lib/patch/apply'; -import {parsePatch} from '../../lib/patch/parse'; -import {createPatch} from '../../lib/patch/create'; +import {applyPatch, applyPatches} from '../../libesm/patch/apply.js'; +import {parsePatch} from '../../libesm/patch/parse.js'; +import {createPatch} from '../../libesm/patch/create.js'; +import {structuredPatch} from '../../libesm/patch/create.js'; import {expect} from 'chai'; describe('patch/apply', function() { describe('#applyPatch', function() { it('should accept parsed patches', function() { - let patch = parsePatch( + const patch = parsePatch( 'Index: test\n' + '===================================================================\n' + '--- test\theader1\n' @@ -180,10 +181,26 @@ describe('patch/apply', function() { + ' line4\n' + ' line4\n')) .to.equal('line1\nline2\nline3\nline4\nline4\nline4\nline4'); + + // Test empty lines in patches + expect(applyPatch( + 'line11\nline2\n\nline4', + + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + '+line1\n' + + '-line11\n' + + ' line2\n' + + '\n' + + ' line4\n' + + '\\ No newline at end of file\n')) + .to.equal('line1\nline2\n\nline4'); }); it('should apply patches', function() { - // Create patch const oldFile = 'value\n' + 'context\n' @@ -259,7 +276,7 @@ describe('patch/apply', function() { + 'new value 2\n' + 'context\n' + 'context'; - let diffFile = + const diffFile = 'Index: testFileName\n' + '===================================================================\n' + '--- testFileName\tOld Header\n' @@ -310,12 +327,12 @@ describe('patch/apply', function() { expect(applyPatch(oldFile, diffFile)).to.equal(newFile); - diffFile = + const identityFile = 'Index: testFileName\n' + '===================================================================\n' + '--- testFileName\tOld Header\n' + '+++ testFileName\tNew Header\n'; - expect(applyPatch(oldFile, diffFile)).to.equal(oldFile); + expect(applyPatch(oldFile, identityFile)).to.equal(oldFile); }); it('should apply patches that lack an index header', function() { @@ -338,6 +355,160 @@ describe('patch/apply', function() { + 'line5\n'); }); + it('should apply single line patches with zero context and zero removed', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2,0 +3 @@\n' + + '+line4\n')) + .to.equal( + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('should apply multiline patches with zero context and zero removed', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n' + + 'line7\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2,0 +3,3 @@\n' + + '+line4\n' + + '+line5\n' + + '+line6\n')) + .to.equal( + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n' + + 'line6\n' + + 'line7\n'); + }); + + it('should apply single line patches with zero context and zero removed at start of file', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -0,0 +1 @@\n' + + '+line1\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n'); + }); + + it('should apply multi line patches with zero context and zero removed at start of file', function() { + expect(applyPatch( + 'line3\n' + + 'line4\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -0,0 +1,2 @@\n' + + '+line1\n' + + '+line2\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n'); + }); + + it('should apply multi line patches with zero context and zero removed at end of file', function() { + expect(applyPatch( + 'line1\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,0 +2 @@\n' + + '+line2\n')) + .to.equal( + 'line1\n' + + 'line2\n'); + }); + + it('should apply multi line patches with zero context and zero removed at end of file', function() { + expect(applyPatch( + 'line1\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,0 +2,2 @@\n' + + '+line2\n' + + '+line3\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n'); + }); + + it('should apply single line patches with zero context and zero added at beginning of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1 +0,0 @@\n' + + '-line1\n')) + .to.equal( + 'line2\n'); + }); + + it('should apply multi line patches with zero context and zero added at beginning of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +0,0 @@\n' + + '-line1\n' + + '-line2\n')) + .to.equal( + 'line3\n'); + }); + + it('should apply single line patches with zero context and zero added at end of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2 +1,0 @@\n' + + '-line2\n')) + .to.equal( + 'line1\n'); + }); + + it('should apply multi line patches with zero context and zero added at end of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2,2 +1,0 @@\n' + + '-line2\n' + + '-line3\n')) + .to.equal( + 'line1\n'); + }); + it('should fail on mismatch', function() { expect(applyPatch( 'line2\n' @@ -353,27 +524,816 @@ describe('patch/apply', function() { + ' line5\n')) .to.equal(false); }); - it('should succeed within fuzz factor', function() { + + it("should fail if a line to delete doesn't match, even with fuzz factor", function() { + const patch = 'Index: foo.txt\n' + + '===================================================================\n' + + '--- foo.txt\n' + + '+++ foo.txt\n' + + '@@ -1,4 +1,3 @@\n' + + ' foo\n' + + '-bar\n' + + ' baz\n' + + ' qux\n'; + + // Sanity-check - patch should apply fine to this: + const result1 = applyPatch('foo\nbar\nbaz\nqux\n', patch, {fuzzFactor: 99}); + expect(result1).to.equal('foo\nbaz\nqux\n'); + + // ... but not to this: + const result2 = applyPatch('foo\nSOMETHING ENTIRELY DIFFERENT\nbaz\nqux\n', patch, {fuzzFactor: 99}); + expect(result2).to.equal(false); + }); + + it("should fail if either line immediately next to an insertion doesn't match, regardless of fuzz factor", function() { expect(applyPatch( - 'line2\n' + 'lineA\n' + + 'lineB\n' + + 'lineC\n' + + 'lineD\n' + + 'lineE\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,5 +1,6 @@\n' + + ' lineA\n' + + ' lineB\n' + + ' lineC\n' + + '+lineNEW\n' + + ' lineX\n' + + ' lineE\n', + {fuzzFactor: 10})) + .to.equal(false); + + expect(applyPatch( + 'lineA\n' + + 'lineB\n' + + 'lineC\n' + + 'lineD\n' + + 'lineE\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,5 +1,6 @@\n' + + ' lineA\n' + + ' lineB\n' + + ' lineX\n' + + '+lineNEW\n' + + ' lineD\n' + + ' lineE\n', + {fuzzFactor: 10})) + .to.equal(false); + }); + + it('should, given a fuzz factor, allow mismatches caused by presence of extra lines', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line2.5\n' + + 'line3\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'line8\n' + + 'line8.5\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 2} + )).to.equal( + 'line1\n' + + 'line2\n' + + 'line2.5\n' + + 'line3\n' + + 'line4\n' + + 'line5\n' + + 'line6\n' + + 'line7\n' + + 'line8\n' + + 'line8.5\n' + + 'line10\n' + ); + }); + + it('should, given a fuzz factor, allow mismatches due to missing lines', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 2} + )).to.equal( + 'line1\n' + + 'line2\n' + + 'line4\n' + + 'line5\n' + + 'line6\n' + + 'line7\n' + + 'line10\n' + ); + }); + + it('should, given a fuzz factor, allow mismatches caused by lines being changed', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'lineTHREE\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'lineEIGHT\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 2} + )).to.equal( + 'line1\n' + + 'line2\n' + + 'lineTHREE\n' + + 'line4\n' + + 'line5\n' + + 'line6\n' + + 'line7\n' + + 'lineEIGHT\n' + + 'line10\n' + ); + }); + + it('should, given a fuzz factor, allow mismatches caused by a mixture of ins/sub/del', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line2.5\n' + + 'lineTHREE\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 3} + )).to.equal( + 'line1\n' + + 'line2\n' + + 'line2.5\n' + + 'lineTHREE\n' + + 'line4\n' + + 'line5\n' + + 'line6\n' + + 'line7\n' + + 'line10\n' + ); + }); + + it('should fail if number of lines of context mismatch is greater than fuzz factor', function() { + // 3 extra lines of context, but fuzzFactor: 2 + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line2.5\n' + + 'line3\n' + + 'line4\n' + + 'line6\n' + + 'line6.5\n' + + 'line7\n' + + 'line8\n' + + 'line8.5\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 2} + )).to.equal( + false + ); + + // 2 lines of context missing from file to patch, fuzz factor 1 + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 1} + )).to.equal(false); + + // 3 changed context lines, but fuzzFactor of 2 + expect(applyPatch( + 'line1\n' + + 'lineTWO\n' + + 'lineTHREE\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'lineEIGHT\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 2} + )).to.equal(false); + + // 3 total changes, fuzzFactor 2 + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line2.5\n' + + 'lineTHREE\n' + + 'line4\n' + + 'line6\n' + + 'line7\n' + + 'line9\n' + + 'line10\n', + + '--- foo.txt\t2024-07-19 09:58:02.489059795 +0100\n' + + '+++ bar.txt\t2024-07-19 09:58:24.768153252 +0100\n' + + '@@ -2,8 +2,8 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n' + + ' line6\n' + + ' line7\n' + + ' line8\n' + + '-line9\n' + + ' line10\n', + + {fuzzFactor: 2} + )).to.equal(false); + }); + + it('should adjust where it starts looking to apply the hunk based on offsets of prior hunks', function() { + const patch = '--- foo.txt\t2024-07-19 12:28:25.056182029 +0100\n' + + '+++ bar.txt\t2024-07-19 12:28:13.036639136 +0100\n' + + '@@ -9,7 +9,6 @@\n' + + ' 1 2 3 introductory text\n' + + ' Baa oink moo introductory text\n' + + ' Probably enough introductory text\n' + + '-Incy wincy mincy introductory text\n' + + ' \n' + + ' Three repeated verses:\n' + + ' \n' + + '@@ -28,7 +27,7 @@\n' + + ' The wind came along and blew them in again\n' + + ' Poor old Michael Finnegan, begin again\n' + + ' \n' + + '-There was an old man named Michael Finnegan\n' + + '+There was an old man named Bob\n' + + ' He had whiskers on his chinnegan\n' + + ' The wind came along and blew them in again\n' + + ' Poor old Michael Finnegan, begin again\n'; + + + // First we try applying the text to the original text I used to generate the patch. + // The patch was generated by modifying the fourth of the six occurrences of the repeated + // verse, and that's what we should see when we apply it... + expect(applyPatch( + 'Bla bla bla introductory text\n' + + 'Foo bar baz introductory text\n' + + 'Fworble worble glorble introductory text\n' + + 'Need to be at least 6 lines of introductory text\n' + + 'Jingle jangle jungle introductory text\n' + + 'Horgle worgle borgle introductory text\n' + + 'Wiggly jiggly piggly introductory text\n' + + 'A B C introductory text\n' + + '1 2 3 introductory text\n' + + 'Baa oink moo introductory text\n' + + 'Probably enough introductory text\n' + + 'Incy wincy mincy introductory text\n' + + '\n' + + 'Three repeated verses:\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n', + + patch + )).to.equal( + 'Bla bla bla introductory text\n' + + 'Foo bar baz introductory text\n' + + 'Fworble worble glorble introductory text\n' + + 'Need to be at least 6 lines of introductory text\n' + + 'Jingle jangle jungle introductory text\n' + + 'Horgle worgle borgle introductory text\n' + + 'Wiggly jiggly piggly introductory text\n' + + 'A B C introductory text\n' + + '1 2 3 introductory text\n' + + 'Baa oink moo introductory text\n' + + 'Probably enough introductory text\n' + + '\n' + + 'Three repeated verses:\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Bob\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + ); + + // But what if we apply the patch to a source file where the first 5 lines are deleted? + // Then we expect applyPatch to still modify the fourth occurrence of the repeated verse, + // NOT the fifth (which is now the one at the line number indicated by the hunk header). This + // is because it should be able to tell when it applied the previous hunk that 5 lines at the + // beginning of the file had been deleted, and to adjust where it tries to apply the second + // hunk accordingly. + expect(applyPatch( + 'Horgle worgle borgle introductory text\n' + + 'Wiggly jiggly piggly introductory text\n' + + 'A B C introductory text\n' + + '1 2 3 introductory text\n' + + 'Baa oink moo introductory text\n' + + 'Probably enough introductory text\n' + + 'Incy wincy mincy introductory text\n' + + '\n' + + 'Three repeated verses:\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n', + + patch + )).to.equal( + 'Horgle worgle borgle introductory text\n' + + 'Wiggly jiggly piggly introductory text\n' + + 'A B C introductory text\n' + + '1 2 3 introductory text\n' + + 'Baa oink moo introductory text\n' + + 'Probably enough introductory text\n' + + '\n' + + 'Three repeated verses:\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Bob\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + ); + + // What if we instead ADD five lines? Same thing - we still expect verse 4 to be the one + // changed + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n' + + 'Bla bla bla introductory text\n' + + 'Foo bar baz introductory text\n' + + 'Fworble worble glorble introductory text\n' + + 'Need to be at least 6 lines of introductory text\n' + + 'Jingle jangle jungle introductory text\n' + + 'Horgle worgle borgle introductory text\n' + + 'Wiggly jiggly piggly introductory text\n' + + 'A B C introductory text\n' + + '1 2 3 introductory text\n' + + 'Baa oink moo introductory text\n' + + 'Probably enough introductory text\n' + + 'Incy wincy mincy introductory text\n' + + '\n' + + 'Three repeated verses:\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n', + + patch + )).to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n' + + 'Bla bla bla introductory text\n' + + 'Foo bar baz introductory text\n' + + 'Fworble worble glorble introductory text\n' + + 'Need to be at least 6 lines of introductory text\n' + + 'Jingle jangle jungle introductory text\n' + + 'Horgle worgle borgle introductory text\n' + + 'Wiggly jiggly piggly introductory text\n' + + 'A B C introductory text\n' + + '1 2 3 introductory text\n' + + 'Baa oink moo introductory text\n' + + 'Probably enough introductory text\n' + + '\n' + + 'Three repeated verses:\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Bob\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + + '\n' + + 'There was an old man named Michael Finnegan\n' + + 'He had whiskers on his chinnegan\n' + + 'The wind came along and blew them in again\n' + + 'Poor old Michael Finnegan, begin again\n' + ); + }); + + it('should succeed when hunk needs a negative offset', function() { + expect(applyPatch( + 'line1\n' + + 'line3\n' + + 'line4\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -3,2 +3,3 @@\n' + + ' line1\n' + + '+line2\n' + + ' line3\n')) + .to.equal( + 'line1\n' + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('can handle an insertion before the first line', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n' + + 'line4\n' + 'line5\n', '--- test\theader1\n' + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' + + '@@ -1,2 +1,3 @@\n' + + '+line1\n' + ' line2\n' + + ' line3\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('can handle an insertion after the first line', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -3,2 +3,3 @@\n' + + ' line3\n' + + ' line4\n' + + '+line5\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('should succeed when hunk needs a positive offset', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + ' line3\n' + '+line4\n' - + ' line5\n', - {fuzzFactor: 1})) + + ' line5\n')) .to.equal( - 'line2\n' + 'line1\n' + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('should succeed when 1st hunk specifies invalid newStart', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +2,3 @@\n' + + ' line3\n' + + '+line4\n' + + ' line5\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('should succeed when 2nd hunk specifies invalid newStart', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,2 @@\n' + + ' line1\n' + + '-line2\n' + + ' line3\n' + + '@@ -3,2 +3,3 @@\n' + + ' line3\n' + + '+line4\n' + + ' line5\n')) + .to.equal( + 'line1\n' + + 'line3\n' + 'line4\n' + 'line5\n'); }); + it('should create a file', function() { + expect(applyPatch('', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -0,0 +1,4 @@\n' + + '+line1\n' + + '+line2\n' + + '+line3\n' + + '+line4\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n'); + }); + + it('should erase a file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +0,0 @@\n' + + '-line1\n' + + '-line2\n' + + '-line3\n' + + '-line4\n')) + .to.equal(''); + }); + it('should allow custom line comparison', function() { expect(applyPatch( 'line2\n' @@ -429,7 +1389,245 @@ describe('patch/apply', function() { expect(applyPatch(oldtext, diffed)).to.equal(newtext); }); + it('should accept structured patches', function() { + const oldContent = [ + 'line1', + 'line2', + '' + ].join('\n'); + const newContent = [ + 'line1', + 'line02' + ].join('\n'); + const patch = structuredPatch('test.txt', 'test.txt', oldContent, newContent); + + expect(applyPatch(oldContent, patch)).to.equal(newContent); + }); + + // Regression test based on bug https://github.com/kpdecker/jsdiff/issues/177 + it('should correctly apply a patch that truncates an entire file', function() { + const patch = parsePatch( + '===================================================================\n' + + '--- index.js\n' + + '+++ index.js\n' + + '@@ -1,3 +1,0 @@\n' + + '-this\n' + + '-\n' + + '-tos\n' + + '\\ No newline at end of file\n' + ); + const fileContents = 'this\n\ntos'; + + expect(applyPatch(fileContents, patch)) + .to.equal(''); + }); + + it('should automatically convert a patch with Unix file endings to Windows when patching a Windows file', () => { + const oldFile = 'foo\r\nbar\r\nbaz\r\nqux\r\n'; + const diffFile = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\tOld Header\n' + + '+++ testFileName\tNew Header\n' + + '@@ -2,2 +2,3 @@\n' + + '-bar\n' + + '-baz\n' + + '+new\n' + + '+two\n' + + '+three\n'; + + expect(applyPatch(oldFile, diffFile)).to.equal('foo\r\nnew\r\ntwo\r\nthree\r\nqux\r\n'); + }); + + it('should automatically convert a patch with Windows file endings to Unix when patching a Unix file', () => { + const oldFile = 'foo\nbar\nbaz\nqux\n'; + const diffFile = + 'Index: testFileName\r\n' + + '===================================================================\r\n' + + '--- testFileName\tOld Header\r\n' + + '+++ testFileName\tNew Header\r\n' + + '@@ -2,2 +2,3 @@\r\n' + + '-bar\r\n' + + '-baz\r\n' + + '+new\r\n' + + '+two\r\n' + + '+three\r\n'; + + expect(applyPatch(oldFile, diffFile)).to.equal('foo\nnew\ntwo\nthree\nqux\n'); + }); + + it('should leave line endings in the patch alone if the target file has mixed file endings, even if this means the patch does not apply', () => { + const oldFile1 = 'foo\r\nbar\nbaz\nqux\n'; + const oldFile2 = 'foo\nbar\r\nbaz\r\nqux\n'; + const diffFile = + 'Index: testFileName\r\n' + + '===================================================================\r\n' + + '--- testFileName\tOld Header\r\n' + + '+++ testFileName\tNew Header\r\n' + + '@@ -2,2 +2,3 @@\r\n' + + '-bar\r\n' + + '-baz\r\n' + + '+new\r\n' + + '+two\r\n' + + '+three\r\n'; + + expect(applyPatch(oldFile1, diffFile)).to.equal(false); + expect(applyPatch(oldFile2, diffFile)).to.equal('foo\nnew\r\ntwo\r\nthree\r\nqux\n'); + }); + + it('should leave patch file endings alone if autoConvertLineEndings=false', () => { + const oldFile = 'foo\r\nbar\r\nbaz\r\nqux\r\n'; + const diffFile = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\tOld Header\n' + + '+++ testFileName\tNew Header\n' + + '@@ -2,2 +2,3 @@\n' + + '-bar\n' + + '-baz\n' + + '+new\n' + + '+two\n' + + '+three\n'; + + expect(applyPatch(oldFile, diffFile, {autoConvertLineEndings: false})).to.equal(false); + }); + + it('fails if asked to remove a non-existent trailing newline with fuzzFactor 0', () => { + const oldFile = 'foo\nbar\nbaz\nqux'; + const diffFile = + 'Index: bla\n' + + '===================================================================\n' + + '--- bla\tOld Header\n' + + '+++ bla\tNew Header\n' + + '@@ -4,1 +4,1 @@\n' + + '-qux\n' + + '+qux\n' + + '\\ No newline at end of file\n'; + + expect(applyPatch(oldFile, diffFile)).to.equal(false); + }); + + it('fails if asked to add an EOF newline, with fuzzFactor 0, when one already exists', () => { + const oldFile = 'foo\nbar\nbaz\nqux\n'; + const diffFile = + 'Index: bla\n' + + '===================================================================\n' + + '--- bla\tOld Header\n' + + '+++ bla\tNew Header\n' + + '@@ -4,1 +4,1 @@\n' + + '-qux\n' + + '\\ No newline at end of file\n' + + '+qux\n'; + + expect(applyPatch(oldFile, diffFile)).to.equal(false); + }); + + it('ignores being asked to remove a non-existent trailing newline if fuzzFactor >0', () => { + const oldFile = 'foo\nbar\nbaz\nqux'; + const diffFile = + 'Index: bla\n' + + '===================================================================\n' + + '--- bla\tOld Header\n' + + '+++ bla\tNew Header\n' + + '@@ -4,1 +4,1 @@\n' + + '-qux\n' + + '+qux\n' + + '\\ No newline at end of file\n'; + + expect(applyPatch(oldFile, diffFile, {fuzzFactor: 1})).to.equal(oldFile); + }); + + it('ignores being asked to add an EOF newline when one already exists if fuzzFactor>0', () => { + const oldFile = 'foo\nbar\nbaz\nqux\n'; + const diffFile = + 'Index: bla\n' + + '===================================================================\n' + + '--- bla\tOld Header\n' + + '+++ bla\tNew Header\n' + + '@@ -4,1 +4,1 @@\n' + + '-qux\n' + + '\\ No newline at end of file\n' + + '+qux\n'; + + expect(applyPatch(oldFile, diffFile, {fuzzFactor: 1})).to.equal(oldFile); + }); + + describe('when the last line is changed but both old & new version have no trailing newline...', () => { + const diffFile = 'Index: file.txt\n' + + '===================================================================\n' + + '--- file.txt\n' + + '+++ file.txt\n' + + '@@ -1,4 +1,4 @@\n' + + ' foo\n' + + ' bar\n' + + ' baz\n' + + '-banana\n' + + '\\ No newline at end of file\n' + + '+babaco\n' + + '\\ No newline at end of file\n'; + + it('correctly applies the patch to the original source file', () => { + const oldFile = 'foo\nbar\nbaz\nbanana'; + expect(applyPatch(oldFile, diffFile)).to.equal('foo\nbar\nbaz\nbabaco'); + }); + + it('fails if fuzzFactor=0 and the source file has an unexpected trailing newline', () => { + const oldFile = 'foo\nbar\nbaz\nbanana\n'; + expect(applyPatch(oldFile, diffFile)).to.equal(false); + }); + + it('ignores an unexpected trailing newline if fuzzFactor > 0', () => { + const oldFile = 'foo\nbar\nbaz\nbanana\n'; + expect(applyPatch(oldFile, diffFile, {fuzzFactor: 1})).to.equal('foo\nbar\nbaz\nbabaco\n'); + }); + + it("ignores extra lines, even with fuzzFactor = 0, as long as there's no newline at EOF", () => { + const oldFile = 'foo\nbar\nbaz\nbanana\nqux'; + expect(applyPatch(oldFile, diffFile)).to.equal('foo\nbar\nbaz\nbabaco\nqux'); + }); + }); + + it('rejects negative or non-integer fuzz factors', () => { + expect(() => { + applyPatch( + 'line2\n' + + 'line3\n' + + 'line5\n', + + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + ' line5\n', + + {fuzzFactor: -1} + ); + }).to['throw']('fuzzFactor must be a non-negative integer'); + + expect(() => { + applyPatch( + 'line2\n' + + 'line3\n' + + 'line5\n', + + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + ' line5\n', + {fuzzFactor: 1.5} + ); + }).to['throw']('fuzzFactor must be a non-negative integer'); + }); }); describe('#applyPatches', function() { @@ -475,15 +1673,37 @@ describe('patch/apply', function() { + 'foo5\n' }; + it('should handle errors on complete', function(done) { + const expected = new Error(); + + applyPatches(patch, { + loadFile(index, callback) { + callback(undefined, contents[index.index]); + }, + patched(index, content, callback) { + callback(expected); + }, + complete(err) { + expect(err) + .to.equal(expected) + .to.not.be.undefined; + + done(); + } + }); + }); + it('should handle multiple files', function(done) { applyPatches(patch, { loadFile(index, callback) { callback(undefined, contents[index.index]); }, - patched(index, content) { + patched(index, content, callback) { expect(content) .to.equal(expected[index.index]) .to.not.be.undefined; + + callback(); }, complete: done }); @@ -493,10 +1713,12 @@ describe('patch/apply', function() { loadFile(index, callback) { callback(undefined, contents[index.index]); }, - patched(index, content) { + patched(index, content, callback) { expect(content) .to.equal(expected[index.index]) .to.not.be.undefined; + + callback(); }, complete: done }); @@ -512,5 +1734,95 @@ describe('patch/apply', function() { } }); }); + it('should handle patches without Index', function(done) { + const patch = + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + ' line5\n' + + '===================================================================\n' + + '--- test2\theader1\n' + + '+++ test2\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' foo2\n' + + ' foo3\n' + + '+foo4\n' + + ' foo5\n'; + + applyPatches(patch, { + loadFile(index, callback) { + callback(undefined, contents[index.oldFileName]); + }, + patched(index, content, callback) { + expect(content) + .to.equal(expected[index.newFileName]) + .to.not.be.undefined; + + callback(); + }, + complete: done + }); + }); + + it('should handle file names containing spaces', done => { + const patch = + `=================================================================== +--- test file\theader1 ++++ test file\theader2 +@@ -1,2 +1,3 @@ + line1 ++line2 + line3 +=================================================================== +--- test file 2\theader1 ++++ test file 2\theader2 +@@ -1,2 +1,3 @@ + foo1 ++foo2 + foo3 +`; + + const contents = { + 'test file': + `line1 +line3 +`, + 'test file 2': + `foo1 +foo3 +` + }; + + const expected = { + 'test file': + `line1 +line2 +line3 +`, + 'test file 2': + `foo1 +foo2 +foo3 +` + }; + + applyPatches(patch, { + loadFile(index, callback) { + callback(undefined, contents[index.oldFileName]); + }, + patched(index, content, callback) { + expect(content) + .to.equal(expected[index.newFileName]) + .to.not.be.undefined; + + callback(); + }, + complete: done + }); + }); }); }); diff --git a/test/patch/create.js b/test/patch/create.js index d1ae33a4b..d97716787 100644 --- a/test/patch/create.js +++ b/test/patch/create.js @@ -1,10 +1,12 @@ -import {diffWords} from '../../lib'; -import {createPatch, createTwoFilesPatch, structuredPatch} from '../../lib/patch/create'; +import {diffWords} from 'diff'; +import {createPatch, createTwoFilesPatch, FILE_HEADERS_ONLY, formatPatch, INCLUDE_HEADERS, OMIT_HEADERS, structuredPatch} from '../../libesm/patch/create.js'; +import {parsePatch} from '../../libesm/patch/parse.js'; import {expect} from 'chai'; const VERBOSE = false; function log() { + // eslint-disable-next-line no-console VERBOSE && console.log.apply(console, arguments); } @@ -89,6 +91,55 @@ describe('patch/create', function() { + '+line4\n'); }); + it('should output "no newline" at end of file message on both missing nl', function() { + expect(createPatch('test', 'line1\nline2\nline3\nline4', 'line1\nline2\nline3\nline44', 'header1', 'header2')).to.equal( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + ' line1\n' + + ' line2\n' + + ' line3\n' + + '-line4\n' + + '\\ No newline at end of file\n' + + '+line44\n' + + '\\ No newline at end of file\n'); + }); + + it('should get the "No newline" position right in the case from https://github.com/kpdecker/jsdiff/issues/531', function() { + const oldContent = '1st line.\n2nd line.\n3rd line.'; + const newContent = 'Z11 thing.\nA New thing.\n2nd line.\nNEW LINE.\n3rd line.\n\nSOMETHING ELSE.'; + + const diff = createPatch( + 'a.txt', + oldContent, + newContent, + undefined, + undefined, + { context: 3 } + ); + + expect(diff).to.equal( + 'Index: a.txt\n' + + '===================================================================\n' + + '--- a.txt\n' + + '+++ a.txt\n' + + '@@ -1,3 +1,7 @@\n' + + '-1st line.\n' + + '+Z11 thing.\n' + + '+A New thing.\n' + + ' 2nd line.\n' + + '-3rd line.\n' + + '\\ No newline at end of file\n' + + '+NEW LINE.\n' + + '+3rd line.\n' + + '+\n' + + '+SOMETHING ELSE.\n' + + '\\ No newline at end of file\n' + ); + }); + it('should output "no newline" at end of file message on context missing nl', function() { expect(createPatch('test', 'line11\nline2\nline3\nline4', 'line1\nline2\nline3\nline4', 'header1', 'header2')).to.equal( 'Index: test\n' @@ -104,6 +155,32 @@ describe('patch/create', function() { + '\\ No newline at end of file\n'); }); + it('should output no "no newline" at end of file message on empty file', function() { + expect(createPatch('test', '', 'line1\nline2\nline3\nline4', 'header1', 'header2')).to.equal( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -0,0 +1,4 @@\n' + + '+line1\n' + + '+line2\n' + + '+line3\n' + + '+line4\n' + + '\\ No newline at end of file\n'); + + expect(createPatch('test', 'line1\nline2\nline3\nline4', '', 'header1', 'header2')).to.equal( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +0,0 @@\n' + + '-line1\n' + + '-line2\n' + + '-line3\n' + + '-line4\n' + + '\\ No newline at end of file\n'); + }); + it('should not output no newline at end of file message when eof outside hunk', function() { expect(createPatch('test', 'line11\nline2\nline3\nline4\nline4\nline4\nline4', 'line1\nline2\nline3\nline4\nline4\nline4\nline4', 'header1', 'header2')).to.equal( 'Index: test\n' @@ -485,7 +562,7 @@ describe('patch/create', function() { expect(diffResult).to.equal(expectedResult); }); - it('should generatea a patch with context size 0', function() { + it('should generate a patch with context size 0', function() { const expectedResult = 'Index: testFileName\n' + '===================================================================\n' @@ -495,11 +572,11 @@ describe('patch/create', function() { + '-value\n' + '+new value\n' + '+new value 2\n' - + '@@ -11,1 +12,0 @@\n' + + '@@ -11,1 +11,0 @@\n' + '-remove value\n' - + '@@ -21,1 +21,0 @@\n' + + '@@ -21,1 +20,0 @@\n' + '-remove value\n' - + '@@ -30,0 +29,1 @@\n' + + '@@ -29,0 +29,1 @@\n' + '+add value\n' + '@@ -34,1 +34,2 @@\n' + '-value\n' @@ -551,43 +628,240 @@ describe('patch/create', function() { expect(diffResult).to.equal(expectedResult); }); - it('should output headers only for identical files', function() { - const expectedResult = - 'Index: testFileName\n' - + '===================================================================\n' - + '--- testFileName\tOld Header\n' - + '+++ testFileName\tNew Header\n'; - const diffResult = createPatch('testFileName', oldFile, oldFile, 'Old Header', 'New Header'); - expect(diffResult).to.equal(expectedResult); + describe('headers handling', function() { + it('should output headers only for identical files', function() { + const expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\tOld Header\n' + + '+++ testFileName\tNew Header\n'; + const diffResult = createPatch('testFileName', oldFile, oldFile, 'Old Header', 'New Header'); + expect(diffResult).to.equal(expectedResult); + }); + + it('should omit headers if undefined', function() { + const expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n'; + const diffResult = createPatch('testFileName', oldFile, oldFile); + expect(diffResult).to.equal(expectedResult); + }); + + it('should safely handle empty inputs', function() { + const expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n'; + const diffResult = createPatch('testFileName', '', ''); + expect(diffResult).to.equal(expectedResult); + }); + + it('should omit index with multiple file names', function() { + const expectedResult = + '===================================================================\n' + + '--- foo\n' + + '+++ bar\n'; + const diffResult = createTwoFilesPatch('foo', 'bar', '', ''); + expect(diffResult).to.equal(expectedResult); + }); + + it('should handle INCLUDE_HEADERS the same as not specifying options regarding headers', function() { + const expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n' + + '@@ -1,1 +1,1 @@\n' + + '-foo\n' + + '+bar\n'; + const diffResult1 = createTwoFilesPatch('testFileName', 'testFileName', 'foo\n', 'bar\n'); + const diffResult2 = createTwoFilesPatch('testFileName', 'testFileName', 'foo\n', 'bar\n', undefined, undefined, {}); + const diffResult3 = createTwoFilesPatch('testFileName', 'testFileName', 'foo\n', 'bar\n', undefined, undefined, { headerOptions: INCLUDE_HEADERS }); + expect(diffResult1).to.equal(expectedResult); + expect(diffResult2).to.equal(expectedResult); + expect(diffResult3).to.equal(expectedResult); + }); + + it('should respect FILE_HEADERS_ONLY', function() { + const expectedResult = + '--- testFileName\n' + + '+++ testFileName\n' + + '@@ -1,1 +1,1 @@\n' + + '-foo\n' + + '+bar\n'; + const diffResult = createTwoFilesPatch('testFileName', 'testFileName', 'foo\n', 'bar\n', undefined, undefined, {headerOptions: FILE_HEADERS_ONLY}); + expect(diffResult).to.equal(expectedResult); + }); + + it('should respect OMIT_HEADERS', function() { + const expectedResult = + '@@ -1,1 +1,1 @@\n' + + '-foo\n' + + '+bar\n'; + const diffResult = createTwoFilesPatch('testFileName', 'testFileName', 'foo\n', 'bar\n', undefined, undefined, {headerOptions: OMIT_HEADERS}); + expect(diffResult).to.equal(expectedResult); + }); }); - it('should omit headers if undefined', function() { - const expectedResult = - 'Index: testFileName\n' - + '===================================================================\n' - + '--- testFileName\n' - + '+++ testFileName\n'; - const diffResult = createPatch('testFileName', oldFile, oldFile); - expect(diffResult).to.equal(expectedResult); + it('should respect maxEditLength', function() { + expect(createPatch('test', 'line1\nline2\nline3\n', 'lineX\nlineY\nlineZ\nline42\n', 'header1', 'header2', {maxEditLength: 1})).to.be.undefined; + }); + + it('should not crash when encountering an enormous hunk', function() { + // Regression test; we used to crash here due to lines like + // ret.push.apply(ret, hunk.lines); + // and + // curRange.push(... contextLines(lines.slice(0, contextSize))); + // in the patch creation code that fail in practice due to JS implementations having a + // maximum argument count. + expect(createTwoFilesPatch('foo', 'bar', '', 'foo\n'.repeat(1000000))).to.equal( + '===================================================================\n' + + '--- foo\n' + + '+++ bar\n' + + '@@ -0,0 +1,1000000 @@\n' + + '+foo\n'.repeat(1000000) + ); + }); + + describe('ignoreWhitespace', function() { + it('ignoreWhitespace: false', function() { + const expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n' + + '@@ -1,2 +1,2 @@\n' + + '-line \n' + + '- line\n' + + '\\ No newline at end of file\n' + + '+line\n' + + '+line\n' + + '\\ No newline at end of file\n'; + + const diffResult = createPatch('testFileName', 'line \n line', 'line\nline', undefined, undefined, {ignoreWhitespace: false}); + expect(diffResult).to.equal(expectedResult); + }); + + it('ignoreWhitespace: true', function() { + const expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n'; + + const diffResult = createPatch('testFileName', 'line \n line', 'line\nline', undefined, undefined, {ignoreWhitespace: true}); + expect(diffResult).to.equal(expectedResult); + }); + }); + + describe('newlineIsToken', function() { + // See https://github.com/kpdecker/jsdiff/pull/345#issuecomment-2255886105 + it("isn't allowed any more, since the patches produced were nonsense", function() { + expect(() => { + createPatch('testFileName', 'line\nline', 'line\r\nline', undefined, undefined, {newlineIsToken: true}); + }).to['throw']('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); + }); + }); + + it('takes an optional callback option', function(done) { + createPatch( + 'test', + 'foo\nbar\nbaz\n', 'foo\nbarcelona\nbaz\n', + 'header1', 'header2', + {callback: (res) => { + expect(res).to.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,3 @@\n' + + ' foo\n' + + '-bar\n' + + '+barcelona\n' + + ' baz\n' + ); + done(); + }} + ); + }); + + it('lets you provide a callback by passing a function as the `options` parameter', function(done) { + createPatch( + 'test', + 'foo\nbar\nbaz\n', 'foo\nbarcelona\nbaz\n', + 'header1', 'header2', + res => { + expect(res).to.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,3 @@\n' + + ' foo\n' + + '-bar\n' + + '+barcelona\n' + + ' baz\n' + ); + done(); + } + ); + }); + + it('still supports early termination when in async mode', function(done) { + createPatch( + 'test', + 'foo\nbar\nbaz\n', 'food\nbarcelona\nbaz\n', + 'header1', 'header2', + { + maxEditLength: 1, + callback: (res) => { + expect(res).to.eql(undefined); + done(); + } + } + ); }); + }); - it('should safely handle empty inputs', function() { + describe('stripTrailingCr', function() { + it('stripTrailingCr: false', function() { const expectedResult = - 'Index: testFileName\n' - + '===================================================================\n' - + '--- testFileName\n' - + '+++ testFileName\n'; - const diffResult = createPatch('testFileName', '', ''); - expect(diffResult).to.equal(expectedResult); + '===================================================================\n' + + '--- foo\n' + + '+++ bar\n' + + '@@ -1,2 +1,2 @@\n' + + '-line\n' + + '+line\r\n' + + ' line\n' + + '\\ No newline at end of file\n'; + expect(createTwoFilesPatch( + 'foo', + 'bar', + 'line\nline', + 'line\r\nline', + undefined, + undefined, + {stripTrailingCr: false} + )).to.equal(expectedResult); }); - it('should omit index with multiple file names', function() { + it('stripTrailingCr: true', function() { const expectedResult = '===================================================================\n' + '--- foo\n' + '+++ bar\n'; - const diffResult = createTwoFilesPatch('foo', 'bar', '', ''); - expect(diffResult).to.equal(expectedResult); + expect(createTwoFilesPatch( + 'foo', + 'bar', + 'line\nline', + 'line\r\nline', + undefined, + undefined, + {stripTrailingCr: true} + )).to.equal(expectedResult); }); }); @@ -607,5 +881,579 @@ describe('patch/create', function() { }] }); }); + + it('takes an optional callback option', function(done) { + structuredPatch( + 'oldfile', 'newfile', + 'foo\nbar\nbaz\n', 'foo\nbarcelona\nbaz\n', + 'header1', 'header2', + {callback: (res) => { + expect(res).to.eql({ + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' foo', '-bar', '+barcelona', ' baz'] + }] + }); + done(); + }} + ); + }); + + it('lets you provide a callback by passing a function as the `options` parameter', function(done) { + structuredPatch( + 'oldfile', 'newfile', + 'foo\nbar\nbaz\n', 'foo\nbarcelona\nbaz\n', + 'header1', 'header2', + res => { + expect(res).to.eql({ + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' foo', '-bar', '+barcelona', ' baz'] + }] + }); + done(); + } + ); + }); + + it('sets oldHeader and newHeader to undefined if they are not provided', function() { + const res = structuredPatch( + 'oldfile', 'newfile', + 'line2\nline3\nline4\n', 'line2\nline3\nline5' + ); + expect(res).to.eql({ + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: undefined, newHeader: undefined, + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'] + }] + }); + }); + + describe('given options.maxEditLength', function() { + const options = { maxEditLength: 1 }; + + it('terminates early', function() { + const res = structuredPatch( + 'oldfile', 'newfile', + 'line2\nline3\nline4\n', 'line2\nline3\nline5', + 'header1', 'header2', options + ); + expect(res).to.be.undefined; + }); + }); + }); + + describe('#formatPatch', function() { + it('should generate a patch string from an input diff', function() { + const res = formatPatch(structuredPatch( + 'oldfile', 'newfile', + 'line2\nline3\nline4\n', 'line2\nline3\nline5', + 'header1', 'header2' + )); + expect(res).to.equal(createTwoFilesPatch( + 'oldfile', 'newfile', + 'line2\nline3\nline4\n', 'line2\nline3\nline5', + 'header1', 'header2' + )); + }); + it('supports serializing an array of structured patch objects into a single patch file in unified diff format', function() { + const patch = [ + { + oldFileName: 'foo', + oldHeader: '2023-12-29 15:48:17.976616966 +0000', + newFileName: 'bar', + newHeader: '2023-12-29 15:48:21.400516845 +0000', + hunks: [ + { + oldStart: 1, + oldLines: 1, + newStart: 1, + newLines: 1, + lines: [ + '-xxx', + '+yyy' + ] + } + ] + }, + { + oldFileName: 'baz', + oldHeader: '2023-12-29 15:48:29.376283616 +0000', + newFileName: 'qux', + newHeader: '2023-12-29 15:48:32.908180343 +0000', + hunks: [ + { + oldStart: 1, + oldLines: 1, + newStart: 1, + newLines: 1, + lines: [ + '-aaa', + '+bbb' + ] + } + ] + } + ]; + expect(formatPatch(patch)).to.equal( + '===================================================================\n' + + '--- foo\t2023-12-29 15:48:17.976616966 +0000\n' + + '+++ bar\t2023-12-29 15:48:21.400516845 +0000\n' + + '@@ -1,1 +1,1 @@\n' + + '-xxx\n' + + '+yyy\n' + + '\n' + + '===================================================================\n' + + '--- baz\t2023-12-29 15:48:29.376283616 +0000\n' + + '+++ qux\t2023-12-29 15:48:32.908180343 +0000\n' + + '@@ -1,1 +1,1 @@\n' + + '-aaa\n' + + '+bbb\n' + ); + }); + it('should roughly be the inverse of parsePatch', function() { + // There are so many differences in how a semantically-equivalent patch + // can be formatted in unified diff format, AND in JsDiff's structured + // patch format as long as https://github.com/kpdecker/jsdiff/issues/434 + // goes unresolved, that a stronger claim than "roughly the inverse" is + // sadly not possible here. + + // Check 1: starting with a patch in uniform diff format, does + // formatPatch(parsePatch(...)) round-trip? + const uniformPatch = '===================================================================\n' + + '--- baz\t2023-12-29 15:48:29.376283616 +0000\n' + + '+++ qux\t2023-12-29 15:48:32.908180343 +0000\n' + + '@@ -1,1 +1,1 @@\n' + + '-aaa\n' + + '+bbb\n'; + expect(formatPatch(parsePatch(uniformPatch))).to.equal(uniformPatch); + + // Check 2: starting with a structuredPatch, does formatting and then + // parsing again basically round-trip as long as we wrap it in an array + // to match the output of parsePatch? + const patchObj = structuredPatch( + 'oldfile', 'newfile', + 'line2\nline3\nline4\n', 'line2\nline3\nline5', + 'header1', 'header2' + ); + + const roundTrippedPatch = parsePatch(formatPatch([patchObj])); + + expect(roundTrippedPatch).to.deep.equal([patchObj]); + }); + + describe('with headerOptions parameter', function() { + const patch = { + oldFileName: 'oldfile', + oldHeader: 'old-timestamp', + newFileName: 'newfile', + newHeader: 'new-timestamp', + hunks: [ + { + oldStart: 1, + oldLines: 1, + newStart: 1, + newLines: 1, + lines: [ + '-old line', + '+new line' + ] + } + ] + }; + + const patchArray = [ + { + oldFileName: 'file1', + oldHeader: 'timestamp1', + newFileName: 'file1', + newHeader: 'timestamp2', + hunks: [ + { + oldStart: 1, + oldLines: 1, + newStart: 1, + newLines: 1, + lines: ['-a', '+b'] + } + ] + }, + { + oldFileName: 'file2', + oldHeader: 'timestamp3', + newFileName: 'file2', + newHeader: 'timestamp4', + hunks: [ + { + oldStart: 1, + oldLines: 1, + newStart: 1, + newLines: 1, + lines: ['-x', '+y'] + } + ] + } + ]; + + it('should include all headers with INCLUDE_HEADERS', function() { + const result = formatPatch(patch, INCLUDE_HEADERS); + const expected = + '===================================================================\n' + + '--- oldfile\told-timestamp\n' + + '+++ newfile\tnew-timestamp\n' + + '@@ -1,1 +1,1 @@\n' + + '-old line\n' + + '+new line\n'; + expect(result).to.equal(expected); + }); + + it('should include only file headers with FILE_HEADERS_ONLY', function() { + const result = formatPatch(patch, FILE_HEADERS_ONLY); + const expected = + '--- oldfile\told-timestamp\n' + + '+++ newfile\tnew-timestamp\n' + + '@@ -1,1 +1,1 @@\n' + + '-old line\n' + + '+new line\n'; + expect(result).to.equal(expected); + }); + + it('should omit all headers with OMIT_HEADERS', function() { + const result = formatPatch(patch, OMIT_HEADERS); + const expected = + '@@ -1,1 +1,1 @@\n' + + '-old line\n' + + '+new line\n'; + expect(result).to.equal(expected); + }); + + it('should work with array of patches and INCLUDE_HEADERS', function() { + const result = formatPatch(patchArray, INCLUDE_HEADERS); + const expected = + 'Index: file1\n' + + '===================================================================\n' + + '--- file1\ttimestamp1\n' + + '+++ file1\ttimestamp2\n' + + '@@ -1,1 +1,1 @@\n' + + '-a\n' + + '+b\n' + + '\n' + + 'Index: file2\n' + + '===================================================================\n' + + '--- file2\ttimestamp3\n' + + '+++ file2\ttimestamp4\n' + + '@@ -1,1 +1,1 @@\n' + + '-x\n' + + '+y\n'; + expect(result).to.equal(expected); + }); + + it('should work with array of patches and FILE_HEADERS_ONLY', function() { + const result = formatPatch(patchArray, FILE_HEADERS_ONLY); + const expected = + '--- file1\ttimestamp1\n' + + '+++ file1\ttimestamp2\n' + + '@@ -1,1 +1,1 @@\n' + + '-a\n' + + '+b\n' + + '\n' + + '--- file2\ttimestamp3\n' + + '+++ file2\ttimestamp4\n' + + '@@ -1,1 +1,1 @@\n' + + '-x\n' + + '+y\n'; + expect(result).to.equal(expected); + }); + + it('should throw an error when given an array of patches and OMIT_HEADERS', function() { + // eslint-disable-next-line dot-notation + expect(() => formatPatch(patchArray, OMIT_HEADERS)).to.throw(); + }); + + it('should silently skip headers when filenames are undefined', function() { + const patchWithNoFilenames = { + oldFileName: undefined, + newFileName: undefined, + oldHeader: undefined, + newHeader: undefined, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + }] + }; + // All header options should silently skip headers when filenames + // are undefined, rather than emitting "--- undefined" etc. + const expectedOutput = + '@@ -1,1 +1,1 @@\n' + + '-old\n' + + '+new\n'; + const expectedWithUnderline = + '===================================================================\n' + + '@@ -1,1 +1,1 @@\n' + + '-old\n' + + '+new\n'; + expect(formatPatch(patchWithNoFilenames, OMIT_HEADERS)).to.equal(expectedOutput); + expect(formatPatch(patchWithNoFilenames, FILE_HEADERS_ONLY)).to.equal(expectedOutput); + expect(formatPatch(patchWithNoFilenames, INCLUDE_HEADERS)).to.equal(expectedWithUnderline); + expect(formatPatch(patchWithNoFilenames)).to.equal(expectedWithUnderline); + }); + + it('should emit diff --git header for patches with isGit flag', function() { + const patch = { + oldFileName: 'a/file.txt', + newFileName: 'b/file.txt', + oldHeader: '', + newHeader: '', + isGit: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + }] + }; + expect(formatPatch(patch)).to.equal( + 'diff --git a/file.txt b/file.txt\n' + + '--- a/file.txt\n' + + '+++ b/file.txt\n' + + '@@ -1,1 +1,1 @@\n' + + '-old\n' + + '+new\n' + ); + }); + + it('should throw if oldFileName is missing or empty for a Git patch', function() { + const patch = { + newFileName: 'b/file.txt', + oldHeader: '', + newHeader: '', + isGit: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + }] + }; + // eslint-disable-next-line dot-notation + expect(() => formatPatch(patch)).to.throw('oldFileName must be specified for Git patches'); + }); + + it('should throw if newFileName is missing or empty for a Git patch', function() { + const patch = { + oldFileName: 'a/file.txt', + oldHeader: '', + newFileName: '', + newHeader: '', + isGit: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + }] + }; + // eslint-disable-next-line dot-notation + expect(() => formatPatch(patch)).to.throw('newFileName must be specified for Git patches'); + }); + + it('should not mutate hunk objects', function() { + const patch = { + oldFileName: 'a/file.txt', + newFileName: 'b/file.txt', + oldHeader: '', + newHeader: '', + hunks: [{ + oldStart: 1, oldLines: 0, + newStart: 1, newLines: 0, + lines: [] + }] + }; + formatPatch(patch); + expect(patch.hunks[0].oldStart).to.equal(1); + expect(patch.hunks[0].newStart).to.equal(1); + }); + + it('should ignore headerOptions for multi-file patches with isGit flag', function() { + const patches = [ + { + oldFileName: 'a/one.txt', + newFileName: 'b/one.txt', + oldHeader: '', + newHeader: '', + isGit: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-a', '+b'] + }] + }, + { + oldFileName: 'a/two.txt', + newFileName: 'b/two.txt', + oldHeader: '', + newHeader: '', + isGit: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-c', '+d'] + }] + } + ]; + const expected = + 'diff --git a/one.txt b/one.txt\n' + + '--- a/one.txt\n' + + '+++ b/one.txt\n' + + '@@ -1,1 +1,1 @@\n' + + '-a\n' + + '+b\n' + + '\n' + + 'diff --git a/two.txt b/two.txt\n' + + '--- a/two.txt\n' + + '+++ b/two.txt\n' + + '@@ -1,1 +1,1 @@\n' + + '-c\n' + + '+d\n'; + // All three headerOptions values should produce identical output; + // Git patches are self-delimiting via diff --git headers, so + // OMIT_HEADERS should also not throw for multi-file patches: + expect(formatPatch(patches, INCLUDE_HEADERS)).to.equal(expected); + expect(formatPatch(patches, FILE_HEADERS_ONLY)).to.equal(expected); + expect(formatPatch(patches, OMIT_HEADERS)).to.equal(expected); + }); + + it('should emit rename headers for patches with isGit and isRename', function() { + const patch = { + oldFileName: 'a/old.txt', + newFileName: 'b/new.txt', + oldHeader: undefined, + newHeader: undefined, + isGit: true, + isRename: true, + hunks: [] + }; + expect(formatPatch(patch)).to.equal( + 'diff --git a/old.txt b/new.txt\n' + + 'rename from old.txt\n' + + 'rename to new.txt\n' + ); + }); + + it('should emit copy headers for patches with isGit and isCopy', function() { + const patch = { + oldFileName: 'a/original.txt', + newFileName: 'b/copy.txt', + oldHeader: undefined, + newHeader: undefined, + isGit: true, + isCopy: true, + hunks: [] + }; + expect(formatPatch(patch)).to.equal( + 'diff --git a/original.txt b/copy.txt\n' + + 'copy from original.txt\n' + + 'copy to copy.txt\n' + ); + }); + + it('should emit new file mode header for patches with isGit and isCreate', function() { + const patch = { + oldFileName: '/dev/null', + newFileName: 'b/newfile.txt', + oldHeader: '', + newHeader: '', + isGit: true, + isCreate: true, + hunks: [{ + oldStart: 1, oldLines: 0, + newStart: 1, newLines: 1, + lines: ['+hello'] + }] + }; + expect(formatPatch(patch)).to.equal( + 'diff --git a/newfile.txt b/newfile.txt\n' + + 'new file mode 100644\n' + + '--- /dev/null\n' + + '+++ b/newfile.txt\n' + + '@@ -0,0 +1,1 @@\n' + + '+hello\n' + ); + }); + + it('should emit deleted file mode header for patches with isGit and isDelete', function() { + const patch = { + oldFileName: 'a/doomed.txt', + newFileName: '/dev/null', + oldHeader: '', + newHeader: '', + isGit: true, + isDelete: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 0, + lines: ['-goodbye'] + }] + }; + expect(formatPatch(patch)).to.equal( + 'diff --git a/doomed.txt b/doomed.txt\n' + + 'deleted file mode 100644\n' + + '--- a/doomed.txt\n' + + '+++ /dev/null\n' + + '@@ -1,1 +0,0 @@\n' + + '-goodbye\n' + ); + }); + + it('should still emit rename headers with file headers if hunks are present', function() { + const patch = { + oldFileName: 'a/old.txt', + newFileName: 'b/new.txt', + oldHeader: '', + newHeader: '', + isGit: true, + isRename: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-aaa', '+bbb'] + }] + }; + expect(formatPatch(patch)).to.equal( + 'diff --git a/old.txt b/new.txt\n' + + 'rename from old.txt\n' + + 'rename to new.txt\n' + + '--- a/old.txt\n' + + '+++ b/new.txt\n' + + '@@ -1,1 +1,1 @@\n' + + '-aaa\n' + + '+bbb\n' + ); + }); + + it('should round-trip a Git rename patch through formatPatch and parsePatch', function() { + const original = { + oldFileName: 'a/old.txt', + newFileName: 'b/new.txt', + oldHeader: '', + newHeader: '', + isGit: true, + isRename: true, + hunks: [{ + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-aaa', '+bbb'] + }] + }; + const formatted = formatPatch(original); + const parsed = parsePatch(formatted); + expect(parsed).to.have.length(1); + expect(parsed[0]).to.deep.equal(original); + }); + }); }); }); diff --git a/test/patch/line-endings.js b/test/patch/line-endings.js new file mode 100644 index 000000000..19d318b8e --- /dev/null +++ b/test/patch/line-endings.js @@ -0,0 +1,151 @@ +import {parsePatch} from '../../libesm/patch/parse.js'; +import {formatPatch} from '../../libesm/patch/create.js'; +import {winToUnix, unixToWin, isWin, isUnix} from '../../libesm/patch/line-endings.js'; + +import {expect} from 'chai'; + +describe('unixToWin and winToUnix', function() { + it('should convert line endings in a patch between Unix-style and Windows-style', function() { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\r\n' + + '+line4\r\n' + + ' line5\n' + ); + + const winPatch = unixToWin(patch); + expect(formatPatch(winPatch)).to.equal( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\r\n' + + ' line3\r\n' + + '+line4\r\n' + + ' line5\r\n' + ); + + const unixPatch = winToUnix(winPatch); + expect(formatPatch(unixPatch)).to.equal( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + ' line5\n' + ); + + expect(formatPatch(winToUnix(patch))).to.equal(formatPatch(unixPatch)); + }); + + it('should not introduce \\r on the last line if there was no newline at EOF', () => { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + '\\ No newline at end of file\n' + ); + + const winPatch = unixToWin(patch); + expect(formatPatch(winPatch)).to.equal( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\r\n' + + ' line3\r\n' + + '+line4\n' + + '\\ No newline at end of file\n' + ); + }); +}); + +describe('isWin', () => { + it('should return true if all lines end with CRLF', () => { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\r\n' + + ' line3\r\n' + + '+line4\r\n' + ); + expect(isWin(patch)).to.equal(true); + }); + + it('should return false if a line ends with a LF without a CR', () => { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\r\n' + + ' line3\r\n' + + '+line4\n' + ); + expect(isWin(patch)).to.equal(false); + }); + + it('should still return true if only the last line in a file is missing a CR and there is a no newline at EOF indicator', () => { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\r\n' + + ' line3\r\n' + + '+line4\n' + + '\\ No newline at end of file\n' + ); + expect(isWin(patch)).to.equal(true); + }); +}); + +describe('isUnix', () => { + it('should return false if some lines end with CRLF', () => { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\r\n' + + ' line3\n' + + '+line4\r\n' + ); + expect(isUnix(patch)).to.equal(false); + }); + + it('should return true if no lines end with CRLF', () => { + const patch = parsePatch( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + ); + expect(isUnix(patch)).to.equal(true); + }); +}); diff --git a/test/patch/merge.js b/test/patch/merge.js deleted file mode 100644 index d487cadbd..000000000 --- a/test/patch/merge.js +++ /dev/null @@ -1,1212 +0,0 @@ -import {merge} from '../../lib/patch/merge'; -import {parsePatch} from '../../lib/patch/parse'; - -import {expect} from 'chai'; - -describe('patch/merge', function() { - describe('#merge', function() { - it('should update line numbers for no conflicts', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4\n' - + ' line5\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -25,3 +25,4 @@\n' - + ' foo2\n' - + ' foo3\n' - + '+foo4\n' - + ' foo5\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 4, - lines: [ - ' line2', - ' line3', - '+line4', - ' line5' - ] - }, - { - oldStart: 25, oldLines: 3, - newStart: 26, newLines: 4, - lines: [ - ' foo2', - ' foo3', - '+foo4', - ' foo5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should remove identical hunks', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4\n' - + ' line5\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4\n' - + ' line5\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 4, - lines: [ - ' line2', - ' line3', - '+line4', - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - describe('hunk merge', function() { - it('should merge adjacent additions', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4-1\n' - + '+line4-2\n' - + '+line4-3\n' - + ' line5\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -2,2 +2,3 @@\n' - + ' line3\n' - + ' line5\n' - + '+line4-4\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 7, - lines: [ - ' line2', - ' line3', - '+line4-1', - '+line4-2', - '+line4-3', - ' line5', - '+line4-4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should merge leading additions', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + '+line2\n' - + ' line3\n' - + '+line4\n' - + ' line5\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -3,1 +3,2 @@\n' - + ' line5\n' - + '+line4\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 2, - newStart: 1, newLines: 5, - lines: [ - '+line2', - ' line3', - '+line4', - ' line5', - '+line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - - it('should merge adjacent removals', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + '-line3\n' - + '+line4\n' - + ' line5\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -2,2 +2,3 @@\n' - + ' line3\n' - + ' line5\n' - + '+line4\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 3, - lines: [ - '-line2', - '-line3', - '+line4', - ' line5', - '+line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - - it('should merge adjacent additions with context removal', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4-1\n' - + '+line4-2\n' - + '+line4-3\n' - + '-line5\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -2,2 +2,3 @@\n' - + ' line3\n' - + ' line5\n' - + '+line4-4\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 6, - lines: [ - ' line2', - ' line3', - '+line4-1', - '+line4-2', - '+line4-3', - '-line5', - '+line4-4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - - it('should merge removal supersets', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '-line4\n' - + '-line4\n' - + ' line5\n'; - const theirs = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '-line4\n' - + ' line4\n' - + ' line5\n'; - - const expected = { - hunks: [ - { - oldStart: 1, oldLines: 5, - newStart: 1, newLines: 3, - lines: [ - ' line2', - ' line3', - '-line4', - '-line4', - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should conflict removal disjoint sets', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '-line4\n' - + '-line4\n' - + '-line4\n' - + ' line5\n'; - const theirs = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '-line4\n' - + '-line4\n' - + '-line5\n' - + ' line5\n'; - - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - ' line2', - ' line3', - { - conflict: true, - mine: [ - '-line4', - '-line4', - '-line4' - ], - theirs: [ - '-line4', - '-line4', - '-line5' - ] - }, - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - - it('should conflict removal disjoint context', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '-line4\n' - + '-line4\n' - + '-line4\n' - + ' line5\n'; - const theirs = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '-line4\n' - + '-line4\n' - + ' line5\n' - + ' line5\n'; - - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - ' line2', - ' line3', - { - conflict: true, - mine: [ - '-line4', - '-line4', - '-line4' - ], - theirs: [ - '-line4', - '-line4' - ] - }, - ' line5', - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - - // These are all conflicts. A conflict is anything that is on the same desired line that is not identical - it('should conflict two additions at the same line', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4-1\n' - + '+line4-2\n' - + '+line4-3\n' - + ' line5\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + ' line3\n' - + '+line4-4\n'; - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - ' line2', - ' line3', - { - conflict: true, - mine: [ - '+line4-1', - '+line4-2', - '+line4-3' - ], - theirs: [ - '+line4-4' - ] - }, - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should conflict addition supersets', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4\n' - + '+line4\n' - + ' line5\n'; - const theirs = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4\n' - + ' line5\n'; - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - ' line2', - ' line3', - { - conflict: true, - mine: [ - '+line4', - '+line4' - ], - theirs: [ - '+line4' - ] - }, - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle removal and edit (add+remove) at the same line', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + '-line3\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '+line4\n'; - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - ' line2', - { - conflict: true, - mine: [ - '-line3' - ], - theirs: [ - '-line3', - '+line4' - ] - } - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit (add+remove) on multiple lines', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + ' line3\n' - + ' line3\n' - + ' line5\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '-line3\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - oldStart: 1, oldLines: 4, - newStart: 1, newLines: 3, - lines: [ - '-line2', - '-line3', - '-line3', - '+line4', - '+line4', - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit (add+remove) past extents', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + ' line3\n' - + ' line3\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '-line3\n' - + '-line5\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - oldStart: 1, oldLines: 4, - newStart: 1, newLines: 2, - lines: [ - '-line2', - '-line3', - '-line3', - '-line5', - '+line4', - '+line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit (add+remove) past extents', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + ' line3\n' - + ' line3\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '-line3\n' - + '-line5\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - oldStart: 1, oldLines: 4, - newStart: 1, newLines: 2, - lines: [ - '-line2', - '-line3', - '-line3', - '-line5', - '+line4', - '+line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit (add+remove) context mismatch', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + ' line3\n' - + ' line4\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '-line3\n' - + '-line5\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - '-line2', - { - conflict: true, - mine: [ - ' line3' - ], - theirs: [ - '-line3', - '-line3', - '-line5', - '+line4', - '+line4' - ] - }, - ' line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit (add+remove) addition', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + ' line3\n' - + '+line6\n' - + ' line3\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '-line3\n' - + '-line5\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - '-line2', - { - conflict: true, - mine: [ - ' line3', - '+line6', - ' line3' - ], - theirs: [ - '-line3', - '-line3', - '-line5', - '+line4', - '+line4' - ] - } - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit (add+remove) on multiple lines with context', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + '-line3\n' - + ' line3\n' - + ' line5\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + '-line3\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - ' line2', - { - conflict: true, - mine: [ - '-line3' - ], - theirs: [ - '-line3', - '-line3', - '+line4', - '+line4' - ] - }, - ' line3', // TODO: Fix - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should conflict edit with remove in middle', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + ' line3\n' - + '-line3\n' - + ' line5\n'; - const theirs = - '@@ -1,3 +1,2 @@\n' - + ' line2\n' - + '-line3\n' - + '-line3\n' - + '+line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - '-line2', - { - conflict: true, - mine: [ - ' line3', - '-line3' - ], - theirs: [ - '-line3', - '-line3', - '+line4', - '+line4' - ] - }, - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle edit and addition with context connextion', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + '-line3\n' - + '-line4\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + ' line3\n' - + ' line4\n' - + '+line4\n'; - - const expected = { - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 2, - lines: [ - ' line2', - '-line3', - '-line4', - '+line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - - it('should merge removals that start in the leading section', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + '-line3\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + ' line4\n'; - const expected = { - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 1, - lines: [ - '-line2', - '-line3', - ' line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should conflict edits that start in the leading section', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '-line2\n' - + '-line3\n' - + '-line3\n' - + '-line3\n' - + '-line3\n' - + '+line4\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + ' line3\n' - + ' line3\n' - + '-line3\n' - + '-line3\n' - + ' line5\n'; - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - '-line2', - { - conflict: true, - mine: [ - '-line3', - '-line3', - '-line3', - '-line3', - '+line4' - ], - theirs: [ - ' line3', - ' line3', - '-line3', - '-line3' - ] - }, - ' line5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should conflict adds that start in the leading section', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + '+line2\n' - + '+line3\n'; - const theirs = - '@@ -2 +2,2 @@\n' - + '-line3\n' - + ' line4\n'; - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - '+line2', - { - conflict: true, - mine: [ - '+line3' - ], - theirs: [ - '-line3' - ] - }, - ' line4' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should conflict context mismatch', function() { - const mine = - '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n'; - const theirs = - '@@ -1 +1,2 @@\n' - + ' line3\n' - + ' line4\n'; - const expected = { - hunks: [ - { - conflict: true, - oldStart: 1, - newStart: 1, - lines: [ - { - conflict: true, - mine: [ - ' line2', - ' line3' - ], - theirs: [ - ' line3', - ' line4' - ] - } - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - - swapConflicts(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - }); - - it('should handle file name updates', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test2\theader2\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n'; - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test2', - newHeader: 'header2', - hunks: [] - }; - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - }); - it('should handle file name conflicts', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test-a\theader-a\n' - + '+++ test2\theader2\n'; - const theirs = - 'Index: test\n' - + '===================================================================\n' - + '--- test-b\theader-b\n' - + '+++ test3\theader3\n'; - const partialMatch = - 'Index: test\n' - + '===================================================================\n' - + '--- test-b\theader-a\n' - + '+++ test3\theader3\n'; - - expect(merge(mine, theirs)).to.eql({ - conflict: true, - index: 'test', - oldFileName: { - mine: 'test-a', - theirs: 'test-b' - }, - oldHeader: { - mine: 'header-a', - theirs: 'header-b' - }, - newFileName: { - mine: 'test2', - theirs: 'test3' - }, - newHeader: { - mine: 'header2', - theirs: 'header3' - }, - hunks: [] - }); - expect(merge(mine, partialMatch)).to.eql({ - conflict: true, - index: 'test', - oldFileName: { - mine: 'test-a', - theirs: 'test-b' - }, - oldHeader: 'header-a', - newFileName: { - mine: 'test2', - theirs: 'test3' - }, - newHeader: { - mine: 'header2', - theirs: 'header3' - }, - hunks: [] - }); - }); - it('should select available headers', function() { - const mine = - 'Index: test\n' - + '===================================================================\n' - + '--- test\theader1\n' - + '+++ test\theader2\n' - + '@@ -1,3 +1,4 @@\n' - + ' line2\n' - + ' line3\n' - + '+line4\n' - + ' line5\n'; - const theirs = - '@@ -25,3 +25,4 @@\n' - + ' foo2\n' - + ' foo3\n' - + '+foo4\n' - + ' foo5\n'; - - const expected = { - index: 'test', - oldFileName: 'test', - oldHeader: 'header1', - newFileName: 'test', - newHeader: 'header2', - hunks: [ - { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 4, - lines: [ - ' line2', - ' line3', - '+line4', - ' line5' - ] - }, - { - oldStart: 25, oldLines: 3, - newStart: 26, newLines: 4, - lines: [ - ' foo2', - ' foo3', - '+foo4', - ' foo5' - ] - } - ] - }; - - expect(merge(mine, theirs)).to.eql(expected); - expect(merge(theirs, mine)).to.eql(expected); - expect(merge(mine, parsePatch(theirs)[0])).to.eql(expected); - expect(merge(theirs, parsePatch(mine)[0])).to.eql(expected); - }); - - it('should diff from base', function() { - expect(merge('foo\nbar\nbaz\n', 'foo\nbaz\nbat\n', 'foo\nbaz\n')).to.eql({ - hunks: [ - { - oldStart: 1, oldLines: 2, - newStart: 1, newLines: 4, - lines: [ - ' foo', - '+bar', - ' baz', - '+bat' - ] - } - ] - }); - }); - it('should error if not passed base', function() { - expect(function() { - merge('foo', 'foo'); - }).to['throw']('Must provide a base reference or pass in a patch'); - }); - }); -}); - -function swapConflicts(expected) { - expected.hunks.forEach(function(hunk) { - hunk.lines.forEach(function(line) { - if (line.conflict) { - let tmp = line.mine; - line.mine = line.theirs; - line.theirs = tmp; - } - }); - }); -} diff --git a/test/patch/parse.js b/test/patch/parse.js index a7a9cb2d9..3fa88985b 100644 --- a/test/patch/parse.js +++ b/test/patch/parse.js @@ -1,4 +1,5 @@ -import {parsePatch} from '../../lib/patch/parse'; +import {parsePatch} from '../../libesm/patch/parse.js'; +import {createPatch} from '../../libesm/patch/create.js'; import {expect} from 'chai'; @@ -51,7 +52,7 @@ describe('patch/parse', function() { line3 +line4 line5 -@@ -4,3 +1,4 @@ +@@ -4,4 +1,3 @@ line2 line3 -line4 @@ -69,8 +70,8 @@ describe('patch/parse', function() { ] }, { - oldStart: 4, oldLines: 3, - newStart: 1, newLines: 4, + oldStart: 4, oldLines: 4, + newStart: 1, newLines: 3, lines: [ ' line2', ' line3', @@ -171,16 +172,439 @@ Index: test2 }]); }); + it('should parse the header format used by Mercurial', function() { + // (At least, Mercurial is the only tool I could find that uses this + // format. Claude was unable to suggest any other tool that would produce + // this format, and I don't know of any either. See + // https://claude.ai/share/51e202d0-9da0-4dfa-a4a4-d6c6b476b300.) + // + // Support for this got added by Kevin in commit: + // 0c9dd6d0e622d8a32b441b45baa797a7e86a4c55 + // + // I find it a bit odd that (at the time of adding this test) our header + // parsing has special handling for Mercurial's diff format but does not + // support Git's format (given Git is much more popular). I also find it + // a bit odd that we discard the information in the header about what + // revisions are being diffed and preserve only the filename (which is + // available anyway via the lines below, and exposed by us in the + // oldFileName and newFileName fields). But for now I am just trying to + // document and test the current state of things. + // + // -- ExplodingCabbage + + // (Patch below was produced by running `hg diff -r 0 -r 1` in the + // Mercurial repo for Mercurial itself.) + const patchStr = `diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/.hgignore Tue May 03 13:27:13 2005 -0800 +@@ -0,0 +1,1 @@ ++.*~ +diff -r 9117c6561b0b -r 273ce12ad8f1 README +--- a/README Tue May 03 13:16:10 2005 -0800 ++++ b/README Tue May 03 13:27:13 2005 -0800 +@@ -69,6 +69,10 @@ + + Network support (highly experimental): + ++ # pull the self-hosting hg repo ++ foo$ hg init ++ foo$ hg merge http://selenic.com/hg/ ++ + # export your .hg directory as a directory on your webserver + foo$ ln -s .hg ~/public_html/hg-linux + +@@ -76,5 +80,10 @@ + bar$ hg merge http://foo/~user/hg-linux + + This is just a proof of concept of grabbing byte ranges, and is not +- expected to perform well. ++ expected to perform well. Fixing this needs some pipelining to reduce ++ the number of round trips. See zsync for a similar approach. + ++ Another approach which does perform well right now is to use rsync. ++ Simply rsync the remote repo to a read-only local copy and then do a ++ local pull. ++ +`; + + const patchObj = parsePatch(patchStr); + expect(patchObj).to.deep.equals([ + { + // Parsed from line `diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore`: + index: '.hgignore', + // Parsed from line `--- /dev/null Thu Jan 01 00:00:00 1970 +0000`: + oldFileName: '/dev/null', + oldHeader: 'Thu Jan 01 00:00:00 1970 +0000', + // Parsed from line `+++ b/.hgignore Tue May 03 13:27:13 2005 -0800`: + newFileName: 'b/.hgignore', + newHeader: 'Tue May 03 13:27:13 2005 -0800', + hunks: [ + { + oldStart: 1, + oldLines: 0, + newStart: 1, + newLines: 1, + lines: [ + '+.*~' + ] + } + ] + }, + { + index: 'README', + oldFileName: 'a/README', + oldHeader: 'Tue May 03 13:16:10 2005 -0800', + newFileName: 'b/README', + newHeader: 'Tue May 03 13:27:13 2005 -0800', + hunks: [ + { + oldStart: 69, + oldLines: 6, + newStart: 69, + newLines: 10, + lines: [ + '', + ' Network support (highly experimental):', + '', + '+ # pull the self-hosting hg repo', + '+ foo$ hg init', + '+ foo$ hg merge http://selenic.com/hg/', + '+', + ' # export your .hg directory as a directory on your webserver', + ' foo$ ln -s .hg ~/public_html/hg-linux', + '' + ] + }, + { + oldStart: 76, + oldLines: 5, + newStart: 80, + newLines: 10, + lines: [ + ' bar$ hg merge http://foo/~user/hg-linux', + '', + ' This is just a proof of concept of grabbing byte ranges, and is not', + '- expected to perform well.', + '+ expected to perform well. Fixing this needs some pipelining to reduce', + '+ the number of round trips. See zsync for a similar approach.', + '', + '+ Another approach which does perform well right now is to use rsync.', + '+ Simply rsync the remote repo to a read-only local copy and then do a', + '+ local pull.', + '+' + ] + } + ] + } + ]); + }); + + it('should parse multi-file output from GNU `diff -u -r`', function() { + // GNU `diff -u -r` (recursive unified diff) produces a `diff -u -r ...` + // header before each file. + expect(parsePatch( +`diff -u -r old/file1.txt new/file1.txt +--- old/file1.txt\t2026-01-01 00:00:00.000000000 +0000 ++++ new/file1.txt\t2026-01-01 00:00:00.000000000 +0000 +@@ -1,3 +1,4 @@ + alpha ++beta + gamma + delta +diff -u -r old/file2.txt new/file2.txt +--- old/file2.txt\t2026-01-01 00:00:00.000000000 +0000 ++++ new/file2.txt\t2026-01-01 00:00:00.000000000 +0000 +@@ -1,3 +1,3 @@ + one +-two ++TWO + three`)) + .to.eql([{ + oldFileName: 'old/file1.txt', + oldHeader: '2026-01-01 00:00:00.000000000 +0000', + newFileName: 'new/file1.txt', + newHeader: '2026-01-01 00:00:00.000000000 +0000', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' alpha', + '+beta', + ' gamma', + ' delta' + ] + } + ] + }, { + oldFileName: 'old/file2.txt', + oldHeader: '2026-01-01 00:00:00.000000000 +0000', + newFileName: 'new/file2.txt', + newHeader: '2026-01-01 00:00:00.000000000 +0000', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 3, + lines: [ + ' one', + '-two', + '+TWO', + ' three' + ] + } + ] + }]); + }); + + it('should tolerate `Only in` lines from GNU `diff -r`', function() { + // When comparing directories, `diff -r` emits `Only in : ` + // for files that exist on only one side. These appear between file diffs. + expect(parsePatch( +`diff -u -r old/file1.txt new/file1.txt +--- old/file1.txt\t2026-01-01 ++++ new/file1.txt\t2026-01-01 +@@ -1,2 +1,3 @@ + alpha + beta ++gamma +Only in old: removed.txt +Only in new: added.txt +diff -u -r old/file2.txt new/file2.txt +--- old/file2.txt\t2026-01-01 ++++ new/file2.txt\t2026-01-01 +@@ -1,3 +1,3 @@ + one +-two ++TWO + three`)) + .to.have.length(2); + }); + + it('should tolerate SVN property change sections between file diffs', function() { + // `svn diff` can emit `Property changes on:` sections after a file's + // hunks. These are not part of the unified diff format but appear + // interspersed with it in SVN output. + expect(parsePatch( +`Index: file.txt +=================================================================== +--- file.txt\t(revision 1) ++++ file.txt\t(working copy) +@@ -1,3 +1,4 @@ + alpha ++beta + gamma + delta + +Property changes on: file.txt +___________________________________________________________________ +Added: svn:eol-style +## -0,0 +1 ## ++native +Index: other.txt +=================================================================== +--- other.txt\t(revision 1) ++++ other.txt\t(working copy) +@@ -1 +1 @@ +-old ++new`)) + .to.eql([{ + index: 'file.txt', + oldFileName: 'file.txt', + oldHeader: '(revision 1)', + newFileName: 'file.txt', + newHeader: '(working copy)', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' alpha', + '+beta', + ' gamma', + ' delta' + ] + } + ] + }, { + index: 'other.txt', + oldFileName: 'other.txt', + oldHeader: '(revision 1)', + newFileName: 'other.txt', + newHeader: '(working copy)', + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: [ + '-old', + '+new' + ] + } + ] + }]); + }); + + it('should tolerate trailing garbage after the last hunk', function() { + // GNU `patch` ignores trailing content that doesn't look like part of + // a patch, and so should we. + expect(parsePatch( +`--- file.txt ++++ file.txt +@@ -1,3 +1,4 @@ + alpha ++beta + gamma + delta +This is trailing garbage +More garbage here`)) + .to.eql([{ + oldFileName: 'file.txt', + oldHeader: '', + newFileName: 'file.txt', + newHeader: '', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' alpha', + '+beta', + ' gamma', + ' delta' + ] + } + ] + }]); + }); + + it('should tolerate garbage between file headers and hunks', function() { + // GNU `patch` ignores unrecognized lines between the --- / +++ headers + // and the first @@ hunk header, and so do we. + expect(parsePatch( +`--- file.txt ++++ file.txt +some garbage here +more garbage +@@ -1,3 +1,4 @@ + alpha ++beta + gamma + delta`)) + .to.eql([{ + oldFileName: 'file.txt', + oldHeader: '', + newFileName: 'file.txt', + newHeader: '', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' alpha', + '+beta', + ' gamma', + ' delta' + ] + } + ] + }]); + }); + + it('should parse multiple files without the Index line', function() { + expect(parsePatch( +`--- from\theader1 ++++ to\theader2 +@@ -1,3 +1,4 @@ + line2 + line3 ++line4 + line5 +--- from\theader1 ++++ to\theader2 +@@ -1,3 +1,4 @@ + line2 + line3 ++line4 + line5`)) + .to.eql([{ + oldFileName: 'from', + oldHeader: 'header1', + newFileName: 'to', + newHeader: 'header2', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' line2', + ' line3', + '+line4', + ' line5' + ] + } + ] + }, { + oldFileName: 'from', + oldHeader: 'header1', + newFileName: 'to', + newHeader: 'header2', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' line2', + ' line3', + '+line4', + ' line5' + ] + } + ] + }]); + }); + + it('should parse patches with filenames having quotes and back slashes', function() { + expect(parsePatch( +`Index: test +=================================================================== +--- "from\\\\a\\\\file\\\\with\\\\quotes\\\\and\\\\backslash"\theader1 ++++ "to\\\\a\\\\file\\\\with\\\\quotes\\\\and\\\\backslash"\theader2 +@@ -1,3 +1,4 @@ + line2 + line3 ++line4 + line5`)) + .to.eql([{ + index: 'test', + oldFileName: 'from\\a\\file\\with\\quotes\\and\\backslash', + oldHeader: 'header1', + newFileName: 'to\\a\\file\\with\\quotes\\and\\backslash', + newHeader: 'header2', + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' line2', + ' line3', + '+line4', + ' line5' + ] + } + ] + }]); + }); + it('should note added EOFNL', function() { expect(parsePatch( -`@@ -1,3 +1,4 @@ +`@@ -1,1 +0,0 @@ -line5 \\ No newline at end of file`)) .to.eql([{ hunks: [ { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 4, + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 0, lines: [ '-line5', '\\ No newline at end of file' @@ -191,14 +615,14 @@ Index: test2 }); it('should note removed EOFNL', function() { expect(parsePatch( -`@@ -1,3 +1,4 @@ +`@@ -0,0 +1 @@ +line5 \\ No newline at end of file`)) .to.eql([{ hunks: [ { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 4, + oldStart: 1, oldLines: 0, + newStart: 1, newLines: 1, lines: [ '+line5', '\\ No newline at end of file' @@ -209,15 +633,15 @@ Index: test2 }); it('should ignore context no EOFNL', function() { expect(parsePatch( -`@@ -1,3 +1,4 @@ +`@@ -1 +1,2 @@ +line4 line5 \\ No newline at end of file`)) .to.eql([{ hunks: [ { - oldStart: 1, oldLines: 3, - newStart: 1, newLines: 4, + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 2, lines: [ '+line4', ' line5', @@ -228,28 +652,905 @@ Index: test2 }]); }); + it('should throw if --- and +++ file headers are not paired', function() { + expect(function() { + parsePatch('Index: foo\n+++ bar\nblah'); + }).to['throw']('Missing "--- ..." file header for bar'); + expect(function() { + parsePatch('--- bar\n@@ -1 +1 @@\n-old\n+new'); + }).to['throw']('Missing "+++ ..." file header for bar'); + }); + it('should perform sanity checks on line numbers', function() { - parsePatch(`@@ -1 +1 @@`, {strict: true}); + parsePatch('@@ -1 +1 @@'); expect(function() { - parsePatch(`@@ -1 +1,4 @@`, {strict: true}); + parsePatch('@@ -1 +1,4 @@'); }).to['throw']('Added line count did not match for hunk at line 1'); expect(function() { - parsePatch(`@@ -1,4 +1 @@`, {strict: true}); + parsePatch('@@ -1,4 +1 @@'); }).to['throw']('Removed line count did not match for hunk at line 1'); }); - it('should not throw on invalid input', function() { - expect(parsePatch('blit\nblat\nIndex: foo\nfoo')) + + + it('should handle OOM case', function() { + parsePatch('Index: \n===================================================================\n--- \n+++ \n@@ -1,1 +1,2 @@\n-1\n\\ No newline at end of file\n+1\n+2\n'); + }); + + it('should treat vertical tabs like ordinary characters', function() { + // Patch below was generated as follows: + // 1. From Node, run: + // fs.writeFileSync("foo", "foo\nbar\vbar\nbaz\nqux") + // fs.writeFileSync("bar", "foo\nbarry\vbarry\nbaz\nqux") + // 2. From shell, run: + // diff -u foo bar > diff.txt + // 3. From Node, run + // fs.readFileSync("diff.txt") + // and copy the string literal you get. + // This patch illustrates how the Unix `diff` and `patch` tools handle + // characters like vertical tabs - namely, they simply treat them as + // ordinary characters, NOT as line breaks. JsDiff used to treat them as + // line breaks but this breaks its parsing of patches like this one; this + // test was added to demonstrate the brokenness and prevent us from + // reintroducing it. + const patch = '--- foo\t2023-12-20 16:11:20.908225554 +0000\n' + + '+++ bar\t2023-12-20 16:11:34.391473579 +0000\n' + + '@@ -1,4 +1,4 @@\n' + + ' foo\n' + + '-bar\x0Bbar\n' + + '+barry\x0Bbarry\n' + + ' baz\n' + + ' qux\n' + + '\\ No newline at end of file\n'; + + expect(parsePatch(patch)).to.eql([ + { + oldFileName: 'foo', + oldHeader: '2023-12-20 16:11:20.908225554 +0000', + newFileName: 'bar', + newHeader: '2023-12-20 16:11:34.391473579 +0000', + hunks: [ + { + oldStart: 1, + oldLines: 4, + newStart: 1, + newLines: 4, + lines: [ + ' foo', + '-bar\x0Bbar', + '+barry\x0Bbarry', + ' baz', + ' qux', + '\\ No newline at end of file' + ] + } + ] + } + ]); + }); + + it('should treat vertical tabs in a way consistent with createPatch', function() { + // This is basically the same as the test above, but this time we create + // the patch USING JsDiff instead of testing one created with Unix diff + const patch = createPatch('foo', 'foo\nbar\vbar\nbaz\nqux', 'foo\nbarry\vbarry\nbaz\nqux'); + + expect(parsePatch(patch)).to.eql([ + { + oldFileName: 'foo', + oldHeader: '', + newFileName: 'foo', + newHeader: '', + index: 'foo', + hunks: [ + { + oldStart: 1, + oldLines: 4, + newStart: 1, + newLines: 4, + lines: [ + ' foo', + '-bar\x0Bbar', + '+barry\x0Bbarry', + ' baz', + ' qux', + '\\ No newline at end of file' + ] + } + ] + } + ]); + }); + + it('should treat non-ASCII line break characters \\u2028 and \\u2029 like ordinary characters', () => { + // Regression test for nasty denial-of-service vulnerability fixed by + // https://github.com/kpdecker/jsdiff/pull/649 + const patch = 'Index: t\u2028e\u2028s\u2028t\n' + + '--- f\u2028o\u2028o\t2023-12-20\u202816:11:20.908225554\u2028+0000\u2028\n' + + '+++ b\u2028a\u2028r\t2023-12-20\u202816:11:34.391473579\u2028+0000\u2028\n' + + '@@ -1,4 +1,4 @@\n' + + ' foo\n' + + '-bar\u2028bar\n' + + '+barry\u2028barry\n' + + ' baz\n' + + ' qux\n' + + '\\ No newline at end of file\n'; + expect(parsePatch(patch)).to.eql([ + { + oldFileName: 'f\u2028o\u2028o', + oldHeader: '2023-12-20\u202816:11:20.908225554\u2028+0000', + newFileName: 'b\u2028a\u2028r', + newHeader: '2023-12-20\u202816:11:34.391473579\u2028+0000', + index: 't\u2028e\u2028s\u2028t', + hunks: [ + { + oldStart: 1, + oldLines: 4, + newStart: 1, + newLines: 4, + lines: [ + ' foo', + '-bar\u2028bar', + '+barry\u2028barry', + ' baz', + ' qux', + '\\ No newline at end of file' + ] + } + ] + } + ]); + }); + + it('should tolerate patches with extra trailing newlines after hunks', () => { + // Regression test for https://github.com/kpdecker/jsdiff/issues/524 + // Not only are these considered valid by GNU patch, but jsdiff's own formatPatch method + // emits patches like this, which jsdiff used to then be unable to parse! + const patchStr = `--- foo\t2024-06-14 22:16:31.444276792 +0100 ++++ bar\t2024-06-14 22:17:14.910611219 +0100 +@@ -1,7 +1,7 @@ + first + second + third +-fourth +-fifth ++vierte ++fünfte + sixth + seventh + +`; + expect(parsePatch(patchStr)).to.eql([{ + oldFileName: 'foo', + oldHeader: '2024-06-14 22:16:31.444276792 +0100', + newFileName: 'bar', + newHeader: '2024-06-14 22:17:14.910611219 +0100', + hunks: [ + { + oldStart: 1, + oldLines: 7, + newStart: 1, + newLines: 7, + lines: [ + ' first', + ' second', + ' third', + '-fourth', + '-fifth', + '+vierte', + '+fünfte', + ' sixth', + ' seventh' + ] + } + ] + }]); + }); + + it("shouldn't be caught out by removal/addition of lines starting with -- or ++", () => { + // The patch below is a valid patch generated by diffing this file, foo: + + // first + // second + // third + // -- bla + // fifth + // sixth + + // against this file, bar: + + // first + // second + // third + // ++ bla + // fifth + // sixth + // seventh + + // with the command `diff -u0 foo bar`. (All lines in `foo` and `bar` have no leading + // whitespace and a trailing LF.) + + // This is effectively an adversarial example meant to catch out a parser that tries to + // detect the end of a file in a multi-file diff by looking for lines starting with '---', + // '+++', and then '@@'. jsdiff used to do this. However, as this example illustrates, it is + // unsound, since the '---' and '+++' lines might actually just represent the deletion and + // insertion of lines starting with '--' and '++'. The only way to disambiguate these + // interpretations is to heed the line counts in the @@ hunk headers; you *cannot* reliably + // determine where a hunk or file ends in a unified diff patch without heeding those line + // counts. + + const patchStr = `--- foo\t2024-06-14 21:57:04.341065736 +0100 ++++ bar\t2024-06-14 22:00:57.988080321 +0100 +@@ -4 +4 @@ +--- bla ++++ bla +@@ -6,0 +7 @@ ++seventh +`; + + expect(parsePatch(patchStr)).to.eql([{ + oldFileName: 'foo', + oldHeader: '2024-06-14 21:57:04.341065736 +0100', + newFileName: 'bar', + newHeader: '2024-06-14 22:00:57.988080321 +0100', + hunks: [ + { oldStart: 4, oldLines: 1, newStart: 4, newLines: 1, lines: ['--- bla', '+++ bla'] }, + { oldStart: 7, oldLines: 0, newStart: 7, newLines: 1, lines: ['+seventh'] } + ] + }]); + }); + + it('should emit an error if a hunk has wrong line counts causing extra hunk-body lines to spill out', () => { + // The hunk declares oldLines=2, newLines=2, so the parser consumes + // ' a', '-b', '+B' and then stops. The next line ' c' starts with + // a space (context line) immediately after the hunk ended, which + // indicates the line counts were wrong. + const patchStr = `--- file.txt ++++ file.txt +@@ -1,2 +1,2 @@ + a +-b ++B + c + d + e`; + + // eslint-disable-next-line dot-notation + expect(() => {parsePatch(patchStr);}).to.throw(/Hunk at line 3 has more lines than expected/); + }); + + it('should emit an error if a hunk contains an invalid line', () => { + // Within a hunk, every line must either start with '+' (insertion), '-' (deletion), + // ' ' (context line, i.e. not deleted nor inserted) or a backslash (for + // '\\ No newline at end of file' lines). Seeing anything else before the end of the hunk is + // an error. + + const patchStr = `Index: test +=================================================================== +--- from\theader1 ++++ to\theader2 +@@ -1,3 +1,4 @@ + line2 +line3 ++line4 + line5`; + + // eslint-disable-next-line dot-notation + expect(() => {parsePatch(patchStr);}).to.throw('Hunk at line 5 contained invalid line line3'); + }); + + it('should parse a single-file `diff --git` patch', function() { + expect(parsePatch( +`diff --git a/file.txt b/file.txt +index abc1234..def5678 100644 +--- a/file.txt ++++ b/file.txt +@@ -1,3 +1,4 @@ + line1 + line2 ++line3 + line4`)) + .to.eql([{ + oldFileName: 'a/file.txt', + oldHeader: '', + newFileName: 'b/file.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' line1', + ' line2', + '+line3', + ' line4' + ] + } + ] + }]); + }); + + it('should parse a multi-file `diff --git` patch', function() { + expect(parsePatch( +`diff --git a/file1.txt b/file1.txt +index abc1234..def5678 100644 +--- a/file1.txt ++++ b/file1.txt +@@ -1,3 +1,4 @@ + line1 + line2 ++line3 + line4 +diff --git a/file2.txt b/file2.txt +index 1234567..abcdef0 100644 +--- a/file2.txt ++++ b/file2.txt +@@ -1,3 +1,4 @@ + lineA + lineB ++lineC + lineD`)) + .to.eql([{ + oldFileName: 'a/file1.txt', + oldHeader: '', + newFileName: 'b/file1.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' line1', + ' line2', + '+line3', + ' line4' + ] + } + ] + }, { + oldFileName: 'a/file2.txt', + oldHeader: '', + newFileName: 'b/file2.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' lineA', + ' lineB', + '+lineC', + ' lineD' + ] + } + ] + }]); + }); + + it('should parse a `diff --git` rename with no content change', function() { + expect(parsePatch( +`diff --git a/README.md b/README-2.md +similarity index 100% +rename from README.md +rename to README-2.md`)) + .to.eql([{ + oldFileName: 'a/README.md', + newFileName: 'b/README-2.md', + isGit: true, + hunks: [], + isRename: true + }]); + }); + + it('should parse a `diff --git` rename with content change', function() { + expect(parsePatch( +`diff --git a/old-name.txt b/new-name.txt +similarity index 85% +rename from old-name.txt +rename to new-name.txt +index abc1234..def5678 100644 +--- a/old-name.txt ++++ b/new-name.txt +@@ -1,3 +1,4 @@ + line1 + line2 ++line3 + line4`)) + .to.eql([{ + oldFileName: 'a/old-name.txt', + oldHeader: '', + newFileName: 'b/new-name.txt', + newHeader: '', + isGit: true, + isRename: true, + hunks: [ + { + oldStart: 1, oldLines: 3, + newStart: 1, newLines: 4, + lines: [ + ' line1', + ' line2', + '+line3', + ' line4' + ] + } + ] + }]); + }); + + it('should parse a `diff --git` mode-only change', function() { + expect(parsePatch( +`diff --git a/script.sh b/script.sh +old mode 100644 +new mode 100755`)) + .to.eql([{ + oldFileName: 'a/script.sh', + newFileName: 'b/script.sh', + isGit: true, + oldMode: '100644', + newMode: '100755', + hunks: [] + }]); + }); + + it('should parse a `diff --git` binary file change', function() { + expect(parsePatch( +`diff --git a/image.png b/image.png +index abc1234..def5678 100644 +Binary files a/image.png and b/image.png differ`)) + .to.eql([{ + oldFileName: 'a/image.png', + newFileName: 'b/image.png', + isGit: true, + isBinary: true, + hunks: [] + }]); + }); + + it('should not lose files when a hunkless `diff --git` file is followed by one with hunks', function() { + expect(parsePatch( +`diff --git a/file1.txt b/file1.txt +--- a/file1.txt ++++ b/file1.txt +@@ -1 +1 @@ +-old ++new +diff --git a/image.png b/image.png +Binary files a/image.png and b/image.png differ +diff --git a/file3.txt b/file3.txt +--- a/file3.txt ++++ b/file3.txt +@@ -1 +1 @@ +-foo ++bar`)) + .to.eql([{ + oldFileName: 'a/file1.txt', + oldHeader: '', + newFileName: 'b/file1.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + } + ] + }, { + oldFileName: 'a/image.png', + newFileName: 'b/image.png', + isGit: true, + isBinary: true, + hunks: [] + }, { + oldFileName: 'a/file3.txt', + oldHeader: '', + newFileName: 'b/file3.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-foo', '+bar'] + } + ] + }]); + }); + + it('should parse a `diff --git` copy', function() { + expect(parsePatch( +`diff --git a/original.txt b/copy.txt +similarity index 100% +copy from original.txt +copy to copy.txt`)) + .to.eql([{ + oldFileName: 'a/original.txt', + newFileName: 'b/copy.txt', + isGit: true, + hunks: [], + isCopy: true + }]); + }); + + it('should parse a `diff --git` new file', function() { + expect(parsePatch( +`diff --git a/newfile.txt b/newfile.txt +new file mode 100644 +index 0000000..abc1234 +--- /dev/null ++++ b/newfile.txt +@@ -0,0 +1,2 @@ ++hello ++world`)) + .to.eql([{ + oldFileName: '/dev/null', + oldHeader: '', + newFileName: 'b/newfile.txt', + newHeader: '', + isGit: true, + isCreate: true, + newMode: '100644', + hunks: [ + { + oldStart: 1, oldLines: 0, + newStart: 1, newLines: 2, + lines: ['+hello', '+world'] + } + ] + }]); + }); + + it('should parse a `diff --git` deleted file', function() { + expect(parsePatch( +`diff --git a/old.txt b/old.txt +deleted file mode 100644 +index ce01362..0000000 +--- a/old.txt ++++ /dev/null +@@ -1 +0,0 @@ +-goodbye`)) + .to.eql([{ + oldFileName: 'a/old.txt', + oldHeader: '', + newFileName: '/dev/null', + newHeader: '', + isGit: true, + isDelete: true, + oldMode: '100644', + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 0, + lines: ['-goodbye'] + } + ] + }]); + }); + + it('should parse a `diff --git` empty file creation (no --- / +++ or hunks)', function() { + expect(parsePatch( +`diff --git a/empty.txt b/empty.txt +new file mode 100644 +index 0000000..e69de29`)) + .to.eql([{ + oldFileName: 'a/empty.txt', + newFileName: 'b/empty.txt', + isGit: true, + isCreate: true, + newMode: '100644', + hunks: [] + }]); + }); + + it('should parse a `diff --git` empty file deletion (no --- / +++ or hunks)', function() { + expect(parsePatch( +`diff --git a/empty.txt b/empty.txt +deleted file mode 100644 +index e69de29..0000000`)) + .to.eql([{ + oldFileName: 'a/empty.txt', + newFileName: 'b/empty.txt', + isGit: true, + isDelete: true, + oldMode: '100644', + hunks: [] + }]); + }); + + it('should unquote C-style quoted filenames in rename from/to', function() { + expect(parsePatch( +`diff --git "a/file\\twith\\ttabs.txt" b/normal.txt +similarity index 100% +rename from "file\\twith\\ttabs.txt" +rename to normal.txt`)) + .to.eql([{ + oldFileName: 'a/file\twith\ttabs.txt', + newFileName: 'b/normal.txt', + isGit: true, + hunks: [], + isRename: true + }]); + }); + + it('should handle all Git C-style escape sequences in quoted filenames', function() { + expect(parsePatch( +`diff --git "a/\\a\\b\\f\\r\\v\\001file.txt" "b/\\a\\b\\f\\r\\v\\001file.txt" +old mode 100644 +new mode 100755`)) + .to.eql([{ + oldFileName: 'a/\x07\b\f\r\v\x01file.txt', + newFileName: 'b/\x07\b\f\r\v\x01file.txt', + isGit: true, + oldMode: '100644', + newMode: '100755', + hunks: [] + }]); + }); + + it('should handle multi-byte UTF-8 octal escapes in quoted filenames', function() { + // 🎉 is U+1F389, UTF-8 bytes F0 9F 8E 89 = octal 360 237 216 211 + expect(parsePatch( +`diff --git "a/caf\\303\\251-file\\360\\237\\216\\211.txt" "b/caf\\303\\251-file\\360\\237\\216\\211.txt" +new file mode 100644 +index 0000000..ce01362 +--- /dev/null ++++ "b/caf\\303\\251-file\\360\\237\\216\\211.txt" +@@ -0,0 +1 @@ ++hello`)) + .to.eql([{ + oldFileName: '/dev/null', + oldHeader: '', + newFileName: 'b/café-file🎉.txt', + newHeader: '', + isGit: true, + isCreate: true, + newMode: '100644', + hunks: [ + { + oldStart: 1, oldLines: 0, + newStart: 1, newLines: 1, + lines: ['+hello'] + } + ] + }]); + }); + + it('should parse `diff --git` with unquoted filenames containing spaces (same old and new)', function() { + expect(parsePatch( +`diff --git a/file with spaces.txt b/file with spaces.txt +old mode 100644 +new mode 100755`)) + .to.eql([{ + oldFileName: 'a/file with spaces.txt', + newFileName: 'b/file with spaces.txt', + isGit: true, + oldMode: '100644', + newMode: '100755', + hunks: [] + }]); + }); + + it('should parse `diff --git` rename with unquoted filenames containing spaces', function() { + // Typical, easy case where the `diff --git` line is unambiguous. + // See a later test for the pathological case. + expect(parsePatch( +`diff --git a/file with spaces.txt b/another file with spaces.txt +similarity index 100% +rename from file with spaces.txt +rename to another file with spaces.txt`)) + .to.eql([{ + oldFileName: 'a/file with spaces.txt', + newFileName: 'b/another file with spaces.txt', + isGit: true, + hunks: [], + isRename: true + }]); + }); + + it('should handle `diff --git` with a filename containing " b/"', function() { + // The filename literally contains " b/" which is also the separator + // between the old and new paths. Since old === new, the parser can + // find the unique split where both halves match. + expect(parsePatch( +`diff --git a/x b/y.txt b/x b/y.txt +old mode 100644 +new mode 100755`)) + .to.eql([{ + oldFileName: 'a/x b/y.txt', + newFileName: 'b/x b/y.txt', + isGit: true, + oldMode: '100644', + newMode: '100755', + hunks: [] + }]); + }); + + it('should handle `diff --git` rename where filenames contain " b/"', function() { + // The diff --git line "diff --git a/x b/y b/z" is ambiguous: it + // could be split as old="a/x" new="b/y b/z" or old="a/x b/y" + // new="b/z". We parse two patches with the SAME diff --git line + // but different rename from/rename to headers to prove the + // extended headers win and correctly disambiguate the split. + + // Split interpretation 1: old="a/x", new="b/y b/z" + expect(parsePatch( +`diff --git a/x b/y b/z +similarity index 100% +rename from x +rename to y b/z`)) + .to.eql([{ + oldFileName: 'a/x', + newFileName: 'b/y b/z', + isGit: true, + hunks: [], + isRename: true + }]); + + // Split interpretation 2: old="a/x b/y", new="b/z" + expect(parsePatch( +`diff --git a/x b/y b/z +similarity index 100% +rename from x b/y +rename to z`)) + .to.eql([{ + oldFileName: 'a/x b/y', + newFileName: 'b/z', + isGit: true, + hunks: [], + isRename: true + }]); + }); + + it('should handle `diff --git` rename where filenames contain " b/", without rename from/to', function() { + // Same ambiguous `diff --git` line as previous test, but here + // disambiguated by the ---/+++ headers. + + // Split interpretation 1: old="a/x", new="b/y b/z" + expect(parsePatch( +`diff --git a/x b/y b/z +--- a/x ++++ b/y b/z +@@ -1 +1 @@ +-hello ++world`)) + .to.eql([{ + oldFileName: 'a/x', + oldHeader: '', + newFileName: 'b/y b/z', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-hello', '+world'] + } + ] + }]); + + // Split interpretation 2: old="a/x b/y", new="b/z" + expect(parsePatch( +`diff --git a/x b/y b/z +--- a/x b/y ++++ b/z +@@ -1 +1 @@ +-hello ++world`)) + .to.eql([{ + oldFileName: 'a/x b/y', + oldHeader: '', + newFileName: 'b/z', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-hello', '+world'] + } + ] + }]); + }); + + describe('unparseable `diff --git headers', function() { + // So far as we know, Git never actually produces diff --git headers that + // can't be parsed (e.g. with unterminated quotes or missing a/b prefixes). + // But we test these cases to confirm parsePatch doesn't crash and instead + // gracefully falls back to getting filenames from --- / +++ lines. + it('should handle an unparseable `diff --git` header with unterminated quote', function() { + expect(parsePatch( + `diff --git "a/unterminated +--- a/file.txt ++++ b/file.txt +@@ -1 +1 @@ +-old ++new`)) .to.eql([{ - hunks: [], - index: 'foo' + oldFileName: 'a/file.txt', + oldHeader: '', + newFileName: 'b/file.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + } + ] }]); - }); - it('should throw on invalid input in strict mode', function() { - expect(function() { - parsePatch('Index: foo\nfoo', {strict: true}); - }).to['throw'](/Unknown line 2 "foo"/); + }); + + it('should handle an unparseable `diff --git` header with no a/b prefixes', function() { + expect(parsePatch( + `diff --git file.txt file.txt +--- a/file.txt ++++ b/file.txt +@@ -1 +1 @@ +-old ++new`)) + .to.eql([{ + oldFileName: 'a/file.txt', + oldHeader: '', + newFileName: 'b/file.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + } + ] + }]); + }); + + + it('should handle an incomplete octal escape in a quoted `diff --git` filename', function() { + // The quoted filename has a truncated octal escape (\36 instead of \360). + // parseQuotedFileName should return null, so parseGitDiffHeader returns + // null and we fall back to --- / +++ lines for filenames. + expect(parsePatch( + `diff --git "a/file\\36" "b/file\\36" +--- a/file.txt ++++ b/file.txt +@@ -1 +1 @@ +-old ++new`)) + .to.eql([{ + oldFileName: 'a/file.txt', + oldHeader: '', + newFileName: 'b/file.txt', + newHeader: '', + isGit: true, + hunks: [ + { + oldStart: 1, oldLines: 1, + newStart: 1, newLines: 1, + lines: ['-old', '+new'] + } + ] + }]); + }); + + it('should handle an unparseable `diff --git` header with no --- or +++ fallback', function() { + // When both the `diff --git` header is unparseable AND there are no + // --- / +++ lines, filenames remain undefined. + expect(parsePatch( + `diff --git file.txt file.txt +old mode 100644 +new mode 100755`)) + .to.eql([{ + isGit: true, + oldMode: '100644', + newMode: '100755', + hunks: [] + }]); + }); }); }); }); diff --git a/test/patch/readme-rename-example.js b/test/patch/readme-rename-example.js new file mode 100644 index 000000000..a9b2e103e --- /dev/null +++ b/test/patch/readme-rename-example.js @@ -0,0 +1,240 @@ +import {applyPatches} from '../../libesm/patch/apply.js'; + +import {expect} from 'chai'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +describe('README Git rename example', function() { + let tmpDir; + let originalCwd; + + beforeEach(function() { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsdiff-readme-test-')); + originalCwd = process.cwd(); + process.chdir(tmpDir); + }); + + afterEach(function() { + process.chdir(originalCwd); + fs.rmSync(tmpDir, {recursive: true, force: true}); + }); + + /** + * Extract the Git rename example code from the README and return it as a + * function that takes (applyPatches, patch, fs) and runs the example. + */ + const applyGitPatch = (function() { + const readme = fs.readFileSync( + path.join(__dirname, '../../README.md'), + 'utf-8' + ); + + // Find the heading + const headingIndex = readme.indexOf('##### Applying a multi-file Git patch'); + if (headingIndex === -1) { + throw new Error('Could not find the Git rename example heading in README.md'); + } + + // Find the code block after the heading + const afterHeading = readme.substring(headingIndex); + const codeBlockStart = afterHeading.indexOf('\n```\n'); + if (codeBlockStart === -1) { + throw new Error('Could not find the code block in the Git rename example'); + } + const codeStart = codeBlockStart + 4; // skip past the \n```\n + const codeBlockEnd = afterHeading.indexOf('\n```\n', codeStart); + if (codeBlockEnd === -1) { + throw new Error('Could not find the end of the code block in the Git rename example'); + } + + let code = afterHeading.substring(codeStart, codeBlockEnd); + + // Strip the require line — we'll provide applyPatches as an argument. + // Strip the fs.readFileSync for the patch — we'll provide patch as an argument. + code = code + .replace(/const \{applyPatches\}.*\n/, '') + .replace(/const patch = .*\n/, ''); + + // eslint-disable-next-line no-new-func + return new Function('applyPatches', 'patch', 'fs', code); + }()); + + it('should handle a simple rename with content change', function() { + fs.writeFileSync('old.txt', 'line1\nline2\nline3\n'); + + const patch = +`diff --git a/old.txt b/new.txt +similarity index 80% +rename from old.txt +rename to new.txt +--- a/old.txt ++++ b/new.txt +@@ -1,3 +1,3 @@ + line1 +-line2 ++line2modified + line3 +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.existsSync('old.txt')).to.equal(false); + expect(fs.readFileSync('new.txt', 'utf-8')) + .to.equal('line1\nline2modified\nline3\n'); + }); + + it('should handle a swap rename (a→b, b→a)', function() { + fs.writeFileSync('a.txt', 'content of a\n'); + fs.writeFileSync('b.txt', 'content of b\n'); + + const patch = +`diff --git a/a.txt b/b.txt +similarity index 100% +rename from a.txt +rename to b.txt +diff --git a/b.txt b/a.txt +similarity index 100% +rename from b.txt +rename to a.txt +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.readFileSync('a.txt', 'utf-8')).to.equal('content of b\n'); + expect(fs.readFileSync('b.txt', 'utf-8')).to.equal('content of a\n'); + }); + + it('should handle a swap rename with content changes', function() { + fs.writeFileSync('a.txt', 'aaa\n'); + fs.writeFileSync('b.txt', 'bbb\n'); + + const patch = +`diff --git a/a.txt b/b.txt +similarity index 50% +rename from a.txt +rename to b.txt +--- a/a.txt ++++ b/b.txt +@@ -1 +1 @@ +-aaa ++aaa-modified +diff --git a/b.txt b/a.txt +similarity index 50% +rename from b.txt +rename to a.txt +--- a/b.txt ++++ b/a.txt +@@ -1 +1 @@ +-bbb ++bbb-modified +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.readFileSync('a.txt', 'utf-8')).to.equal('bbb-modified\n'); + expect(fs.readFileSync('b.txt', 'utf-8')).to.equal('aaa-modified\n'); + }); + + it('should handle a three-way rotation (a→b, b→c, c→a)', function() { + fs.writeFileSync('a.txt', 'content of a\n'); + fs.writeFileSync('b.txt', 'content of b\n'); + fs.writeFileSync('c.txt', 'content of c\n'); + + const patch = +`diff --git a/a.txt b/b.txt +similarity index 100% +rename from a.txt +rename to b.txt +diff --git a/b.txt b/c.txt +similarity index 100% +rename from b.txt +rename to c.txt +diff --git a/c.txt b/a.txt +similarity index 100% +rename from c.txt +rename to a.txt +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.readFileSync('a.txt', 'utf-8')).to.equal('content of c\n'); + expect(fs.readFileSync('b.txt', 'utf-8')).to.equal('content of a\n'); + expect(fs.readFileSync('c.txt', 'utf-8')).to.equal('content of b\n'); + }); + + it('should handle a file deletion', function() { + fs.writeFileSync('doomed.txt', 'goodbye\n'); + + const patch = +`diff --git a/doomed.txt b/doomed.txt +deleted file mode 100644 +index 2b31011..0000000 +--- a/doomed.txt ++++ /dev/null +@@ -1 +0,0 @@ +-goodbye +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.existsSync('doomed.txt')).to.equal(false); + }); + + it('should handle a file creation', function() { + const patch = +`diff --git a/brand-new.txt b/brand-new.txt +new file mode 100644 +index 0000000..fa49b07 +--- /dev/null ++++ b/brand-new.txt +@@ -0,0 +1 @@ ++hello world +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.readFileSync('brand-new.txt', 'utf-8')).to.equal('hello world\n'); + }); + + it('should create a new executable file with correct mode', function() { + const patch = +`diff --git a/run.sh b/run.sh +new file mode 100755 +index 0000000..abc1234 +--- /dev/null ++++ b/run.sh +@@ -0,0 +1,2 @@ ++#!/bin/bash ++echo hello +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.readFileSync('run.sh', 'utf-8')).to.equal('#!/bin/bash\necho hello\n'); + const mode = fs.statSync('run.sh').mode & 0o777; + expect(mode).to.equal(0o755); + }); + + it('should set the mode when a file is modified with a mode change', function() { + fs.writeFileSync('script.sh', 'echo old\n'); + fs.chmodSync('script.sh', 0o644); + + const patch = +`diff --git a/script.sh b/script.sh +old mode 100644 +new mode 100755 +--- a/script.sh ++++ b/script.sh +@@ -1 +1 @@ +-echo old ++echo new +`; + + applyGitPatch(applyPatches, patch, fs); + + expect(fs.readFileSync('script.sh', 'utf-8')).to.equal('echo new\n'); + const mode = fs.statSync('script.sh').mode & 0o777; + expect(mode).to.equal(0o755); + }); +}); diff --git a/test/patch/reverse.js b/test/patch/reverse.js new file mode 100644 index 000000000..bff011d47 --- /dev/null +++ b/test/patch/reverse.js @@ -0,0 +1,201 @@ +import {applyPatch} from '../../libesm/patch/apply.js'; +import {structuredPatch, formatPatch} from '../../libesm/patch/create.js'; +import {reversePatch} from '../../libesm/patch/reverse.js'; +import {parsePatch} from '../../libesm/patch/parse.js'; + +import {expect} from 'chai'; + +describe('patch/reverse', function() { + describe('#reversePatch', function() { + it('should output a patch that is the inverse of the provided patch', function() { + const file1 = 'line1\nline2\nline3\nline4\n'; + const file2 = 'line1\nline2\nline5\nline4\n'; + const patch = structuredPatch('file1', 'file2', file1, file2); + const reversedPatch = reversePatch(patch); + expect(formatPatch(reversedPatch)).to.equal( + '===================================================================\n' + + '--- file2\n' + + '+++ file1\n' + + '@@ -1,4 +1,4 @@\n' + + ' line1\n' + + ' line2\n' + + '+line3\n' + + '-line5\n' + + ' line4\n' + ); + expect(applyPatch(file2, reversedPatch)).to.equal(file1); + }); + + it('should support taking an array of structured patches, as output by parsePatch', function() { + const patch = parsePatch( + 'Index: file1.txt\n' + + '===================================================================\n' + + '--- file1.txt\n' + + '+++ file1.txt\n' + + '@@ -1,4 +1,5 @@\n' + + ' alpha\n' + + '+beta\n' + + ' gamma\n' + + ' delta\n' + + ' epsilon\n' + + 'Index: file2.txt\n' + + '===================================================================\n' + + '--- file2.txt\n' + + '+++ file2.txt\n' + + '@@ -2,3 +2,3 @@\n' + + ' two\n' + + '-three\n' + + '+THREE\n' + + ' four\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'Index: file2.txt\n' + + '===================================================================\n' + + '--- file2.txt\n' + + '+++ file2.txt\n' + + '@@ -2,3 +2,3 @@\n' + + ' two\n' + + '+three\n' + + '-THREE\n' + + ' four\n' + + '\n' + + 'Index: file1.txt\n' + + '===================================================================\n' + + '--- file1.txt\n' + + '+++ file1.txt\n' + + '@@ -1,5 +1,4 @@\n' + + ' alpha\n' + + '-beta\n' + + ' gamma\n' + + ' delta\n' + + ' epsilon\n' + ); + }); + + it('should reverse a rename patch into a rename in the opposite direction', function() { + const patch = parsePatch( + 'diff --git a/old.txt b/new.txt\n' + + 'similarity index 85%\n' + + 'rename from old.txt\n' + + 'rename to new.txt\n' + + '--- a/old.txt\n' + + '+++ b/new.txt\n' + + '@@ -1,3 +1,3 @@\n' + + ' line1\n' + + '-line2\n' + + '+line2modified\n' + + ' line3\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/new.txt b/old.txt\n' + + 'rename from new.txt\n' + + 'rename to old.txt\n' + + '--- a/new.txt\n' + + '+++ b/old.txt\n' + + '@@ -1,3 +1,3 @@\n' + + ' line1\n' + + '+line2\n' + + '-line2modified\n' + + ' line3\n' + ); + }); + + it('should reverse a copy patch into a deletion', function() { + const patch = parsePatch( + 'diff --git a/original.txt b/copy.txt\n' + + 'similarity index 85%\n' + + 'copy from original.txt\n' + + 'copy to copy.txt\n' + + '--- a/original.txt\n' + + '+++ b/copy.txt\n' + + '@@ -1,3 +1,3 @@\n' + + ' line1\n' + + '-line2\n' + + '+line2modified\n' + + ' line3\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/copy.txt b/copy.txt\n' + + 'deleted file mode 100644\n' + ); + }); + + it('should reverse a hunk-less copy into a deletion', function() { + const patch = parsePatch( + 'diff --git a/original.txt b/copy.txt\n' + + 'similarity index 100%\n' + + 'copy from original.txt\n' + + 'copy to copy.txt\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/copy.txt b/copy.txt\n' + + 'deleted file mode 100644\n' + ); + }); + + it('should reverse a hunk-less rename', function() { + const patch = parsePatch( + 'diff --git a/old.txt b/new.txt\n' + + 'similarity index 100%\n' + + 'rename from old.txt\n' + + 'rename to new.txt\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/new.txt b/old.txt\n' + + 'rename from new.txt\n' + + 'rename to old.txt\n' + ); + }); + + it('should reverse a creation into a deletion, swapping isCreate/isDelete and oldMode/newMode', function() { + const patch = parsePatch( + 'diff --git a/newfile.txt b/newfile.txt\n' + + 'new file mode 100755\n' + + '--- /dev/null\n' + + '+++ b/newfile.txt\n' + + '@@ -0,0 +1 @@\n' + + '+hello\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/newfile.txt b/newfile.txt\n' + + 'deleted file mode 100755\n' + + '--- a/newfile.txt\n' + + '+++ /dev/null\n' + + '@@ -1,1 +0,0 @@\n' + + '-hello\n' + ); + }); + + it('should reverse a deletion into a creation, swapping isCreate/isDelete and oldMode/newMode', function() { + const patch = parsePatch( + 'diff --git a/oldfile.txt b/oldfile.txt\n' + + 'deleted file mode 100644\n' + + '--- a/oldfile.txt\n' + + '+++ /dev/null\n' + + '@@ -1 +0,0 @@\n' + + '-goodbye\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/oldfile.txt b/oldfile.txt\n' + + 'new file mode 100644\n' + + '--- /dev/null\n' + + '+++ b/oldfile.txt\n' + + '@@ -0,0 +1,1 @@\n' + + '+goodbye\n' + ); + }); + + it('should swap oldMode and newMode when reversing a mode change', function() { + const patch = parsePatch( + 'diff --git a/script.sh b/script.sh\n' + + 'old mode 100644\n' + + 'new mode 100755\n' + ); + expect(formatPatch(reversePatch(patch))).to.equal( + 'diff --git a/script.sh b/script.sh\n' + + 'old mode 100755\n' + + 'new mode 100644\n' + ); + }); + }); +}); diff --git a/test/util/string.js b/test/util/string.js new file mode 100644 index 000000000..19e9a457d --- /dev/null +++ b/test/util/string.js @@ -0,0 +1,101 @@ +import {longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs} from '../../libesm/util/string.js'; +import {expect} from 'chai'; + +describe('#longestCommonPrefix', function() { + it('finds the longest common prefix', function() { + expect(longestCommonPrefix('food', 'foolish')).to.equal('foo'); + expect(longestCommonPrefix('foolish', 'food')).to.equal('foo'); + expect(longestCommonPrefix('foolish', 'foo')).to.equal('foo'); + expect(longestCommonPrefix('foo', 'foolish')).to.equal('foo'); + expect(longestCommonPrefix('foo', '')).to.equal(''); + expect(longestCommonPrefix('', 'foo')).to.equal(''); + expect(longestCommonPrefix('', '')).to.equal(''); + expect(longestCommonPrefix('foo', 'bar')).to.equal(''); + }); +}); + +describe('#longestCommonSuffix', function() { + it('finds the longest common suffix', function() { + expect(longestCommonSuffix('bumpy', 'grumpy')).to.equal('umpy'); + expect(longestCommonSuffix('grumpy', 'bumpy')).to.equal('umpy'); + expect(longestCommonSuffix('grumpy', 'umpy')).to.equal('umpy'); + expect(longestCommonSuffix('umpy', 'grumpy')).to.equal('umpy'); + expect(longestCommonSuffix('foo', '')).to.equal(''); + expect(longestCommonSuffix('', 'foo')).to.equal(''); + expect(longestCommonSuffix('', '')).to.equal(''); + expect(longestCommonSuffix('foo', 'bar')).to.equal(''); + }); +}); + +describe('#replacePrefix', function() { + it('replaces a prefix on a string with a different prefix', function() { + expect((replacePrefix('food', 'foo', 'gla'))).to.equal('glad'); + expect((replacePrefix('food', '', 'good '))).to.equal('good food'); + }); + + it("throws if the prefix to remove isn't present", function() { + // eslint-disable-next-line dot-notation + expect(() => replacePrefix('food', 'drin', 'goo')).to.throw(); + }); +}); + +describe('#replaceSuffix', function() { + it('replaces a suffix on a string with a different suffix', function() { + expect((replaceSuffix('bangle', 'gle', 'jo'))).to.equal('banjo'); + expect((replaceSuffix('bun', '', 'gle'))).to.equal('bungle'); + }); + + it("throws if the suffix to remove isn't present", function() { + // eslint-disable-next-line dot-notation + expect(() => replaceSuffix('food', 'ool', 'ondle')).to.throw(); + }); +}); + +describe('#removePrefix', function() { + it('removes a prefix', function() { + expect(removePrefix('inconceivable', 'in')).to.equal('conceivable'); + expect(removePrefix('inconceivable', '')).to.equal('inconceivable'); + expect(removePrefix('inconceivable', 'inconceivable')).to.equal(''); + }); + + it("throws if the prefix to remove isn't present", function() { + // eslint-disable-next-line dot-notation + expect(() => removePrefix('food', 'dr')).to.throw(); + }); +}); + +describe('#removeSuffix', function() { + it('removes a suffix', function() { + expect(removeSuffix('counterfactual', 'factual')).to.equal('counter'); + expect(removeSuffix('counterfactual', '')).to.equal('counterfactual'); + expect(removeSuffix('counterfactual', 'counterfactual')).to.equal(''); + }); + + it("throws if the suffix to remove isn't present", function() { + // eslint-disable-next-line dot-notation + expect(() => removeSuffix('food', 'dr')).to.throw(); + }); +}); + +describe('#maximumOverlap', function() { + it('finds the maximum overlap between the end of one string and the start of the other', function() { + expect(maximumOverlap('qwertyuiop', 'uiopasdfgh')).to.equal('uiop'); + expect(maximumOverlap('qwertyuiop', 'qwertyuiop')).to.equal('qwertyuiop'); + expect(maximumOverlap('qwertyuiop', 'asdfghjkl')).to.equal(''); + expect(maximumOverlap('qwertyuiop', '')).to.equal(''); + expect(maximumOverlap('uiopasdfgh', 'qwertyuiop')).to.equal(''); + expect(maximumOverlap('x ', ' x')).to.equal(' '); + expect(maximumOverlap('', '')).to.equal(''); + }); +}); + +describe('leadingWs & trailingWs', function() { + it('returns leading/trailing whitespace (with diacritics on whitespace considered part of the whitespace iff we are in segmenter mode)', () => { + const segmenter = new Intl.Segmenter('en', { granularity: 'word' }); + const text = '\t\u0300 foo bar\n baz qux\t \u0300 '; + expect(leadingWs(text)).to.equal('\t'); + expect(leadingWs(text, segmenter)).to.equal('\t\u0300 '); + expect(trailingWs(text)).to.equal(' '); + expect(trailingWs(text, segmenter)).to.equal('\t \u0300 '); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..0f39735d1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,60 @@ +{ + "include": ["src/*.ts", "src/**/*.ts"], + "compilerOptions": { + "rootDir": "src/", + "target": "es6", + + // What we would IDEALLY like to do is compile against type declarations corresponding to + // precisely what is available in BOTH the "Widely Available" Baseline + // (https://web.dev/baseline) AND the latest Node.js LTS, since jsdiff supports both. + // Alas, there is no way to do this. As of configuring the settings below (7th + // April 2026)... + // 1. there is simply no way to tell TypeScript to use the intersection of two sets of + // global type declarations (i.e. to only let us use stuff supported by both + // environments). At best, we can minimise how many global type declarations we include + // by keeping the "lib" and "types" arrays below minimal, and ideally avoiding including + // anything in them that is either browser-specific or Node-specific. + // 2. jsdiff actually uses some stuff that is both "Widely Available" and available in Node, + // but not part of *any* ECMAScript spec. These include `TextDecoder`, which has its own + // dedicated spec, and `setTimeout`, which is (weirdly!) part of the HTML standard, not + // the ECMAScript spec (although Node has its own, identical-ish implementation). + // Consequently setting `"lib": ["esnext"]` will not suffice to let jsdiff compile; to + // get to a tsconfig that actually works, we MUST include "dom" in the "lib" array + // and/or use the @types/node type declaration module by including "node" in the + // "types" array. + // Obviously, both available solutions to point 2 above are unsatisfactory, due to point 1. + // If we include "dom" in the "lib" array, then if we use browser-specific features in our + // TypeScript code (that won't work in Node), TypeScript will not warn us about it at + // compilation time. If we instead use `types: ["node"]`, the converse problem will exist. + // + // Faced with two unsatisfactory options, we reluctantly choose one. But it means we + // absolutely cannot rely on TypeScript to catch if we use a global that isn't actually + // available in all our supported environments, and so must rely on tests and vigilance. + "lib": [ + "es2023", "dom" + ], + "types": [], + + "declaration": true, + "skipLibCheck": true, + + // Options below enabled per recommendation at + // https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-writing-a-library + "strict": true, + "declarationMap": true, + // The same docs page recommends setting + // "verbatimModuleSyntax": true, + // but this totally breaks the CJS build and a footnote observes that "Any configuration + // that produces both an ESM and a CJS output from the same source file is fundamentally + // incompatible with" this setting. I don't really know WTF the authors of those docs were + // thinking, because the recommendation to turn this setting on is from the section + // specifically for "a library author" who wants to ensure their code works "under all + // possible library consumer compilation settings" - i.e. a person who is essentially 100% + // guaranteed to be running both an ESM and CJS build. So how can the recommendation to + // turn this setting on possibly be even remotely sane? Beats me. ¯\_(ツ)_/¯ + // I've done the best I can by using the @typescript-eslint/consistent-type-imports and + // @typescript-eslint/consistent-type-exports ESLint rules to enforce SOME of what this + // setting would have enforced, though I dunno if I'm enforcing the bits that motivated the + // recommendation to turn it on. + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..f8c9fa992 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8710 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@andrewbranch/untar.js@npm:^1.0.3": + version: 1.0.3 + resolution: "@andrewbranch/untar.js@npm:1.0.3" + checksum: 10c0/16774208cd5bc2cace3c8c6ca608b2b9ab07719a44501e5553f72bffb63c5fbac0b715a4b1065a65d09e010d940ac3cd148ade44dd7d49682765fe09e2c3b2a8 + languageName: node + linkType: hard + +"@arethetypeswrong/cli@npm:^0.18.2": + version: 0.18.2 + resolution: "@arethetypeswrong/cli@npm:0.18.2" + dependencies: + "@arethetypeswrong/core": "npm:0.18.2" + chalk: "npm:^4.1.2" + cli-table3: "npm:^0.6.3" + commander: "npm:^10.0.1" + marked: "npm:^9.1.2" + marked-terminal: "npm:^7.1.0" + semver: "npm:^7.5.4" + bin: + attw: dist/index.js + checksum: 10c0/15fa8ee8659aa7b163c08f3d739247fe44d838b1aa5cefe125d8c90c03955fbc1e5a7c76885c354ec217a07c8fdcb5f5dc297188cadf626b303b1ae85232c51e + languageName: node + linkType: hard + +"@arethetypeswrong/core@npm:0.18.2": + version: 0.18.2 + resolution: "@arethetypeswrong/core@npm:0.18.2" + dependencies: + "@andrewbranch/untar.js": "npm:^1.0.3" + "@loaderkit/resolve": "npm:^1.0.2" + cjs-module-lexer: "npm:^1.2.3" + fflate: "npm:^0.8.2" + lru-cache: "npm:^11.0.1" + semver: "npm:^7.5.4" + typescript: "npm:5.6.1-rc" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10c0/465b4e6bf41a236b265108d21e88e75f6940959d6611d7daf989b69c875cf84fdaf77a539d0d5ce1d2602b7eadeba22da54994247a4825d8ad473685ad7e860e + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.28.6, @babel/compat-data@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/compat-data@npm:7.29.0" + checksum: 10c0/08f348554989d23aa801bf1405aa34b15e841c0d52d79da7e524285c77a5f9d298e70e11d91cc578d8e2c9542efc586d50c5f5cf8e1915b254a9dcf786913a94 + languageName: node + linkType: hard + +"@babel/core@npm:^7.23.9, @babel/core@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/core@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helpers": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/5127d2e8e842ae409e11bcbb5c2dff9874abf5415e8026925af7308e903f4f43397341467a130490d1a39884f461bc2b67f3063bce0be44340db89687fd852aa + languageName: node + linkType: hard + +"@babel/generator@npm:^7.29.0": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" + dependencies: + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" + dependencies: + "@babel/types": "npm:^7.27.3" + checksum: 10c0/94996ce0a05b7229f956033e6dcd69393db2b0886d0db6aff41e704390402b8cdcca11f61449cb4f86cfd9e61b5ad3a73e4fa661eeed7846b125bd1c33dbc633 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" + dependencies: + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-create-class-features-plugin@npm:7.28.6" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0b62b46717891f4366006b88c9b7f277980d4f578c4c3789b7a4f5a2e09e121de4cda9a414ab403986745cd3ad1af3fe2d948c9f78ab80d4dc085afc9602af50 + languageName: node + linkType: hard + +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.27.1, @babel/helper-create-regexp-features-plugin@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.28.5" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + regexpu-core: "npm:^6.3.1" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/7af3d604cadecdb2b0d2cedd696507f02a53a58be0523281c2d6766211443b55161dde1e6c0d96ab16ddfd82a2607a2f792390caa24797e9733631f8aa86859f + languageName: node + linkType: hard + +"@babel/helper-define-polyfill-provider@npm:^0.6.8": + version: 0.6.8 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.8" + dependencies: + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + debug: "npm:^4.4.3" + lodash.debounce: "npm:^4.0.8" + resolve: "npm:^1.22.11" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/306a169f2cb285f368578219ef18ea9702860d3d02d64334f8d45ea38648be0b9e1edad8c8f732fa34bb4206ccbb9883c395570fd57ab7bbcf293bc5964c5b3a + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5" + dependencies: + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + checksum: 10c0/4e6e05fbf4dffd0bc3e55e28fcaab008850be6de5a7013994ce874ec2beb90619cda4744b11607a60f8aae0227694502908add6188ceb1b5223596e765b44814 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" + dependencies: + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-transforms@npm:7.28.6" + dependencies: + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/6f03e14fc30b287ce0b839474b5f271e72837d0cafe6b172d759184d998fbee3903a035e81e07c2c596449e504f453463d58baa65b6f40a37ded5bec74620b2b + languageName: node + linkType: hard + +"@babel/helper-optimise-call-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" + dependencies: + "@babel/types": "npm:^7.27.1" + checksum: 10c0/6b861e7fcf6031b9c9fc2de3cd6c005e94a459d6caf3621d93346b52774925800ca29d4f64595a5ceacf4d161eb0d27649ae385110ed69491d9776686fa488e6 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-plugin-utils@npm:7.28.6" + checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d + languageName: node + linkType: hard + +"@babel/helper-remap-async-to-generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-remap-async-to-generator@npm:7.27.1" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-wrap-function": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/5ba6258f4bb57c7c9fa76b55f416b2d18c867b48c1af4f9f2f7cd7cc933fe6da7514811d08ceb4972f1493be46f4b69c40282b811d1397403febae13c2ec57b5 + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.27.1, @babel/helper-replace-supers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-replace-supers@npm:7.28.6" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/04663c6389551b99b8c3e7ba4e2638b8ca2a156418c26771516124c53083aa8e74b6a45abe5dd46360af79709a0e9c6b72c076d0eab9efecdd5aaf836e79d8d5 + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/f625013bcdea422c470223a2614e90d2c1cc9d832e97f32ca1b4f82b34bb4aa67c3904cb4b116375d3b5b753acfb3951ed50835a1e832e7225295c7b0c24dff7 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helper-wrap-function@npm:^7.27.1": + version: 7.28.6 + resolution: "@babel/helper-wrap-function@npm:7.28.6" + dependencies: + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/110674c7aa705dd8cc34f278628f540b37a4cb35e81fcaf557772e026a6fd95f571feb51a8efb146e4e91bbf567dc9dd7f534f78da80f55f4be2ec842f36b678 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.28.6": + version: 7.29.2 + resolution: "@babel/helpers@npm:7.29.2" + dependencies: + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" + checksum: 10c0/dab0e65b9318b2502a62c58bc0913572318595eec0482c31f0ad416b72636e6698a1d7c57cd2791d4528eb8c548bca88d338dc4d2a55a108dc1f6702f9bc5512 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.23.9, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": + version: 7.29.2 + resolution: "@babel/parser@npm:7.29.2" + dependencies: + "@babel/types": "npm:^7.29.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/e5a4e69e3ac7acdde995f37cf299a68458cfe7009dff66bd0962fd04920bef287201169006af365af479c08ff216bfefbb595e331f87f6ae7283858aebbc3317 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/844b7c7e9eec6d858262b2f3d5af75d3a6bbd9d3ecc740d95271fbdd84985731674536f5d8ac98f2dc0e8872698b516e406636e4d0cb04b50afe471172095a53 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/2cd7a55a856e5e59bbd9484247c092a41e0d9f966778e7019da324d9e0928892d26afc4fbb2ac3d76a3c5a631cd3cf0d72dd2653b44f634f6c663b9e6f80aacd + languageName: node + linkType: hard + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/cf29835498c4a25bd470908528919729a0799b2ec94e89004929a5532c94a5e4b1a49bc5d6673a22e5afe05d08465873e14ee3b28c42eb3db489cdf5ca47c680 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.13.0 + checksum: 10c0/eddcd056f76e198868cbff883eb148acfade8f0890973ab545295df0c08e39573a72e65372bcc0b0bfadba1b043fe1aea6b0907d0b4889453ac154c404194ebc + languageName: node + linkType: hard + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/f1a9194e8d1742081def7af748e9249eb5082c25d0ced292720a1f054895f99041c764a05f45af669a2c8898aeb79266058aedb0d3e1038963ad49be8288918a + languageName: node + linkType: hard + +"@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2": + version: 7.21.0-placeholder-for-preset-env.2 + resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e605e0070da087f6c35579499e65801179a521b6842c15181a1e305c04fded2393f11c1efd09b087be7f8b083d1b75e8f3efcbc1292b4f60d3369e14812cff63 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-assertions@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/f3b8bdccb9b4d3e3b9226684ca518e055399d05579da97dfe0160a38d65198cfe7dce809e73179d6463a863a040f980de32425a876d88efe4eda933d0d95982c + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-attributes@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/1be160e2c426faa74e5be2e30e39e8d0d8c543063bd5d06cd804f8751b8fbcb82ce824ca7f9ce4b09c003693f6c06a11ce503b7e34d85e1a259631e4c3f72ad2 + languageName: node + linkType: hard + +"@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.18.6" + "@babel/helper-plugin-utils": "npm:^7.18.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/9144e5b02a211a4fb9a0ce91063f94fbe1004e80bde3485a0910c9f14897cf83fabd8c21267907cff25db8e224858178df0517f14333cfcf3380ad9a4139cb50 + languageName: node + linkType: hard + +"@babel/plugin-transform-arrow-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/19abd7a7d11eef58c9340408a4c2594503f6c4eaea1baa7b0e5fbdda89df097e50663edb3448ad2300170b39efca98a75e5767af05cad3b0facb4944326896a3 + languageName: node + linkType: hard + +"@babel/plugin-transform-async-generator-functions@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.29.0" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-remap-async-to-generator": "npm:^7.27.1" + "@babel/traverse": "npm:^7.29.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4080fc5e7dad7761bfebbb4fbe06bdfeb3a8bf0c027bcb4373e59e6b3dc7c5002eca7cbb1afba801d6439df8f92f7bcb3fb862e8fbbe43a9e59bb5653dcc0568 + languageName: node + linkType: hard + +"@babel/plugin-transform-async-to-generator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.28.6" + dependencies: + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-remap-async-to-generator": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2eb0826248587df6e50038f36194a138771a7df22581020451c7779edeaf9ef39bf47c5b7a20ae2645af6416e8c896feeca273317329652e84abd79a4ab920ad + languageName: node + linkType: hard + +"@babel/plugin-transform-block-scoped-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/3313130ba3bf0699baad0e60da1c8c3c2f0c2c0a7039cd0063e54e72e739c33f1baadfc9d8c73b3fea8c85dd7250c3964fb09c8e1fa62ba0b24a9fefe0a8dbde + languageName: node + linkType: hard + +"@babel/plugin-transform-block-scoping@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-block-scoping@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2e3e09e1f9770b56cef4dcbffddf262508fd03416072f815ac66b2b224a3a12cd285cfec12fc067f1add414e7db5ce6dafb5164a6e0fb1a728e6a97d0c6f6e9d + languageName: node + linkType: hard + +"@babel/plugin-transform-class-properties@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-class-properties@npm:7.28.6" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c4327fcd730c239d9f173f9b695b57b801729e273b4848aef1f75818069dfd31d985d75175db188d947b9b1bbe5353dae298849042026a5e4fcf07582ff3f9f1 + languageName: node + linkType: hard + +"@babel/plugin-transform-class-static-block@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-class-static-block@npm:7.28.6" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.12.0 + checksum: 10c0/dbe9b1fd302ae41b73186e17ac8d8ecf625ebc2416a91f2dc8013977a1bdf21e6ea288a83f084752b412242f3866e789d4fddeb428af323fe35b60e0fae4f98c + languageName: node + linkType: hard + +"@babel/plugin-transform-classes@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-classes@npm:7.28.6" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-replace-supers": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/dc22f1f6eadab17305128fbf9cc5f30e87a51a77dd0a6d5498097994e8a9b9a90ab298c11edf2342acbeaac9edc9c601cad72eedcf4b592cd465a787d7f41490 + languageName: node + linkType: hard + +"@babel/plugin-transform-computed-properties@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-computed-properties@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/template": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/1e9893503ae6d651125701cc29450e87c0b873c8febebff19da75da9c40cfb7968c52c28bf948244e461110aeb7b3591f2cc199b7406ff74a24c50c7a5729f39 + languageName: node + linkType: hard + +"@babel/plugin-transform-destructuring@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-destructuring@npm:7.28.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/288207f488412b23bb206c7c01ba143714e2506b72a9ec09e993f28366cc8188d121bde714659b3437984a86d2881d9b1b06de3089d5582823ccf2f3b3eaa2c4 + languageName: node + linkType: hard + +"@babel/plugin-transform-dotall-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.28.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e2fb76b7ae99087cf4212013a3ca9dee07048f90f98fd6264855080fb6c3f169be11c9b8c9d8b26cf9a407e4d0a5fa6e103f7cef433a542b75cf7127c99d4f97 + languageName: node + linkType: hard + +"@babel/plugin-transform-duplicate-keys@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/22a822e5342b7066f83eaedc4fd9bb044ac6bc68725484690b33ba04a7104980e43ea3229de439286cb8db8e7db4a865733a3f05123ab58a10f189f03553746f + languageName: node + linkType: hard + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.29.0" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/6f03d9e5e31a05b28555541be6e283407e08447a36be6ddf8068b3efa970411d832e04b1282e2b894baf89a3864ff7e7f1e36346652a8d983170c6d548555167 + languageName: node + linkType: hard + +"@babel/plugin-transform-dynamic-import@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/8dcd3087aca134b064fc361d2cc34eec1f900f6be039b6368104afcef10bb75dea726bb18cabd046716b89b0edaa771f50189fa16bc5c5914a38cbcf166350f7 + languageName: node + linkType: hard + +"@babel/plugin-transform-explicit-resource-management@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e6ea28c26e058fe61ada3e70b0def1992dd5a44f5fc14d8e2c6a3a512fb4d4c6dc96a3e1d0b466d83db32a9101e0b02df94051e48d3140da115b8ea9f8a31f37 + languageName: node + linkType: hard + +"@babel/plugin-transform-exponentiation-operator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4572d955a50dbc9a652a19431b4bb822cb479ee6045f4e6df72659c499c13036da0a2adf650b07ca995f2781e80aa868943bea1e7bff1de3169ec3f0a73a902e + languageName: node + linkType: hard + +"@babel/plugin-transform-export-namespace-from@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d7165cad11f571a54c8d9263d6c6bf2b817aff4874f747cb51e6e49efb32f2c9b37a6850cdb5e3b81e0b638141bb77dc782a6ec1a94128859fbdf7767581e07c + languageName: node + linkType: hard + +"@babel/plugin-transform-for-of@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-for-of@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4635763173a23aae24480681f2b0996b4f54a0cb2368880301a1801638242e263132d1e8adbe112ab272913d1d900ee0d6f7dea79443aef9d3325168cd88b3fb + languageName: node + linkType: hard + +"@babel/plugin-transform-function-name@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-function-name@npm:7.27.1" + dependencies: + "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/5abdc7b5945fbd807269dcc6e76e52b69235056023b0b35d311e8f5dfd6c09d9f225839798998fc3b663f50cf701457ddb76517025a0d7a5474f3fe56e567a4c + languageName: node + linkType: hard + +"@babel/plugin-transform-json-strings@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-json-strings@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ab1091798c58e6c0bb8a864ee2b727c400924592c6ed69797a26b4c205f850a935de77ad516570be0419c279a3d9f7740c2aa448762eb8364ea77a6a357a9653 + languageName: node + linkType: hard + +"@babel/plugin-transform-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-literals@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c40dc3eb2f45a92ee476412314a40e471af51a0f51a24e91b85cef5fc59f4fe06758088f541643f07f949d2c67ee7bdce10e11c5ec56791ae09b15c3b451eeca + languageName: node + linkType: hard + +"@babel/plugin-transform-logical-assignment-operators@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4632a35453d2131f0be466681d0a33e3db44d868ff51ec46cd87e0ebd1e47c6a39b894f7d1c9b06f931addf6efa9d30e60c4cdedeb4f69d426f683e11f8490cf + languageName: node + linkType: hard + +"@babel/plugin-transform-member-expression-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0874ccebbd1c6a155e5f6b3b29729fade1221b73152567c1af1e1a7c12848004dffecbd7eded6dc463955120040ae57c17cb586b53fb5a7a27fcd88177034c30 + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-amd@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-amd@npm:7.27.1" + dependencies: + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/76e86cd278b6a3c5b8cca8dfb3428e9cd0c81a5df7096e04c783c506696b916a9561386d610a9d846ef64804640e0bd818ea47455fed0ee89b7f66c555b29537 + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-commonjs@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.28.6" + dependencies: + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/7c45992797c6150644c8552feff4a016ba7bd6d59ff2b039ed969a9c5b20a6804cd9d21db5045fc8cca8ca7f08262497e354e93f8f2be6a1cdf3fbfa8c31a9b6 + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-systemjs@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.29.0" + dependencies: + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.29.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/44ea502f2c990398b7d9adc5b44d9e1810a0a5e86eebc05c92d039458f0b3994fe243efa9353b90f8a648d8a91b79845fb353d8679d7324cc9de0162d732771d + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-umd@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-umd@npm:7.27.1" + dependencies: + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e5962a8874889da2ab1aa32eb93ec21d419c7423c766e4befb39b4bb512b9ad44b47837b6cd1c8f1065445cbbcc6dc2be10298ac6e734e5ca1059fc23698daed + languageName: node + linkType: hard + +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.29.0" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/1904db22da7f2bc3e380cd2c0786bda330ee1b1b3efa3f5203d980708c4bfeb5daa4dff48d01692193040bcc5f275dbdc0c2eadc8b1eb1b6dfe363564ad6e898 + languageName: node + linkType: hard + +"@babel/plugin-transform-new-target@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-new-target@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/9b0581412fcc5ab1b9a2d86a0c5407bd959391f0a1e77a46953fef9f7a57f3f4020d75f71098c5f9e5dcc680a87f9fd99b3205ab12e25ef8c19eed038c1e4b28 + languageName: node + linkType: hard + +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/6607f2201d66ccb688f0b1db09475ef995837df19f14705da41f693b669f834c206147a854864ab107913d7b4f4748878b0cd9fe9ca8bfd1bee0c206fc027b49 + languageName: node + linkType: hard + +"@babel/plugin-transform-numeric-separator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/191097d8d2753cdd16d1acca65a945d1645ab20b65655c2f5b030a9e38967a52e093dcb21ebf391e342222705c6ffe5dea15dafd6257f7b51b77fb64a830b637 + languageName: node + linkType: hard + +"@babel/plugin-transform-object-rest-spread@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.6" + dependencies: + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/f55334352d4fcde385f2e8a58836687e71ff668c9b6e4c34d52575bf2789cdde92d9d3116edba13647ac0bc3e51fb2a6d1e8fb822dce7e8123334b82600bc4c3 + languageName: node + linkType: hard + +"@babel/plugin-transform-object-super@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-object-super@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/efa2d092ef55105deb06d30aff4e460c57779b94861188128489b72378bf1f0ab0f06a4a4d68b9ae2a59a79719fbb2d148b9a3dca19ceff9c73b1f1a95e0527c + languageName: node + linkType: hard + +"@babel/plugin-transform-optional-catch-binding@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/36e8face000ee65e478a55febf687ce9be7513ad498c60dfe585851555565e0c28e7cb891b3c59709318539ce46f7697d5f42130eb18f385cd47e47cfa297446 + languageName: node + linkType: hard + +"@babel/plugin-transform-optional-chaining@npm:^7.27.1, @babel/plugin-transform-optional-chaining@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c159cc74115c2266be21791f192dd079e2aeb65c8731157e53b80fcefa41e8e28ad370021d4dfbdb31f25e5afa0322669a8eb2d032cd96e65ac37e020324c763 + languageName: node + linkType: hard + +"@babel/plugin-transform-parameters@npm:^7.27.7": + version: 7.27.7 + resolution: "@babel/plugin-transform-parameters@npm:7.27.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/f2da3804e047d9f1cfb27be6c014e2c7f6cf5e1e38290d1cb3cb2607859e3d6facb4ee8c8c1e336e9fbb440091a174ce95ce156582d7e8bf9c0e735d11681f0f + languageName: node + linkType: hard + +"@babel/plugin-transform-private-methods@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-private-methods@npm:7.28.6" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/fb504e2bfdcf3f734d2a90ab20d61427c58385f57f950d3de6ff4e6d12dd4aa7d552147312d218367e129b7920dccfc3230ba554de861986cda38921bad84067 + languageName: node + linkType: hard + +"@babel/plugin-transform-private-property-in-object@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.28.6" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0f6bbc6ec3f93b556d3de7d56bf49335255fc4c43488e51a5025d6ee0286183fd3cf950ffcac1bbeed8a45777f860a49996455c8d3b4a04c3b1a5f28e697fe31 + languageName: node + linkType: hard + +"@babel/plugin-transform-property-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-property-literals@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/15713a87edd6db620d6e66eb551b4fbfff5b8232c460c7c76cedf98efdc5cd21080c97040231e19e06594c6d7dfa66e1ab3d0951e29d5814fb25e813f6d6209c + languageName: node + linkType: hard + +"@babel/plugin-transform-regenerator@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/plugin-transform-regenerator@npm:7.29.0" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/86c7db9b97f85ee47c0fae0528802cbc06e5775e61580ee905335c16bb971270086764a3859873d9adcd7d0f913a5b93eb0dc271aec8fb9e93e090e4ac95e29e + languageName: node + linkType: hard + +"@babel/plugin-transform-regexp-modifiers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.28.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/97e36b086800f71694fa406abc00192e3833662f2bdd5f51c018bd0c95eef247c4ae187417c207d03a9c5374342eac0bb65a39112c431a9b23b09b1eda1562e5 + languageName: node + linkType: hard + +"@babel/plugin-transform-reserved-words@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-reserved-words@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e1a87691cce21a644a474d7c9a8107d4486c062957be32042d40f0a3d0cc66e00a3150989655019c255ff020d2640ac16aaf544792717d586f219f3bad295567 + languageName: node + linkType: hard + +"@babel/plugin-transform-shorthand-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/bd5544b89520a22c41a6df5ddac9039821d3334c0ef364d18b0ba9674c5071c223bcc98be5867dc3865cb10796882b7594e2c40dedaff38e1b1273913fe353e1 + languageName: node + linkType: hard + +"@babel/plugin-transform-spread@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-spread@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/bcac50e558d6f0c501cbce19ec197af558cef51fe3b3a6eba27276e323e57a5be28109b4264a5425ac12a67bf95d6af9c2a42b05e79c522ce913fb9529259d76 + languageName: node + linkType: hard + +"@babel/plugin-transform-sticky-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/5698df2d924f0b1b7bdb7ef370e83f99ed3f0964eb3b9c27d774d021bee7f6d45f9a73e2be369d90b4aff1603ce29827f8743f091789960e7669daf9c3cda850 + languageName: node + linkType: hard + +"@babel/plugin-transform-template-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-template-literals@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c90f403e42ef062b60654d1c122c70f3ec6f00c2f304b0931ebe6d0b432498ef8a5ef9266ddf00debc535f8390842207e44d3900eff1d2bab0cc1a700f03e083 + languageName: node + linkType: hard + +"@babel/plugin-transform-typeof-symbol@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a13c68015311fefa06a51830bc69d5badd06c881b13d5cf9ba04bf7c73e3fc6311cc889e18d9645ce2a64a79456dc9c7be88476c0b6802f62a686cb6f662ecd6 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-escapes@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a6809e0ca69d77ee9804e0c1164e8a2dea5e40718f6dcf234aeddf7292e7414f7ee331d87f17eb6f160823a329d1d6751bd49b35b392ac4a6efc032e4d3038d8 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-property-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.28.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b25f8cde643f4f47e0fa4f7b5c552e2dfbb6ad0ce07cf40f7e8ae40daa9855ad855d76d4d6d010153b74e48c8794685955c92ca637c0da152ce5f0fa9e7c90fa + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.27.1" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/6abda1bcffb79feba6f5c691859cdbe984cc96481ea65d5af5ba97c2e843154005f0886e25006a37a2d213c0243506a06eaeafd93a040dbe1f79539016a0d17a + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-sets-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.28.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/c03c8818736b138db73d1f7a96fbfa22d1994639164d743f0f00e6383d3b7b3144d333de960ff4afad0bddd0baaac257295e3316969eba995b1b6a1b4dec933e + languageName: node + linkType: hard + +"@babel/preset-env@npm:^7.29.2": + version: 7.29.2 + resolution: "@babel/preset-env@npm:7.29.2" + dependencies: + "@babel/compat-data": "npm:^7.29.0" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.28.5" + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.28.6" + "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions": "npm:^7.28.6" + "@babel/plugin-syntax-import-attributes": "npm:^7.28.6" + "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" + "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" + "@babel/plugin-transform-async-generator-functions": "npm:^7.29.0" + "@babel/plugin-transform-async-to-generator": "npm:^7.28.6" + "@babel/plugin-transform-block-scoped-functions": "npm:^7.27.1" + "@babel/plugin-transform-block-scoping": "npm:^7.28.6" + "@babel/plugin-transform-class-properties": "npm:^7.28.6" + "@babel/plugin-transform-class-static-block": "npm:^7.28.6" + "@babel/plugin-transform-classes": "npm:^7.28.6" + "@babel/plugin-transform-computed-properties": "npm:^7.28.6" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" + "@babel/plugin-transform-dotall-regex": "npm:^7.28.6" + "@babel/plugin-transform-duplicate-keys": "npm:^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.29.0" + "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" + "@babel/plugin-transform-explicit-resource-management": "npm:^7.28.6" + "@babel/plugin-transform-exponentiation-operator": "npm:^7.28.6" + "@babel/plugin-transform-export-namespace-from": "npm:^7.27.1" + "@babel/plugin-transform-for-of": "npm:^7.27.1" + "@babel/plugin-transform-function-name": "npm:^7.27.1" + "@babel/plugin-transform-json-strings": "npm:^7.28.6" + "@babel/plugin-transform-literals": "npm:^7.27.1" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.28.6" + "@babel/plugin-transform-member-expression-literals": "npm:^7.27.1" + "@babel/plugin-transform-modules-amd": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.28.6" + "@babel/plugin-transform-modules-systemjs": "npm:^7.29.0" + "@babel/plugin-transform-modules-umd": "npm:^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.29.0" + "@babel/plugin-transform-new-target": "npm:^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.28.6" + "@babel/plugin-transform-numeric-separator": "npm:^7.28.6" + "@babel/plugin-transform-object-rest-spread": "npm:^7.28.6" + "@babel/plugin-transform-object-super": "npm:^7.27.1" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.28.6" + "@babel/plugin-transform-optional-chaining": "npm:^7.28.6" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/plugin-transform-private-methods": "npm:^7.28.6" + "@babel/plugin-transform-private-property-in-object": "npm:^7.28.6" + "@babel/plugin-transform-property-literals": "npm:^7.27.1" + "@babel/plugin-transform-regenerator": "npm:^7.29.0" + "@babel/plugin-transform-regexp-modifiers": "npm:^7.28.6" + "@babel/plugin-transform-reserved-words": "npm:^7.27.1" + "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" + "@babel/plugin-transform-spread": "npm:^7.28.6" + "@babel/plugin-transform-sticky-regex": "npm:^7.27.1" + "@babel/plugin-transform-template-literals": "npm:^7.27.1" + "@babel/plugin-transform-typeof-symbol": "npm:^7.27.1" + "@babel/plugin-transform-unicode-escapes": "npm:^7.27.1" + "@babel/plugin-transform-unicode-property-regex": "npm:^7.28.6" + "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.28.6" + "@babel/preset-modules": "npm:0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2: "npm:^0.4.15" + babel-plugin-polyfill-corejs3: "npm:^0.14.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.6" + core-js-compat: "npm:^3.48.0" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d49cb005f2dbc3f2293ab6d80ee8f1380e6215af5518fe26b087c8961c1ea8ebaa554dfce589abe1fbebac25ad7c2515d943dec3859ea2d4981a3f8f4711c580 + languageName: node + linkType: hard + +"@babel/preset-modules@npm:0.1.6-no-external-plugins": + version: 0.1.6-no-external-plugins + resolution: "@babel/preset-modules@npm:0.1.6-no-external-plugins" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@babel/types": "npm:^7.4.4" + esutils: "npm:^2.0.2" + peerDependencies: + "@babel/core": ^7.0.0-0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/9d02f70d7052446c5f3a4fb39e6b632695fb6801e46d31d7f7c5001f7c18d31d1ea8369212331ca7ad4e7877b73231f470b0d559162624128f1b80fe591409e6 + languageName: node + linkType: hard + +"@babel/register@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/register@npm:7.28.6" + dependencies: + clone-deep: "npm:^4.0.1" + find-cache-dir: "npm:^2.0.0" + make-dir: "npm:^2.1.0" + pirates: "npm:^4.0.6" + source-map-support: "npm:^0.5.16" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/372380504970cf7654c2d65e09c34ed0b3217da64cb0edb6376a89eba7b603c8bdaba666eead7dcd6ed21badd52d396c2c0d6f914ae4dc6c9009e3d03d260e98 + languageName: node + linkType: hard + +"@babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" + dependencies: + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" + debug: "npm:^4.3.1" + checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb + languageName: node + linkType: hard + +"@babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.4.4": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + languageName: node + linkType: hard + +"@braidai/lang@npm:^1.0.0": + version: 1.1.2 + resolution: "@braidai/lang@npm:1.1.2" + checksum: 10c0/24bc85bf85dfe027102b19f418a591202b00cc8be12525ceff8adb65d5ebdda0dc1a0463c1e2cb6de1d85cd409012aa85850d9377bae61160162afbea278ab26 + languageName: node + linkType: hard + +"@colors/colors@npm:1.5.0": + version: 1.5.0 + resolution: "@colors/colors@npm:1.5.0" + checksum: 10c0/eb42729851adca56d19a08e48d5a1e95efd2a32c55ae0323de8119052be0510d4b7a1611f2abcbf28c044a6c11e6b7d38f99fccdad7429300c37a8ea5fb95b44 + languageName: node + linkType: hard + +"@colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 + languageName: node + linkType: hard + +"@epic-web/invariant@npm:^1.0.0": + version: 1.0.0 + resolution: "@epic-web/invariant@npm:1.0.0" + checksum: 10c0/72dbeb026e4e4eb3bc9c65739b91408ca77ab7d603a2494fa2eff3790ec22892c4caba751cffdf30f5ccf0e7ba79c1e9c96cf0a357404b9432bf1365baac23ca + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.12.2": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.23.4": + version: 0.23.5 + resolution: "@eslint/config-array@npm:0.23.5" + dependencies: + "@eslint/object-schema": "npm:^3.0.5" + debug: "npm:^4.3.1" + minimatch: "npm:^10.2.4" + checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.5.4": + version: 0.5.5 + resolution: "@eslint/config-helpers@npm:0.5.5" + dependencies: + "@eslint/core": "npm:^1.2.1" + checksum: 10c0/18889c062cd6bdbd4cd92fe57318c44465ea66184aa0ba204a4420712c66764c64093a7905b6c2ffde23e51b268ca2cec1a39c605d336bebf17ee1ba4f0fc0bb + languageName: node + linkType: hard + +"@eslint/core@npm:^1.2.0, @eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f + languageName: node + linkType: hard + +"@eslint/js@npm:^10.0.1": + version: 10.0.1 + resolution: "@eslint/js@npm:10.0.1" + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/9f3fcaf71ba7fdf65d82e8faad6ecfe97e11801cc3c362b306a88ea1ed1344ae0d35330dddb0e8ad18f010f6687a70b75491b9e01c8af57acd7987cee6b3ec6c + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^3.0.5": + version: 3.0.5 + resolution: "@eslint/object-schema@npm:3.0.5" + checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.7.0": + version: 0.7.1 + resolution: "@eslint/plugin-kit@npm:0.7.1" + dependencies: + "@eslint/core": "npm:^1.2.1" + levn: "npm:^0.4.1" + checksum: 10c0/335b0c1c46fd906cb50bd5ce442b9cee18dc44342ce35c718ba4a63d1aa51d2797f16a517b2f4fe371ccd777b6862fafb2dc8195e00e69197ef4cb17ab32c01b + languageName: node + linkType: hard + +"@gar/promise-retry@npm:^1.0.0": + version: 1.0.3 + resolution: "@gar/promise-retry@npm:1.0.3" + checksum: 10c0/885b02c8b0d75b2d215da25f3b639158c4fbe8fefe0d79163304534b9a6d0710db4b7699f7cd3cc1a730792bff04cbe19f4850a62d3e105a663eaeec88f38332 + languageName: node + linkType: hard + +"@humanfs/core@npm:^0.19.1": + version: 0.19.1 + resolution: "@humanfs/core@npm:0.19.1" + checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.7 + resolution: "@humanfs/node@npm:0.16.7" + dependencies: + "@humanfs/core": "npm:^0.19.1" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/9f83d3cf2cfa37383e01e3cdaead11cd426208e04c44adcdd291aa983aaf72d7d3598844d2fe9ce54896bb1bf8bd4b56883376611c8905a19c44684642823f30 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.6 + resolution: "@istanbuljs/schema@npm:0.1.6" + checksum: 10c0/bb0d370bf3dd454d2f37f1bccb8921e2da99adacef2da56ef47850e25d7a4de69cf639ead8c189755aef38921369024b4afea3535a5c2ac9082b3e1171bcbc3a + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.11 + resolution: "@jridgewell/source-map@npm:0.3.11" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + checksum: 10c0/50a4fdafe0b8f655cb2877e59fe81320272eaa4ccdbe6b9b87f10614b2220399ae3e05c16137a59db1f189523b42c7f88bd097ee991dbd7bc0e01113c583e844 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@jsonjoy.com/base64@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/base64@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/d9616ec1ac0ea6aa455968b1f96f2d48ce38a2b1835922a909a55147d7b8cff3d648d45e9efe6781c6926beb5f04dc41c75ce548b6b84141b14bc122893e16ee + languageName: node + linkType: hard + +"@jsonjoy.com/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@jsonjoy.com/base64@npm:1.1.2" + peerDependencies: + tslib: 2 + checksum: 10c0/88717945f66dc89bf58ce75624c99fe6a5c9a0c8614e26d03e406447b28abff80c69fb37dabe5aafef1862cf315071ae66e5c85f6018b437d95f8d13d235e6eb + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:17.67.0, @jsonjoy.com/buffers@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/buffers@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/ee46d3ea6c2dee4dd5dffd8b156745baeecfe796c7bb3f091f9fe64c402aca5e4d86ba3d736545682f919303fb15359c1f00d41ac91ea1b5d4edbbe74f540d35 + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": + version: 1.2.1 + resolution: "@jsonjoy.com/buffers@npm:1.2.1" + peerDependencies: + tslib: 2 + checksum: 10c0/5edaf761b78b730ae0598824adb37473fef5b40a8fc100625159700eb36e00057c5129c7ad15fc0e3178e8de58a044da65728e8d7b05fd3eed58e9b9a0d02b5a + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/codegen@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/3cc529377cc315acf373dc52dbd39d56285b31ba8ca90a4447230e37e405372cc13bed7df638dc81f9071ff8f4eb8e825217987397d80182d08ded761e609a93 + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:^1.0.0": + version: 1.0.0 + resolution: "@jsonjoy.com/codegen@npm:1.0.0" + peerDependencies: + tslib: 2 + checksum: 10c0/54686352248440ad1484ce7db0270a5a72424fb9651b090e5f1c8e2cd8e55e6c7a3f67dfe4ed90c689cf01ed949e794764a8069f5f52510eaf0a2d0c41d324cd + languageName: node + linkType: hard + +"@jsonjoy.com/fs-core@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-core@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.57.1" + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10c0/8269bb457dfbb783705b12962a2aaae8e40b180801750b8f4029ee8a6ee9941c039e88804eae2764f9a024992ff87bebdd006a65cb0d027fdec11a37b77ac209 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-fsa@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-fsa@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.57.1" + "@jsonjoy.com/fs-node-builtins": "npm:4.57.1" + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10c0/644e1af00d5ab5bae840c737dd7885e92d423fec8fbe77d605f30dd77a858fef0112e2d77fd4009fc4acce7f2344eacb2bcd695052c2240d5b39532aac9bcada + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-builtins@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-node-builtins@npm:4.57.1" + peerDependencies: + tslib: 2 + checksum: 10c0/971d46ea04fbe8803967d2fa7fdf9959bbe395cc740fbcf07f2b8632cd5abd242ec10adef29b4d6019de5753aa1e8a4c4e3cd14592bcebef918bdc7078be974b + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-to-fsa@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-node-to-fsa@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-fsa": "npm:4.57.1" + "@jsonjoy.com/fs-node-builtins": "npm:4.57.1" + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + peerDependencies: + tslib: 2 + checksum: 10c0/8efd27c4411cce5f5ee26f27c41f65aef069807b0f98496cbb7e73775328a14a9a9da04ec1bd7e1276674e7467712cb05fc729a5fb5fe8353cad9f4de1bf2843 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-utils@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-node-utils@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.57.1" + peerDependencies: + tslib: 2 + checksum: 10c0/eea2c25483d304488f9572aaea0940e2528ddb7aa529e9b9ae8ec6f828413cb5597f574510c0adef0d0d54c0de2dfd50f666f24a98a24166e9dc72f3b144f8c5 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-node@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.57.1" + "@jsonjoy.com/fs-node-builtins": "npm:4.57.1" + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + "@jsonjoy.com/fs-print": "npm:4.57.1" + "@jsonjoy.com/fs-snapshot": "npm:4.57.1" + glob-to-regex.js: "npm:^1.0.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10c0/b98f2671330d04191f61f282b65d773ae8bf5dca2f0b8c339e34f0d6a76e949ff3439a9e45dc417d8d661b1b6311cd0699289b72f0ae80d3b5d6211e5086485f + languageName: node + linkType: hard + +"@jsonjoy.com/fs-print@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-print@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/c611103134aefa1d111b375a8509a3b58381a6fae3b9cc01b35e16dd4a1d9ef0e21648b51f97d2a442adbc9d4a462179285564e1deaefea4e2cb920dccc24922 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-snapshot@npm:4.57.1": + version: 4.57.1 + resolution: "@jsonjoy.com/fs-snapshot@npm:4.57.1" + dependencies: + "@jsonjoy.com/buffers": "npm:^17.65.0" + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + "@jsonjoy.com/json-pack": "npm:^17.65.0" + "@jsonjoy.com/util": "npm:^17.65.0" + peerDependencies: + tslib: 2 + checksum: 10c0/ded857cebc0bb3de03f2c1520b1c000cb498e99c47b20e7231fa87eb87b42e600b9804e06e3e7136432a503d330a33da31185871192b93873719b300c533b5aa + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^1.11.0": + version: 1.21.0 + resolution: "@jsonjoy.com/json-pack@npm:1.21.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.2" + "@jsonjoy.com/buffers": "npm:^1.2.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/json-pointer": "npm:^1.0.2" + "@jsonjoy.com/util": "npm:^1.9.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/0183eccccf2ab912389a6784ae81c1a7da48cf178902efe093fb60c457359c7c75da2803f869e0a1489f1342dfa4f8ab9b27b65adc9f44fd9646823773b71e9d + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pack@npm:17.67.0" + dependencies: + "@jsonjoy.com/base64": "npm:17.67.0" + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + "@jsonjoy.com/json-pointer": "npm:17.67.0" + "@jsonjoy.com/util": "npm:17.67.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/fee56d024c84f031ef011a85ccca071c73b8a0739506083bd3dc7a17c720a498599f285e79082a9626314324ea938f189d18d47a03341cb76286ca2e7098bf53 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pointer@npm:17.67.0" + dependencies: + "@jsonjoy.com/util": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/763e0b1bc274390a605073b49e5bf55bdf386e784f5940d456faca958d90915b7d9a47dd9d58a08e2113f40167b0640d313897811680eb91630726920618fe7d + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:^1.0.2": + version: 1.0.2 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" + dependencies: + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/util": "npm:^1.9.0" + peerDependencies: + tslib: 2 + checksum: 10c0/8d959c0fdd77d937d2a829270de51533bb9e3b887b3f6f02943884dc33dd79225071218c93f4bafdee6a3412fd5153264997953a86de444d85c1fff67915af54 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:17.67.0, @jsonjoy.com/util@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/util@npm:17.67.0" + dependencies: + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/44be53d94c99ce74a0eff1bb111f0ff4392a1226e34637321c8bc45b569da3f9e12db8b225eef3694c44b9fd2e9b800d7baf5ea0d38e1d7767bfcbef4fbf91b0 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.9.0": + version: 1.9.0 + resolution: "@jsonjoy.com/util@npm:1.9.0" + dependencies: + "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + peerDependencies: + tslib: 2 + checksum: 10c0/a720a6accaae71fa9e7fa06e93e382702aa5760ef2bdc3bc45c19dc2228a01cc735d36cb970c654bc5e88f1328d55d1f0d5eceef0b76bcc327a2ce863e7b0021 + languageName: node + linkType: hard + +"@leichtgewicht/ip-codec@npm:^2.0.1": + version: 2.0.5 + resolution: "@leichtgewicht/ip-codec@npm:2.0.5" + checksum: 10c0/14a0112bd59615eef9e3446fea018045720cd3da85a98f801a685a818b0d96ef2a1f7227e8d271def546b2e2a0fe91ef915ba9dc912ab7967d2317b1a051d66b + languageName: node + linkType: hard + +"@loaderkit/resolve@npm:^1.0.2": + version: 1.0.4 + resolution: "@loaderkit/resolve@npm:1.0.4" + dependencies: + "@braidai/lang": "npm:^1.0.0" + checksum: 10c0/d0226c60b1ff08358d1f7711de5757553cf71df32129ad518dd760bce5a9bf80c908424f84cbd03d2dd9598dd53691b1b848adbd97463ac826ea7bc47d552236 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.4.0": + version: 1.4.0 + resolution: "@noble/hashes@npm:1.4.0" + checksum: 10c0/8c3f005ee72e7b8f9cff756dfae1241485187254e3f743873e22073d63906863df5d4f13d441b7530ea614b7a093f0d889309f28b59850f33b66cb26a779a4a5 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@npmcli/redact@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/redact@npm:4.0.0" + checksum: 10c0/a1e9ba9c70a6b40e175bda2c3dd8cfdaf096e6b7f7a132c855c083c8dfe545c3237cd56702e2e6627a580b1d63373599d49a1192c4078a85bf47bbde824df31c + languageName: node + linkType: hard + +"@peculiar/asn1-cms@npm:^2.6.0, @peculiar/asn1-cms@npm:^2.6.1": + version: 2.6.1 + resolution: "@peculiar/asn1-cms@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + "@peculiar/asn1-x509-attr": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/682e952fb35dec229bf54ecaff58bdf56281c1d718b5bcc2da103d67b5be302452c6275300c9f9fce1ed02f03792dab3ebe98c903117e0a5b0d9e5d861356280 + languageName: node + linkType: hard + +"@peculiar/asn1-csr@npm:^2.6.0": + version: 2.6.1 + resolution: "@peculiar/asn1-csr@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/5ea1ef27bf3879c793acb0b370b9fc1cb45df244b4706cecf075e45b58d19d65e612f4777eb12aa37f2037c1c725e96543ab9caf41d5a92378c5069071deae1f + languageName: node + linkType: hard + +"@peculiar/asn1-ecc@npm:^2.6.0": + version: 2.6.1 + resolution: "@peculiar/asn1-ecc@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/7804600f12a8993085232839ea020f51a329a195ce991ebbce077668d9ee1e57301bf97d5ef9657bd81717888f36f51f7aed3a9eee59fe4345c55d04eff89927 + languageName: node + linkType: hard + +"@peculiar/asn1-pfx@npm:^2.6.1": + version: 2.6.1 + resolution: "@peculiar/asn1-pfx@npm:2.6.1" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.1" + "@peculiar/asn1-pkcs8": "npm:^2.6.1" + "@peculiar/asn1-rsa": "npm:^2.6.1" + "@peculiar/asn1-schema": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/69c86ed339b945f7c77173da06207af71553a5b033cc1f2bde262085e7b5870543f358a29efd8981ca7247ec7f1c5d722a014cc0979679045909cb13e2ca527e + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs8@npm:^2.6.1": + version: 2.6.1 + resolution: "@peculiar/asn1-pkcs8@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/d712dc79ab877152f20c1772cbe065f5beb2a20e3dcae7892cc72f3227a1d3f7ae8eecba8bc29cf2b77cfdd8a01b0660f5390a416ca78ca7147f0e3c13d4d755 + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs9@npm:^2.6.0": + version: 2.6.1 + resolution: "@peculiar/asn1-pkcs9@npm:2.6.1" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.1" + "@peculiar/asn1-pfx": "npm:^2.6.1" + "@peculiar/asn1-pkcs8": "npm:^2.6.1" + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + "@peculiar/asn1-x509-attr": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/4a2f815bbeee3f65aea391d5e2287a19701d757d2781b3ecfd908a67028f2752796bd22f8ba3eb486911fcc34b52b0f7c1ff3e3b7d7f04ef58767be9ddbc851d + languageName: node + linkType: hard + +"@peculiar/asn1-rsa@npm:^2.6.0, @peculiar/asn1-rsa@npm:^2.6.1": + version: 2.6.1 + resolution: "@peculiar/asn1-rsa@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/4d7c71c5bddf7be3b0270c4d95b8274a392185cad4939a7a837d9c4c612601fee1a1ccabe414383b26629fb2013608e60a58ecd665c371617c1f177431a88ff2 + languageName: node + linkType: hard + +"@peculiar/asn1-schema@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-schema@npm:2.6.0" + dependencies: + asn1js: "npm:^3.0.6" + pvtsutils: "npm:^1.3.6" + tslib: "npm:^2.8.1" + checksum: 10c0/8c283b10a2e4aca4cb20d242cde773c9a798ea15a6c221d1474ef483e182d48195aeb5dde3f7b518f236eceb7808ae4438539d41a3aa9ed6d20aa4d36a21a0c2 + languageName: node + linkType: hard + +"@peculiar/asn1-x509-attr@npm:^2.6.1": + version: 2.6.1 + resolution: "@peculiar/asn1-x509-attr@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.1" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10c0/de8634ec12ef34b430e5a458151e856f954e15fe9e08d056dca51db6962e849a951820ab66d291e2452799576c44221b40087b9350dc3728d3770a46fcdeffc5 + languageName: node + linkType: hard + +"@peculiar/asn1-x509@npm:^2.6.0, @peculiar/asn1-x509@npm:^2.6.1": + version: 2.6.1 + resolution: "@peculiar/asn1-x509@npm:2.6.1" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + pvtsutils: "npm:^1.3.6" + tslib: "npm:^2.8.1" + checksum: 10c0/2e73a0ce6521eeb2d876e0b52e9fae2de4e2d183be5fba77d5fae9b7724de446d02c0b4e5fb04d4fedb50eed0de842f29f4d7cf2e998eaed6a2d2952f5c52d2c + languageName: node + linkType: hard + +"@peculiar/x509@npm:^1.14.2": + version: 1.14.3 + resolution: "@peculiar/x509@npm:1.14.3" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-csr": "npm:^2.6.0" + "@peculiar/asn1-ecc": "npm:^2.6.0" + "@peculiar/asn1-pkcs9": "npm:^2.6.0" + "@peculiar/asn1-rsa": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + pvtsutils: "npm:^1.3.6" + reflect-metadata: "npm:^0.2.2" + tslib: "npm:^2.8.1" + tsyringe: "npm:^4.10.0" + checksum: 10c0/949231ca9daf84534bfe255f28a856df497302fed294d227c6a28e50f5cfb67ed1d91afe6db787b88294ce042295243dbcb44455fe2efa5ed07428a74392eec9 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm64@npm:4.60.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.60.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.60.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.60.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.60.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.60.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.60.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.60.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.60.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.60.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.10 + resolution: "@sinclair/typebox@npm:0.27.10" + checksum: 10c0/ca42a02817656dbdae464ed4bb8aca6ad4718d7618e270760fea84a834ad0ecc1a22eba51421f09e5047174571131356ff3b5d80d609ced775d631df7b404b0d + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^4.6.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e + languageName: node + linkType: hard + +"@socket.io/component-emitter@npm:~3.1.0": + version: 3.1.2 + resolution: "@socket.io/component-emitter@npm:3.1.2" + checksum: 10c0/c4242bad66f67e6f7b712733d25b43cbb9e19a595c8701c3ad99cbeb5901555f78b095e24852f862fffb43e96f1d8552e62def885ca82ae1bb05da3668fd87d7 + languageName: node + linkType: hard + +"@tsd/typescript@npm:^5.9.2": + version: 5.9.3 + resolution: "@tsd/typescript@npm:5.9.3" + checksum: 10c0/1419e6fe468b245e3eb18f837514d68905b475884a4a8540b134ba848c9b4fdf56cd0692b0289dc92aa3d7cc2868e75ef32994a99fd0f16161650aeee8005218 + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.6 + resolution: "@types/body-parser@npm:1.19.6" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/542da05c924dce58ee23f50a8b981fee36921850c82222e384931fda3e106f750f7880c47be665217d72dbe445129049db6eb1f44e7a06b09d62af8f3cca8ea7 + languageName: node + linkType: hard + +"@types/bonjour@npm:^3.5.13": + version: 3.5.13 + resolution: "@types/bonjour@npm:3.5.13" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/eebedbca185ac3c39dd5992ef18d9e2a9f99e7f3c2f52f5561f90e9ed482c5d224c7962db95362712f580ed5713264e777a98d8f0bd8747f4eadf62937baed16 + languageName: node + linkType: hard + +"@types/connect-history-api-fallback@npm:^1.5.4": + version: 1.5.4 + resolution: "@types/connect-history-api-fallback@npm:1.5.4" + dependencies: + "@types/express-serve-static-core": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/1b4035b627dcd714b05a22557f942e24a57ca48e7377dde0d2f86313fe685bc0a6566512a73257a55b5665b96c3041fb29228ac93331d8133011716215de8244 + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + +"@types/cors@npm:^2.8.12": + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/b5dd407040db7d8aa1bd36e79e5f3f32292f6b075abc287529e9f48df1a25fda3e3799ba30b4656667ffb931d3b75690c1d6ca71e39f7337ea6dfda8581916d0 + languageName: node + linkType: hard + +"@types/eslint-scope@npm:^3.7.7": + version: 3.7.7 + resolution: "@types/eslint-scope@npm:3.7.7" + dependencies: + "@types/eslint": "npm:*" + "@types/estree": "npm:*" + checksum: 10c0/a0ecbdf2f03912679440550817ff77ef39a30fa8bfdacaf6372b88b1f931828aec392f52283240f0d648cf3055c5ddc564544a626bcf245f3d09fcb099ebe3cc + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 9.6.1 + resolution: "@types/eslint@npm:9.6.1" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e + languageName: node + linkType: hard + +"@types/eslint@npm:^7.2.13": + version: 7.29.0 + resolution: "@types/eslint@npm:7.29.0" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10c0/780ea3f4abba77a577a9ca5c4b66f74acc0f5ff5162b9a361ca931763ed65bca062389fc26027b416ed0a54d390e2206412db6c682f565e523d2b82159e6c46f + languageName: node + linkType: hard + +"@types/esrecurse@npm:^4.3.1": + version: 4.3.1 + resolution: "@types/esrecurse@npm:4.3.1" + checksum: 10c0/90dad74d5da3ad27606d8e8e757322f33171cfeaa15ad558b615cf71bb2a516492d18f55f4816384685a3eb2412142e732bbae9a4a7cd2cf3deb7572aa4ebe03 + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^5.0.0": + version: 5.1.1 + resolution: "@types/express-serve-static-core@npm:5.1.1" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/ee88216e114368ef06bcafeceb74a7e8671b90900fb0ab1d49ff41542c3a344231ef0d922bf63daa79f0585f3eebe2ce5ec7f83facc581eff8bcdb136a225ef3 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33": + version: 4.19.8 + resolution: "@types/express-serve-static-core@npm:4.19.8" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/6fb58a85b209e0e421b29c52e0a51dbf7c039b711c604cf45d46470937a5c7c16b30aa5ce9bf7da0bd8a2e9361c95b5055599c0500a96bf4414d26c81f02d7fe + languageName: node + linkType: hard + +"@types/express@npm:*": + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:^2" + checksum: 10c0/f1071e3389a955d4f9a38aae38634121c7cd9b3171ba4201ec9b56bd534aba07866839d278adc0dda05b942b05a901a02fd174201c3b1f70ce22b10b6c68f24b + languageName: node + linkType: hard + +"@types/express@npm:^4.17.25": + version: 4.17.25 + resolution: "@types/express@npm:4.17.25" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:^1" + checksum: 10c0/f42b616d2c9dbc50352c820db7de182f64ebbfa8dba6fb6c98e5f8f0e2ef3edde0131719d9dc6874803d25ad9ca2d53471d0fec2fbc60a6003a43d015bab72c4 + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10c0/00f8140fbc504f47356512bd88e1910c2f07e04233d99c88c854b3600ce0523c8cd0ba7d1897667243282eb44c59abb9245959e2428b9de004f93937f52f7c15 + languageName: node + linkType: hard + +"@types/http-proxy@npm:^1.17.8": + version: 1.17.17 + resolution: "@types/http-proxy@npm:1.17.17" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/547e322a5eecf0b50d08f6a46bd89c8c8663d67dbdcd472da5daf968b03e63a82f6b3650443378abe6c10a46475dac52015f30e8c74ba2ea5820dd4e9cdef2d4 + languageName: node + linkType: hard + +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + +"@types/minimist@npm:^1.2.0": + version: 1.2.5 + resolution: "@types/minimist@npm:1.2.5" + checksum: 10c0/3f791258d8e99a1d7d0ca2bda1ca6ea5a94e5e7b8fc6cde84dd79b0552da6fb68ade750f0e17718f6587783c24254bbca0357648dd59dc3812c150305cabdc46 + languageName: node + linkType: hard + +"@types/node@npm:*, @types/node@npm:>=10.0.0": + version: 25.6.0 + resolution: "@types/node@npm:25.6.0" + dependencies: + undici-types: "npm:~7.19.0" + checksum: 10c0/d2d2015630ff098a201407f55f5077a20270ae4f465c739b40865cd9933b91b9c5d2b85568eadaf3db0801b91e267333ca7eb39f007428b173d1cdab4b339ac5 + languageName: node + linkType: hard + +"@types/normalize-package-data@npm:^2.4.0": + version: 2.4.4 + resolution: "@types/normalize-package-data@npm:2.4.4" + checksum: 10c0/aef7bb9b015883d6f4119c423dd28c4bdc17b0e8a0ccf112c78b4fe0e91fbc4af7c6204b04bba0e199a57d2f3fbbd5b4a14bf8739bf9d2a39b2a0aad545e0f86 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.15.0 + resolution: "@types/qs@npm:6.15.0" + checksum: 10c0/1b104cac50e655fc41d7fc1de2c2aba2908c4cf833a555b6808fb4c96752662b439238f2392a15d2590a7a6ca75dbd40e42d9378ac2be0d548ee484954363688 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/retry@npm:0.12.2": + version: 0.12.2 + resolution: "@types/retry@npm:0.12.2" + checksum: 10c0/07481551a988cc90b423351919928b9ddcd14e3f5591cac3ab950851bb20646e55a10e89141b38bc3093d2056d4df73700b22ff2612976ac86a6367862381884 + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/7673747f8c2d8e67f3b1b3b57e9d4d681801a4f7b526ecf09987bb9a84a61cf94aa411c736183884dc762c1c402a61681eb1ef200d8d45d7e5ec0ab67ea5f6c1 + languageName: node + linkType: hard + +"@types/send@npm:<1": + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10c0/a9d76797f0637738062f1b974e0fcf3d396a28c5dc18c3f95ecec5dabda82e223afbc2d56a0bca46b6326fd7bb229979916cea40de2270a98128fd94441b87c2 + languageName: node + linkType: hard + +"@types/serve-index@npm:^1.9.4": + version: 1.9.4 + resolution: "@types/serve-index@npm:1.9.4" + dependencies: + "@types/express": "npm:*" + checksum: 10c0/94c1b9e8f1ea36a229e098e1643d5665d9371f8c2658521718e259130a237c447059b903bac0dcc96ee2c15fd63f49aa647099b7d0d437a67a6946527a837438 + languageName: node + linkType: hard + +"@types/serve-static@npm:^1, @types/serve-static@npm:^1.15.5": + version: 1.15.10 + resolution: "@types/serve-static@npm:1.15.10" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:<1" + checksum: 10c0/842fca14c9e80468f89b6cea361773f2dcd685d4616a9f59013b55e1e83f536e4c93d6d8e3ba5072d40c4e7e64085210edd6646b15d538ded94512940a23021f + languageName: node + linkType: hard + +"@types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/a3c6126bdbf9685e6c7dc03ad34639666eff32754e912adeed9643bf3dd3aa0ff043002a7f69039306e310d233eb8e160c59308f95b0a619f32366bbc48ee094 + languageName: node + linkType: hard + +"@types/sockjs@npm:^0.3.36": + version: 0.3.36 + resolution: "@types/sockjs@npm:0.3.36" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/b20b7820ee813f22de4f2ce98bdd12c68c930e016a8912b1ed967595ac0d8a4cbbff44f4d486dd97f77f5927e7b5725bdac7472c9ec5b27f53a5a13179f0612f + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.10, @types/ws@npm:^8.5.12": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.58.1" + dependencies: + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.58.1" + "@typescript-eslint/type-utils": "npm:8.58.1" + "@typescript-eslint/utils": "npm:8.58.1" + "@typescript-eslint/visitor-keys": "npm:8.58.1" + ignore: "npm:^7.0.5" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + "@typescript-eslint/parser": ^8.58.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/694bdcb2b775a7d8b99e39701cd4b56ad0645063333b3bf3eb3f2802ba01122c442753677efedd65485c89af82cd7397ce14b50a54834e61bda4feae67ca1c8c + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/parser@npm:8.58.1" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.58.1" + "@typescript-eslint/types": "npm:8.58.1" + "@typescript-eslint/typescript-estree": "npm:8.58.1" + "@typescript-eslint/visitor-keys": "npm:8.58.1" + debug: "npm:^4.4.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/f1a1907079c2c2611011125218b0975d99547ac834ac434d7ff4e99fee4e938aedd6b8530ecdc5efc7bcc1a3b9d546252e318690d3e670c394b891ba75e66925 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/project-service@npm:8.58.1" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.58.1" + "@typescript-eslint/types": "npm:^8.58.1" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/c48541a1350f12817b1ab54ab0e4d2a853811449fdc6d02a0d9b617520262fd286d1e3c4adf38b677e807df84cdbf32033e898e71ec7649299ce92e820f8e85d + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/scope-manager@npm:8.58.1" + dependencies: + "@typescript-eslint/types": "npm:8.58.1" + "@typescript-eslint/visitor-keys": "npm:8.58.1" + checksum: 10c0/c7c67d249a9d1dd348ec29878e588422f2fe15531dfe83ff6fa35b8a0bffc2db9ee8a4e8fcc086742a32bc0c5da6c8ff3f4d4b007a62019b3f1da4381947ea7e + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.58.1, @typescript-eslint/tsconfig-utils@npm:^8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.58.1" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/dcccf8c64e3806e3bcac750f9746f852cbf36abb816afb3e3a825f7d0268eb0bf3aa97c019082d0976508b93d2f09ff21cdfffcbffdc3204db3cb98cd0aa33cc + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/type-utils@npm:8.58.1" + dependencies: + "@typescript-eslint/types": "npm:8.58.1" + "@typescript-eslint/typescript-estree": "npm:8.58.1" + "@typescript-eslint/utils": "npm:8.58.1" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/df3dd6f69edd8dd52c576882e8da0e810b47ad1608a3a57d82ff8a2ca12f134a715d0e1ec994bf877a7c6aecdeea349c305b3b8e4b39359c0c90417dc1cb9244 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.58.1, @typescript-eslint/types@npm:^8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/types@npm:8.58.1" + checksum: 10c0/c468e2e3748d0d9a178b1e0f4a8dccb95085ba732ba9e462c21a3ac9be91ab63ce8147f3a181081f7a758f9c885ee6b2e0f5f890ee3f0f405e3caab515130b1a + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.58.1" + dependencies: + "@typescript-eslint/project-service": "npm:8.58.1" + "@typescript-eslint/tsconfig-utils": "npm:8.58.1" + "@typescript-eslint/types": "npm:8.58.1" + "@typescript-eslint/visitor-keys": "npm:8.58.1" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/06ad23dc71a7733c3f01019b7d426c2ebe1f4a845f3843d22f69c63aba8a3e8224a3e847996382da8ce253b3cff42f4f69a57b3db0bb2bc938291bf31d79ea4a + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/utils@npm:8.58.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.58.1" + "@typescript-eslint/types": "npm:8.58.1" + "@typescript-eslint/typescript-estree": "npm:8.58.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/99538feaaa7e5a08c8cfeaaeff5775812bdaf9faba602d55341102761e84ffee8e1fbfbadc9dbd9b036feedc6b541550b300fe26b90ae92f92d1b687dc65ecda + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.58.1": + version: 8.58.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.58.1" + dependencies: + "@typescript-eslint/types": "npm:8.58.1" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/d2709bfb63bd86eb7b28bc86c15d9b29a8cceb5e25843418b039f497a1007fc92fa02eef8a2cbfd9cdec47f490205a00eab7fb204fd14472cf31b8db0e2db963 + languageName: node + linkType: hard + +"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/ast@npm:1.14.1" + dependencies: + "@webassemblyjs/helper-numbers": "npm:1.13.2" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + checksum: 10c0/67a59be8ed50ddd33fbb2e09daa5193ac215bf7f40a9371be9a0d9797a114d0d1196316d2f3943efdb923a3d809175e1563a3cb80c814fb8edccd1e77494972b + languageName: node + linkType: hard + +"@webassemblyjs/floating-point-hex-parser@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.13.2" + checksum: 10c0/0e88bdb8b50507d9938be64df0867f00396b55eba9df7d3546eb5dc0ca64d62e06f8d881ec4a6153f2127d0f4c11d102b6e7d17aec2f26bb5ff95a5e60652412 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-api-error@npm:1.13.2" + checksum: 10c0/31be497f996ed30aae4c08cac3cce50c8dcd5b29660383c0155fce1753804fc55d47fcba74e10141c7dd2899033164e117b3bcfcda23a6b043e4ded4f1003dfb + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.14.1" + checksum: 10c0/0d54105dc373c0fe6287f1091e41e3a02e36cdc05e8cf8533cdc16c59ff05a646355415893449d3768cda588af451c274f13263300a251dc11a575bc4c9bd210 + languageName: node + linkType: hard + +"@webassemblyjs/helper-numbers@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-numbers@npm:1.13.2" + dependencies: + "@webassemblyjs/floating-point-hex-parser": "npm:1.13.2" + "@webassemblyjs/helper-api-error": "npm:1.13.2" + "@xtuc/long": "npm:4.2.2" + checksum: 10c0/9c46852f31b234a8fb5a5a9d3f027bc542392a0d4de32f1a9c0075d5e8684aa073cb5929b56df565500b3f9cc0a2ab983b650314295b9bf208d1a1651bfc825a + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-bytecode@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.13.2" + checksum: 10c0/c4355d14f369b30cf3cbdd3acfafc7d0488e086be6d578e3c9780bd1b512932352246be96e034e2a7fcfba4f540ec813352f312bfcbbfe5bcfbf694f82ccc682 + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-section@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + checksum: 10c0/1f9b33731c3c6dbac3a9c483269562fa00d1b6a4e7133217f40e83e975e636fd0f8736e53abd9a47b06b66082ecc976c7384391ab0a68e12d509ea4e4b948d64 + languageName: node + linkType: hard + +"@webassemblyjs/ieee754@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/ieee754@npm:1.13.2" + dependencies: + "@xtuc/ieee754": "npm:^1.2.0" + checksum: 10c0/2e732ca78c6fbae3c9b112f4915d85caecdab285c0b337954b180460290ccd0fb00d2b1dc4bb69df3504abead5191e0d28d0d17dfd6c9d2f30acac8c4961c8a7 + languageName: node + linkType: hard + +"@webassemblyjs/leb128@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/leb128@npm:1.13.2" + dependencies: + "@xtuc/long": "npm:4.2.2" + checksum: 10c0/dad5ef9e383c8ab523ce432dfd80098384bf01c45f70eb179d594f85ce5db2f80fa8c9cba03adafd85684e6d6310f0d3969a882538975989919329ac4c984659 + languageName: node + linkType: hard + +"@webassemblyjs/utf8@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/utf8@npm:1.13.2" + checksum: 10c0/d3fac9130b0e3e5a1a7f2886124a278e9323827c87a2b971e6d0da22a2ba1278ac9f66a4f2e363ecd9fac8da42e6941b22df061a119e5c0335f81006de9ee799 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-edit@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/helper-wasm-section": "npm:1.14.1" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + "@webassemblyjs/wasm-opt": "npm:1.14.1" + "@webassemblyjs/wasm-parser": "npm:1.14.1" + "@webassemblyjs/wast-printer": "npm:1.14.1" + checksum: 10c0/5ac4781086a2ca4b320bdbfd965a209655fe8a208ca38d89197148f8597e587c9a2c94fb6bd6f1a7dbd4527c49c6844fcdc2af981f8d793a97bf63a016aa86d2 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-gen@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/ieee754": "npm:1.13.2" + "@webassemblyjs/leb128": "npm:1.13.2" + "@webassemblyjs/utf8": "npm:1.13.2" + checksum: 10c0/d678810d7f3f8fecb2e2bdadfb9afad2ec1d2bc79f59e4711ab49c81cec578371e22732d4966f59067abe5fba8e9c54923b57060a729d28d408e608beef67b10 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-opt@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + "@webassemblyjs/wasm-parser": "npm:1.14.1" + checksum: 10c0/515bfb15277ee99ba6b11d2232ddbf22aed32aad6d0956fe8a0a0a004a1b5a3a277a71d9a3a38365d0538ac40d1b7b7243b1a244ad6cd6dece1c1bb2eb5de7ee + languageName: node + linkType: hard + +"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-api-error": "npm:1.13.2" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/ieee754": "npm:1.13.2" + "@webassemblyjs/leb128": "npm:1.13.2" + "@webassemblyjs/utf8": "npm:1.13.2" + checksum: 10c0/95427b9e5addbd0f647939bd28e3e06b8deefdbdadcf892385b5edc70091bf9b92fa5faac3fce8333554437c5d85835afef8c8a7d9d27ab6ba01ffab954db8c6 + languageName: node + linkType: hard + +"@webassemblyjs/wast-printer@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wast-printer@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@xtuc/long": "npm:4.2.2" + checksum: 10c0/8d7768608996a052545251e896eac079c98e0401842af8dd4de78fba8d90bd505efb6c537e909cd6dae96e09db3fa2e765a6f26492553a675da56e2db51f9d24 + languageName: node + linkType: hard + +"@xtuc/ieee754@npm:^1.2.0": + version: 1.2.0 + resolution: "@xtuc/ieee754@npm:1.2.0" + checksum: 10c0/a8565d29d135039bd99ae4b2220d3e167d22cf53f867e491ed479b3f84f895742d0097f935b19aab90265a23d5d46711e4204f14c479ae3637fbf06c4666882f + languageName: node + linkType: hard + +"@xtuc/long@npm:4.2.2": + version: 4.2.2 + resolution: "@xtuc/long@npm:4.2.2" + checksum: 10c0/8582cbc69c79ad2d31568c412129bf23d2b1210a1dfb60c82d5a1df93334da4ee51f3057051658569e2c196d8dc33bc05ae6b974a711d0d16e801e1d0647ccd1 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"accepts@npm:~1.3.4, accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + +"acorn-import-phases@npm:^1.0.3": + version: 1.0.4 + resolution: "acorn-import-phases@npm:1.0.4" + peerDependencies: + acorn: ^8.14.0 + checksum: 10c0/338eb46fc1aed5544f628344cb9af189450b401d152ceadbf1f5746901a5d923016cd0e7740d5606062d374fdf6941c29bb515d2bd133c4f4242d5d4cd73a3c7 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.15.0, acorn@npm:^8.16.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: "npm:^2.0.0" + indent-string: "npm:^4.0.0" + checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10c0/e43ba22e91b6a48d96224b83d260d3a3a561b42d391f8d3c6d2c1559f9aa5b253bfb306bc94bbeca1d967c014e15a6efe9a207309e95b3eaae07fcbcdc2af662 + languageName: node + linkType: hard + +"ajv-keywords@npm:^5.1.0": + version: 5.1.0 + resolution: "ajv-keywords@npm:5.1.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + peerDependencies: + ajv: ^8.8.2 + checksum: 10c0/18bec51f0171b83123ba1d8883c126e60c6f420cef885250898bf77a8d3e65e3bfb9e8564f497e30bdbe762a83e0d144a36931328616a973ee669dc74d4a9590 + languageName: node + linkType: hard + +"ajv@npm:^6.14.0": + version: 6.14.0 + resolution: "ajv@npm:6.14.0" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/a2bc39b0555dc9802c899f86990eb8eed6e366cddbf65be43d5aa7e4f3c4e1a199d5460fd7ca4fb3d864000dbbc049253b72faa83b3b30e641ca52cb29a68c22 + languageName: node + linkType: hard + +"ajv@npm:^8.0.0, ajv@npm:^8.9.0": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-escapes@npm:^7.0.0": + version: 7.3.0 + resolution: "ansi-escapes@npm:7.3.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/068961d99f0ef28b661a4a9f84a5d645df93ccf3b9b93816cc7d46bbe1913321d4cdf156bb842a4e1e4583b7375c631fa963efb43001c4eb7ff9ab8f78fc0679 + languageName: node + linkType: hard + +"ansi-html-community@npm:^0.0.8": + version: 0.0.8 + resolution: "ansi-html-community@npm:0.0.8" + bin: + ansi-html: bin/ansi-html + checksum: 10c0/45d3a6f0b4f10b04fdd44bef62972e2470bfd917bf00439471fa7473d92d7cbe31369c73db863cc45dda115cb42527f39e232e9256115534b8ee5806b0caeed4 + languageName: node + linkType: hard + +"ansi-regex@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-regex@npm:3.0.1" + checksum: 10c0/d108a7498b8568caf4a46eea4f1784ab4e0dfb2e3f3938c697dee21443d622d765c958f2b7e2b9f6b9e55e2e2af0584eaa9915d51782b89a841c28e744e7a167 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.1.0, ansi-regex@npm:^6.2.2": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 10c0/60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"append-transform@npm:^2.0.0": + version: 2.0.0 + resolution: "append-transform@npm:2.0.0" + dependencies: + default-require-extensions: "npm:^3.0.0" + checksum: 10c0/f1505e4f4597f4eb7b3df8da898e431fc25d6cdc6c78d01c700a4fab38d835e7cbac693eade8df7b0a0944dc52a35f92b1771e440af59f1b1f8a1dadaba7d17b + languageName: node + linkType: hard + +"archy@npm:^1.0.0": + version: 1.0.0 + resolution: "archy@npm:1.0.0" + checksum: 10c0/200c849dd1c304ea9914827b0555e7e1e90982302d574153e28637db1a663c53de62bad96df42d50e8ce7fc18d05e3437d9aa8c4b383803763755f0956c7d308 + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"arrify@npm:^1.0.1": + version: 1.0.1 + resolution: "arrify@npm:1.0.1" + checksum: 10c0/c35c8d1a81bcd5474c0c57fe3f4bad1a4d46a5fa353cedcff7a54da315df60db71829e69104b859dff96c5d68af46bd2be259fe5e50dc6aa9df3b36bea0383ab + languageName: node + linkType: hard + +"asn1js@npm:^3.0.6": + version: 3.0.7 + resolution: "asn1js@npm:3.0.7" + dependencies: + pvtsutils: "npm:^1.3.6" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.8.1" + checksum: 10c0/7e79795edf1bcc86532c4084aa7c8c0ebc57f7dd6f964ad6de956abf617329722f6964b7af3a5d1c4554dd61b4b148ae1580e63e3ec2e70e7fba34f6df072b29 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"babel-loader@npm:^10.1.1": + version: 10.1.1 + resolution: "babel-loader@npm:10.1.1" + dependencies: + find-up: "npm:^5.0.0" + peerDependencies: + "@babel/core": ^7.12.0 || ^8.0.0-beta.1 + "@rspack/core": ^1.0.0 || ^2.0.0-0 + webpack: ">=5.61.0" + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10c0/09a14b66aadfc98b4fde123bcdf6e5481fb0a8a0daad1e225ea27656fd006a426d6446d34763b6af94f7a7333c52c9bead9ebefb42ad5b09a27bad65b6dac8f7 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:8.0.0": + version: 8.0.0 + resolution: "babel-plugin-istanbul@npm:8.0.0" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-instrument: "npm:^6.0.2" + test-exclude: "npm:^7.0.1" + checksum: 10c0/c8afa37f2e9d4fc8ab174636a2ca7fcbf0cc0531ebab80003044b923fe93388b1052c693259af45cf7edf304e448422aee700417ee62d8ddf085e00ff24cbca0 + languageName: node + linkType: hard + +"babel-plugin-polyfill-corejs2@npm:^0.4.15": + version: 0.4.17 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.17" + dependencies: + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.8" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/1284960ea403c63b0dd598f338666c4b17d489aefee30b4da6a7313eff1d91edffb0ccf26341a6e5d94231684b74e016eade66b3921ea112f8b0e4980fa08a5c + languageName: node + linkType: hard + +"babel-plugin-polyfill-corejs3@npm:^0.14.0": + version: 0.14.2 + resolution: "babel-plugin-polyfill-corejs3@npm:0.14.2" + dependencies: + "@babel/helper-define-polyfill-provider": "npm:^0.6.8" + core-js-compat: "npm:^3.48.0" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/32f70442f142d0f5607f4b57c121c573b106e09da8659c0f03108a85bf1d09ba5bdc89595a82b34ff76c19f1faf3d1c831b56166f03babf69c024f36da77c3bf + languageName: node + linkType: hard + +"babel-plugin-polyfill-regenerator@npm:^0.6.6": + version: 0.6.8 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.8" + dependencies: + "@babel/helper-define-polyfill-provider": "npm:^0.6.8" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/7c8b2497c29fa880e0acdc8e7b93e29b81b154179b83beb0476eb2c4e7a78b6b42fc35c2554ca250c9bd6d39941eaf75416254b8592ce50979f9a12e1d51c049 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + +"base64id@npm:2.0.0, base64id@npm:~2.0.0": + version: 2.0.0 + resolution: "base64id@npm:2.0.0" + checksum: 10c0/6919efd237ed44b9988cbfc33eca6f173a10e810ce50292b271a1a421aac7748ef232a64d1e6032b08f19aae48dce6ee8f66c5ae2c9e5066c82b884861d4d453 + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.10.12": + version: 2.10.18 + resolution: "baseline-browser-mapping@npm:2.10.18" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/cab0138dd70b3aefe83e27cd03013bfdea772d2329e6458178cceed34b7e3fde72b697c9e4c21fb3c796bcfb831d348bc947b017d8b64417045a34bf014c87b7 + languageName: node + linkType: hard + +"batch@npm:0.6.1": + version: 0.6.1 + resolution: "batch@npm:0.6.1" + checksum: 10c0/925a13897b4db80d4211082fe287bcf96d297af38e26448c857cee3e095c9792e3b8f26b37d268812e7f38a589f694609de8534a018b1937d7dc9f84e6b387c5 + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + +"body-parser@npm:^1.19.0, body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" + dependencies: + bytes: "npm:~3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:~1.2.0" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + on-finished: "npm:~2.4.1" + qs: "npm:~6.14.0" + raw-body: "npm:~2.5.3" + type-is: "npm:~1.6.18" + unpipe: "npm:~1.0.0" + checksum: 10c0/569c1e896297d1fcd8f34026c8d0ab70b90d45343c15c5d8dff5de2bad08125fc1e2f8c2f3f4c1ac6c0caaad115218202594d37dcb8d89d9b5dcae1c2b736aa9 + languageName: node + linkType: hard + +"bonjour-service@npm:^1.2.1": + version: 1.3.0 + resolution: "bonjour-service@npm:1.3.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + multicast-dns: "npm:^7.2.5" + checksum: 10c0/5721fd9f9bb968e9cc16c1e8116d770863dd2329cb1f753231de1515870648c225142b7eefa71f14a5c22bc7b37ddd7fdeb018700f28a8c936d50d4162d433c7 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.14 + resolution: "brace-expansion@npm:1.1.14" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/b6fdac832bc4e36a753658c9ed052c2e1a2be221763b002df25d1efbf7d21724334e726a6cd5eadc72a4b19ec3efb632d629cc003bc9c62f7af7a7915ffa4385 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.2": + version: 2.1.0 + resolution: "brace-expansion@npm:2.1.0" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/439cedf3e23d7993b37919f1d6fdc653ec21a42437ec3e7460bea9ca8b17edf7a24a633273c31d61aa4335877cf29a443f1871814131c87997a1e6223e1f1502 + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.5": + version: 5.0.5 + resolution: "brace-expansion@npm:5.0.5" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/4d238e14ed4f5cc9c07285550a41cef23121ca08ba99fa9eb5b55b580dcb6bf868b8210aa10526bdc9f8dc97f33ca2a7259039c4cc131a93042beddb424c48e3 + languageName: node + linkType: hard + +"braces@npm:^3.0.2, braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browser-stdout@npm:^1.3.1": + version: 1.3.1 + resolution: "browser-stdout@npm:1.3.1" + checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0, browserslist@npm:^4.28.1": + version: 4.28.2 + resolution: "browserslist@npm:4.28.2" + dependencies: + baseline-browser-mapping: "npm:^2.10.12" + caniuse-lite: "npm:^1.0.30001782" + electron-to-chromium: "npm:^1.5.328" + node-releases: "npm:^2.0.36" + update-browserslist-db: "npm:^1.2.3" + bin: + browserslist: cli.js + checksum: 10c0/c0228b6330f785b7fa59d2d360124ec6d9322f96ed9f3ee1f873e33ecc9503a6f0ffc3b71191a28c4ff6e930b753b30043da1c33844a9548f3018d491f09ce60 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + +"bytes@npm:3.1.2, bytes@npm:~3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"bytestreamjs@npm:^2.0.1": + version: 2.0.1 + resolution: "bytestreamjs@npm:2.0.1" + checksum: 10c0/edd66b7ca3f94aae99a1009304a42d82ca4c2085eb934192ff47a81f59215c975dc9d3cd8f23c40a2f43ef5b2fa6f01ace70b10ad247766cec6ec641b89eab48 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.4 + resolution: "cacache@npm:20.0.4" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + checksum: 10c0/539bf4020e44ba9ca5afc2ec435623ed7e0dd80c020097677e6b4a0545df5cc9d20b473212d01209c8b4aea43c0d095af0bb6da97bcb991642ea6fac0d7c462b + languageName: node + linkType: hard + +"caching-transform@npm:^4.0.0": + version: 4.0.0 + resolution: "caching-transform@npm:4.0.0" + dependencies: + hasha: "npm:^5.0.0" + make-dir: "npm:^3.0.0" + package-hash: "npm:^4.0.0" + write-file-atomic: "npm:^3.0.0" + checksum: 10c0/7b33669dadfad292636578087a1aa7bcf9fbd60d6cbc67e8f288e3667397193c00bdac35bb84d34bd44fa9209405791fd3ab101c2126109e6eaaef1b899da759 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"camelcase-keys@npm:^6.2.2": + version: 6.2.2 + resolution: "camelcase-keys@npm:6.2.2" + dependencies: + camelcase: "npm:^5.3.1" + map-obj: "npm:^4.0.0" + quick-lru: "npm:^4.0.1" + checksum: 10c0/bf1a28348c0f285c6c6f68fb98a9d088d3c0269fed0cdff3ea680d5a42df8a067b4de374e7a33e619eb9d5266a448fe66c2dd1f8e0c9209ebc348632882a3526 + languageName: node + linkType: hard + +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.0.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001782": + version: 1.0.30001787 + resolution: "caniuse-lite@npm:1.0.30001787" + checksum: 10c0/93a6975afbf07f49c9077b348948bbc25b6a82596ccd07b4b6503e48f6f968e1a1e057cc25004db8a2d1d4f35f0c792739e33634edfcefc88ff184c9a2f7a036 + languageName: node + linkType: hard + +"chai@npm:^6.2.2": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + +"chalk@npm:^2.0.1, chalk@npm:^2.1.0": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chalk@npm:^5.4.1": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"chokidar@npm:^3.5.1, chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"chrome-trace-event@npm:^1.0.2": + version: 1.0.4 + resolution: "chrome-trace-event@npm:1.0.4" + checksum: 10c0/3058da7a5f4934b87cf6a90ef5fb68ebc5f7d06f143ed5a4650208e5d7acae47bc03ec844b29fbf5ba7e46e8daa6acecc878f7983a4f4bb7271593da91e61ff5 + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.2.3": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"cli-highlight@npm:^2.1.11": + version: 2.1.11 + resolution: "cli-highlight@npm:2.1.11" + dependencies: + chalk: "npm:^4.0.0" + highlight.js: "npm:^10.7.1" + mz: "npm:^2.4.0" + parse5: "npm:^5.1.1" + parse5-htmlparser2-tree-adapter: "npm:^6.0.0" + yargs: "npm:^16.0.0" + bin: + highlight: bin/highlight + checksum: 10c0/b5b4af3b968aa9df77eee449a400fbb659cf47c4b03a395370bd98d5554a00afaa5819b41a9a8a1ca0d37b0b896a94e57c65289b37359a25b700b1f56eb04852 + languageName: node + linkType: hard + +"cli-table3@npm:^0.6.3, cli-table3@npm:^0.6.5": + version: 0.6.5 + resolution: "cli-table3@npm:0.6.5" + dependencies: + "@colors/colors": "npm:1.5.0" + string-width: "npm:^4.2.0" + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 10c0/d7cc9ed12212ae68241cc7a3133c52b844113b17856e11f4f81308acc3febcea7cc9fd298e70933e294dd642866b29fd5d113c2c098948701d0c35f09455de78 + languageName: node + linkType: hard + +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10c0/35229b1bb48647e882104cac374c9a18e34bbf0bace0e2cf03000326b6ca3050d6b59545d91e17bfe3705f4a0e2988787aa5cde6331bf5cbbf0164732cef6492 + languageName: node + linkType: hard + +"cliui@npm:^7.0.2": + version: 7.0.4 + resolution: "cliui@npm:7.0.4" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clone-deep@npm:^4.0.1": + version: 4.0.1 + resolution: "clone-deep@npm:4.0.1" + dependencies: + is-plain-object: "npm:^2.0.4" + kind-of: "npm:^6.0.2" + shallow-clone: "npm:^3.0.0" + checksum: 10c0/637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"colorette@npm:^2.0.10": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 + languageName: node + linkType: hard + +"commander@npm:^10.0.1": + version: 10.0.1 + resolution: "commander@npm:10.0.1" + checksum: 10c0/53f33d8927758a911094adadda4b2cbac111a5b377d8706700587650fd8f45b0bbe336de4b5c3fe47fd61f420a3d9bd452b6e0e6e5600a7e74d7bf0174f6efe3 + languageName: node + linkType: hard + +"commander@npm:^2.20.0": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 + languageName: node + linkType: hard + +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 10c0/33a124960e471c25ee19280c9ce31ccc19574b566dc514fe4f4ca4c34fa8b0b57cf437671f5de380e11353ea9426213fca17687dd2ef03134fea2dbc53809fd6 + languageName: node + linkType: hard + +"compressible@npm:~2.0.18": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" + dependencies: + mime-db: "npm:>= 1.43.0 < 2" + checksum: 10c0/8a03712bc9f5b9fe530cc5a79e164e665550d5171a64575d7dcf3e0395d7b4afa2d79ab176c61b5b596e28228b350dd07c1a2a6ead12fd81d1b6cd632af2fef7 + languageName: node + linkType: hard + +"compression@npm:^1.8.1": + version: 1.8.1 + resolution: "compression@npm:1.8.1" + dependencies: + bytes: "npm:3.1.2" + compressible: "npm:~2.0.18" + debug: "npm:2.6.9" + negotiator: "npm:~0.6.4" + on-headers: "npm:~1.1.0" + safe-buffer: "npm:5.2.1" + vary: "npm:~1.1.2" + checksum: 10c0/85114b0b91c16594dc8c671cd9b05ef5e465066a60e5a4ed8b4551661303559a896ed17bb72c4234c04064e078f6ca86a34b8690349499a43f6fc4b844475da4 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"connect-history-api-fallback@npm:^2.0.0": + version: 2.0.0 + resolution: "connect-history-api-fallback@npm:2.0.0" + checksum: 10c0/90fa8b16ab76e9531646cc70b010b1dbd078153730c510d3142f6cf07479ae8a812c5a3c0e40a28528dd1681a62395d0cfdef67da9e914c4772ac85d69a3ed87 + languageName: node + linkType: hard + +"connect@npm:^3.7.0": + version: 3.7.0 + resolution: "connect@npm:3.7.0" + dependencies: + debug: "npm:2.6.9" + finalhandler: "npm:1.1.2" + parseurl: "npm:~1.3.3" + utils-merge: "npm:1.0.1" + checksum: 10c0/f120c6116bb16a0a7d2703c0b4a0cd7ed787dc5ec91978097bf62aa967289020a9f41a9cd3c3276a7b92aaa36f382d2cd35fed7138fd466a55c8e9fdbed11ca8 + languageName: node + linkType: hard + +"content-disposition@npm:~0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie-signature@npm:~1.0.6": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: 10c0/e7731ad2995ae2efeed6435ec1e22cdd21afef29d300c27281438b1eab2bae04ef0d1a203928c0afec2cee72aa36540b8747406ebe308ad23c8e8cc3c26c9c51 + languageName: node + linkType: hard + +"cookie@npm:~0.7.1, cookie@npm:~0.7.2": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 + languageName: node + linkType: hard + +"core-js-compat@npm:^3.48.0": + version: 3.49.0 + resolution: "core-js-compat@npm:3.49.0" + dependencies: + browserslist: "npm:^4.28.1" + checksum: 10c0/546e64b7ce45f724825bc13c1347f35c0459a6e71c0dcccff3ec21fbff463f5b0b97fc1220e6d90302153863489301793276fe2bf96f46001ff555ead4140308 + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"cors@npm:~2.8.5": + version: 2.8.6 + resolution: "cors@npm:2.8.6" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/ab2bc57b8af8ef8476682a59647f7c55c1a7d406b559ac06119aa1c5f70b96d35036864d197b24cf86e228e4547231088f1f94ca05061dbb14d89cc0bc9d4cab + languageName: node + linkType: hard + +"cross-env@npm:^10.1.0": + version: 10.1.0 + resolution: "cross-env@npm:10.1.0" + dependencies: + "@epic-web/invariant": "npm:^1.0.0" + cross-spawn: "npm:^7.0.6" + bin: + cross-env: dist/bin/cross-env.js + cross-env-shell: dist/bin/cross-env-shell.js + checksum: 10c0/834a862db456ba1fedf6c6da43436b123ae38f514fa286d6f0937c14fa83f13469f77f70f2812db041ae2d84f82bac627040b8686030aca27fbdf113dfa38b63 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"custom-event@npm:~1.0.0": + version: 1.0.1 + resolution: "custom-event@npm:1.0.1" + checksum: 10c0/86cd8497328b1e17dcda894c8df34a73b7a99f915123940d39b33c709482b2d3a2e689cd5e79e4775eb4167227689f57a2ae2f99a3f0bc9c54c0ac1b06853bd5 + languageName: node + linkType: hard + +"date-format@npm:^4.0.14": + version: 4.0.14 + resolution: "date-format@npm:4.0.14" + checksum: 10c0/1c67a4d77c677bb880328c81d81f5b9ed7fbf672bdaff74e5a0f7314b21188f3a829b06acf120c70cc1df876a7724e3e5c23d511e86d64656a3035a76ac3930b + languageName: node + linkType: hard + +"debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.3, debug@npm:~4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"decamelize-keys@npm:^1.1.0": + version: 1.1.1 + resolution: "decamelize-keys@npm:1.1.1" + dependencies: + decamelize: "npm:^1.1.0" + map-obj: "npm:^1.0.0" + checksum: 10c0/4ca385933127437658338c65fb9aead5f21b28d3dd3ccd7956eb29aab0953b5d3c047fbc207111672220c71ecf7a4d34f36c92851b7bbde6fca1a02c541bdd7d + languageName: node + linkType: hard + +"decamelize@npm:^1.1.0, decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"decamelize@npm:^4.0.0": + version: 4.0.0 + resolution: "decamelize@npm:4.0.0" + checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"default-browser-id@npm:^5.0.0": + version: 5.0.1 + resolution: "default-browser-id@npm:5.0.1" + checksum: 10c0/5288b3094c740ef3a86df9b999b04ff5ba4dee6b64e7b355c0fff5217752c8c86908d67f32f6cba9bb4f9b7b61a1b640c0a4f9e34c57e0ff3493559a625245ee + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.5.0 + resolution: "default-browser@npm:5.5.0" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/576593b617b17a7223014b4571bfe1c06a2581a4eb8b130985d90d253afa3f40999caec70eb0e5776e80d4af6a41cce91018cd3f86e57ad578bf59e46fb19abe + languageName: node + linkType: hard + +"default-require-extensions@npm:^3.0.0": + version: 3.0.1 + resolution: "default-require-extensions@npm:3.0.1" + dependencies: + strip-bom: "npm:^4.0.0" + checksum: 10c0/5ca376cb527d9474336ad76dd302d06367a7163379dda26558258de26f85861e696d0b7bb19ee3c6b8456bb7c95cdc0e4e4d45c2aa487e61b2d3b60d80c10648 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"depd@npm:2.0.0, depd@npm:~2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"depd@npm:~1.1.2": + version: 1.1.2 + resolution: "depd@npm:1.1.2" + checksum: 10c0/acb24aaf936ef9a227b6be6d495f0d2eb20108a9a6ad40585c5bda1a897031512fef6484e4fdbb80bd249fdaa82841fa1039f416ece03188e677ba11bcfda249 + languageName: node + linkType: hard + +"destroy@npm:1.2.0, destroy@npm:~1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10c0/f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 + languageName: node + linkType: hard + +"di@npm:^0.0.1": + version: 0.0.1 + resolution: "di@npm:0.0.1" + checksum: 10c0/fbca4cc93e8c493d50f82df3a9ecaa5d8b2935674aabddeb8f68db3ab03c942c201f9c3d920de094407392ee6f488eac16b96f500c0ea6b408634864b7b939d1 + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"diff@npm:^7.0.0": + version: 7.0.0 + resolution: "diff@npm:7.0.0" + checksum: 10c0/251fd15f85ffdf814cfc35a728d526b8d2ad3de338dcbd011ac6e57c461417090766b28995f8ff733135b5fbc3699c392db1d5e27711ac4e00244768cd1d577b + languageName: node + linkType: hard + +"diff@workspace:.": + version: 0.0.0-use.local + resolution: "diff@workspace:." + dependencies: + "@arethetypeswrong/cli": "npm:^0.18.2" + "@babel/core": "npm:^7.29.0" + "@babel/preset-env": "npm:^7.29.2" + "@babel/register": "npm:^7.28.6" + "@colors/colors": "npm:^1.6.0" + "@eslint/js": "npm:^10.0.1" + babel-loader: "npm:^10.1.1" + babel-plugin-istanbul: "npm:8.0.0" + chai: "npm:^6.2.2" + cross-env: "npm:^10.1.0" + eslint: "npm:^10.2.0" + globals: "npm:^17.5.0" + karma: "npm:^6.4.4" + karma-mocha: "npm:^2.0.1" + karma-mocha-reporter: "npm:^2.2.5" + karma-sourcemap-loader: "npm:^0.4.0" + karma-webpack: "npm:^5.0.1" + mocha: "npm:^11.7.5" + nyc: "npm:^18.0.0" + rollup: "npm:^4.60.1" + tsd: "npm:^0.33.0" + typescript: "npm:^6.0.2" + typescript-eslint: "npm:^8.58.1" + uglify-js: "npm:^3.19.3" + webpack: "npm:^5.106.1" + webpack-dev-server: "npm:^5.2.3" + languageName: unknown + linkType: soft + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"dns-packet@npm:^5.2.2": + version: 5.6.1 + resolution: "dns-packet@npm:5.6.1" + dependencies: + "@leichtgewicht/ip-codec": "npm:^2.0.1" + checksum: 10c0/8948d3d03063fb68e04a1e386875f8c3bcc398fc375f535f2b438fad8f41bf1afa6f5e70893ba44f4ae884c089247e0a31045722fa6ff0f01d228da103f1811d + languageName: node + linkType: hard + +"dom-serialize@npm:^2.2.1": + version: 2.2.1 + resolution: "dom-serialize@npm:2.2.1" + dependencies: + custom-event: "npm:~1.0.0" + ent: "npm:~2.2.0" + extend: "npm:^3.0.0" + void-elements: "npm:^2.0.0" + checksum: 10c0/ceb6e62b73c658986ca4c9b8b2fae358d8ae914eb06712d137da595a327c3bbca45a762f412a6d181f892ce5e3cffb855c2db2b64c53ad0534b2a0ad8e65b05e + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.328": + version: 1.5.335 + resolution: "electron-to-chromium@npm:1.5.335" + checksum: 10c0/ce634af91fba9ac647b5660bacdf08c6cec49743281733ae1c754487fafdb6299968e386434f1b749d09d5fd5a977c66d5cd2335a9dd2837ab10dc5b4c2545ac + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"emojilib@npm:^2.4.0": + version: 2.4.0 + resolution: "emojilib@npm:2.4.0" + checksum: 10c0/6e66ba8921175842193f974e18af448bb6adb0cf7aeea75e08b9d4ea8e9baba0e4a5347b46ed901491dcaba277485891c33a8d70b0560ca5cc9672a94c21ab8f + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"engine.io-parser@npm:~5.2.1": + version: 5.2.3 + resolution: "engine.io-parser@npm:5.2.3" + checksum: 10c0/ed4900d8dbef470ab3839ccf3bfa79ee518ea8277c7f1f2759e8c22a48f64e687ea5e474291394d0c94f84054749fd93f3ef0acb51fa2f5f234cc9d9d8e7c536 + languageName: node + linkType: hard + +"engine.io@npm:~6.6.0": + version: 6.6.6 + resolution: "engine.io@npm:6.6.6" + dependencies: + "@types/cors": "npm:^2.8.12" + "@types/node": "npm:>=10.0.0" + "@types/ws": "npm:^8.5.12" + accepts: "npm:~1.3.4" + base64id: "npm:2.0.0" + cookie: "npm:~0.7.2" + cors: "npm:~2.8.5" + debug: "npm:~4.4.1" + engine.io-parser: "npm:~5.2.1" + ws: "npm:~8.18.3" + checksum: 10c0/014b8864e786846e7b896b5a854d7b1117a9f29af9c48975937247f0fc8e8ae5616b2f2bd9398ca66b94e2c7cec4f5d13cbec58054ce513c17aa4f4921c0ad24 + languageName: node + linkType: hard + +"enhanced-resolve@npm:^5.20.0": + version: 5.20.1 + resolution: "enhanced-resolve@npm:5.20.1" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.3.0" + checksum: 10c0/c6503ee1b2d725843e047e774445ecb12b779aa52db25d11ebe18d4b3adc148d3d993d2038b3d0c38ad836c9c4b3930fbc55df42f72b44785e2f94e5530eda69 + languageName: node + linkType: hard + +"ent@npm:~2.2.0": + version: 2.2.2 + resolution: "ent@npm:2.2.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + punycode: "npm:^1.4.1" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/83673cc952bb1ca01473460eb4f1289448d887ef2bfcdd142bfe83cd20a794a4393b6bca543922bf1eb913d1ae0ab69ca2d2f1f6a5e9f3de6e68464b3a3b9096 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-module-lexer@npm:^2.0.0": + version: 2.0.0 + resolution: "es-module-lexer@npm:2.0.0" + checksum: 10c0/ae78dbbd43035a4b972c46cfb6877e374ea290adfc62bc2f5a083fea242c0b2baaab25c5886af86be55f092f4a326741cb94334cd3c478c383fdc8a9ec5ff817 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es6-error@npm:^4.0.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10c0/357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-formatter-pretty@npm:^4.1.0": + version: 4.1.0 + resolution: "eslint-formatter-pretty@npm:4.1.0" + dependencies: + "@types/eslint": "npm:^7.2.13" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.1.0" + eslint-rule-docs: "npm:^1.1.5" + log-symbols: "npm:^4.0.0" + plur: "npm:^4.0.0" + string-width: "npm:^4.2.0" + supports-hyperlinks: "npm:^2.0.0" + checksum: 10c0/7cc55b873d3e9a5049cf0db65cef873abfc299639e7527bed52dea61f0742661b68e48018cf05de4c9cd8fb9362badc20f22c50fd5f36d745a346fa17bac8b60 + languageName: node + linkType: hard + +"eslint-rule-docs@npm:^1.1.5": + version: 1.1.235 + resolution: "eslint-rule-docs@npm:1.1.235" + checksum: 10c0/76a735c1e13a511ddff1017d5913b2526643827c8fdc86a23467f680b8dcbdfd07806cb092c82dd8d0e99789f23c8a38b9d2b838cd1cd62cc1932612ed606b8e + languageName: node + linkType: hard + +"eslint-scope@npm:5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^9.1.2": + version: 9.1.2 + resolution: "eslint-scope@npm:9.1.2" + dependencies: + "@types/esrecurse": "npm:^4.3.1" + "@types/estree": "npm:^1.0.8" + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/9fb8bca5a73e5741efb6cec84467027b6cb6f4203ff9b43a938e272c5cd30800bde46a5c20dfd1609f840225f0b62b7673be391b20acadf8658ca9fa4729b3dd + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 + languageName: node + linkType: hard + +"eslint@npm:^10.2.0": + version: 10.2.0 + resolution: "eslint@npm:10.2.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.2" + "@eslint/config-array": "npm:^0.23.4" + "@eslint/config-helpers": "npm:^0.5.4" + "@eslint/core": "npm:^1.2.0" + "@eslint/plugin-kit": "npm:^0.7.0" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.14.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^9.1.2" + eslint-visitor-keys: "npm:^5.0.1" + espree: "npm:^11.2.0" + esquery: "npm:^1.7.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + minimatch: "npm:^10.2.4" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/c275115f8937c243125986bf8f7d5c09bdc083f4a9fba8a77ad15a15989f05732f5037fe990cc1bc22dd887cf16060f57b8949dc5f1055d5020689adff49e219 + languageName: node + linkType: hard + +"espree@npm:^11.2.0": + version: 11.2.0 + resolution: "espree@npm:11.2.0" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.7.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.0": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + +"events@npm:^3.2.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"express@npm:^4.22.1": + version: 4.22.1 + resolution: "express@npm:4.22.1" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:~1.20.3" + content-disposition: "npm:~0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:~0.7.1" + cookie-signature: "npm:~1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:~1.3.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:~2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:~0.1.12" + proxy-addr: "npm:~2.0.7" + qs: "npm:~6.14.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:~0.19.0" + serve-static: "npm:~1.16.2" + setprototypeof: "npm:1.2.0" + statuses: "npm:~2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10c0/ea57f512ab1e05e26b53a14fd432f65a10ec735ece342b37d0b63a7bcb8d337ffbb830ecb8ca15bcdfe423fbff88cea09786277baff200e8cde3ab40faa665cd + languageName: node + linkType: hard + +"extend@npm:^3.0.0": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.20.1 + resolution: "fastq@npm:1.20.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/e5dd725884decb1f11e5c822221d76136f239d0236f176fab80b7b8f9e7619ae57e6b4e5b73defc21e6b9ef99437ee7b545cff8e6c2c337819633712fa9d352e + languageName: node + linkType: hard + +"faye-websocket@npm:^0.11.3": + version: 0.11.4 + resolution: "faye-websocket@npm:0.11.4" + dependencies: + websocket-driver: "npm:>=0.5.1" + checksum: 10c0/c6052a0bb322778ce9f89af92890f6f4ce00d5ec92418a35e5f4c6864a4fe736fec0bcebd47eac7c0f0e979b01530746b1c85c83cb04bae789271abf19737420 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fflate@npm:^0.8.2": + version: 0.8.2 + resolution: "fflate@npm:0.8.2" + checksum: 10c0/03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293 + languageName: node + linkType: hard + +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"finalhandler@npm:1.1.2": + version: 1.1.2 + resolution: "finalhandler@npm:1.1.2" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + on-finished: "npm:~2.3.0" + parseurl: "npm:~1.3.3" + statuses: "npm:~1.5.0" + unpipe: "npm:~1.0.0" + checksum: 10c0/6a96e1f5caab085628c11d9fdceb82ba608d5e426c6913d4d918409baa271037a47f28fbba73279e8ad614f0b8fa71ea791d265e408d760793829edd8c2f4584 + languageName: node + linkType: hard + +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + on-finished: "npm:~2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:~2.0.2" + unpipe: "npm:~1.0.0" + checksum: 10c0/435a4fd65e4e4e4c71bb5474980090b73c353a123dd415583f67836bdd6516e528cf07298e219a82b94631dee7830eae5eece38d3c178073cf7df4e8c182f413 + languageName: node + linkType: hard + +"find-cache-dir@npm:^2.0.0": + version: 2.1.0 + resolution: "find-cache-dir@npm:2.1.0" + dependencies: + commondir: "npm:^1.0.1" + make-dir: "npm:^2.0.0" + pkg-dir: "npm:^3.0.0" + checksum: 10c0/556117fd0af14eb88fb69250f4bba9e905e7c355c6136dff0e161b9cbd1f5285f761b778565a278da73a130f42eccc723d7ad4c002ae547ed1d698d39779dabb + languageName: node + linkType: hard + +"find-cache-dir@npm:^3.2.0": + version: 3.3.2 + resolution: "find-cache-dir@npm:3.3.2" + dependencies: + commondir: "npm:^1.0.1" + make-dir: "npm:^3.0.2" + pkg-dir: "npm:^4.1.0" + checksum: 10c0/92747cda42bff47a0266b06014610981cfbb71f55d60f2c8216bc3108c83d9745507fb0b14ecf6ab71112bed29cd6fb1a137ee7436179ea36e11287e3159e587 + languageName: node + linkType: hard + +"find-up@npm:^3.0.0": + version: 3.0.0 + resolution: "find-up@npm:3.0.0" + dependencies: + locate-path: "npm:^3.0.0" + checksum: 10c0/2c2e7d0a26db858e2f624f39038c74739e38306dee42b45f404f770db357947be9d0d587f1cac72d20c114deb38aa57316e879eb0a78b17b46da7dab0a3bd6e3 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + languageName: node + linkType: hard + +"flat@npm:^5.0.2": + version: 5.0.2 + resolution: "flat@npm:5.0.2" + bin: + flat: cli.js + checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe + languageName: node + linkType: hard + +"flatted@npm:^3.2.7, flatted@npm:^3.2.9": + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed + languageName: node + linkType: hard + +"follow-redirects@npm:^1.0.0": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 + languageName: node + linkType: hard + +"foreground-child@npm:^2.0.0": + version: 2.0.0 + resolution: "foreground-child@npm:2.0.0" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/6719982783a448162f9a01500757fb2053bc5dcd4d67c7cd30739b38ccc01b39f84e408c30989d1d8774519c021c0498e2450ab127690fb09d7f2568fd94ffcc + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fresh@npm:~0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fromentries@npm:^1.2.0": + version: 1.3.2 + resolution: "fromentries@npm:1.3.2" + checksum: 10c0/63938819a86e39f490b0caa1f6b38b8ad04f41ccd2a1c144eb48a21f76e4dbc074bc62e97abb053c7c1f541ecc70cf0b8aaa98eed3fe02206db9b6f9bb9a6a47 + languageName: node + linkType: hard + +"fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob-to-regex.js@npm:^1.0.0, glob-to-regex.js@npm:^1.0.1": + version: 1.2.0 + resolution: "glob-to-regex.js@npm:1.2.0" + peerDependencies: + tslib: 2 + checksum: 10c0/011c81ae2a4d7ac5fd617038209fd9639d54c76211cc88fe8dd85d1a0850bc683a63cf5b1eae370141fca7dd2c834dfb9684dfdd8bf7472f2c1e4ef6ab6e34f9 + languageName: node + linkType: hard + +"glob-to-regexp@npm:^0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 + languageName: node + linkType: hard + +"glob@npm:^10.4.1, glob@npm:^10.4.5": + version: 10.5.0 + resolution: "glob@npm:10.5.0" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828 + languageName: node + linkType: hard + +"glob@npm:^13.0.0, glob@npm:^13.0.3, glob@npm:^13.0.6": + version: 13.0.6 + resolution: "glob@npm:13.0.6" + dependencies: + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.7": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"globals@npm:^17.5.0": + version: 17.5.0 + resolution: "globals@npm:17.5.0" + checksum: 10c0/92828102ed2f5637907725f0478038bed02fc83e9fc89300bb753639ba7c022b6c02576fc772117302b431b204591db1f2fa909d26f3f0a9852cc856a941df3f + languageName: node + linkType: hard + +"globby@npm:^11.0.1": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"handle-thing@npm:^2.0.0": + version: 2.0.1 + resolution: "handle-thing@npm:2.0.1" + checksum: 10c0/7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 + languageName: node + linkType: hard + +"hard-rejection@npm:^2.1.0": + version: 2.1.0 + resolution: "hard-rejection@npm:2.1.0" + checksum: 10c0/febc3343a1ad575aedcc112580835b44a89a89e01f400b4eda6e8110869edfdab0b00cd1bd4c3bfec9475a57e79e0b355aecd5be46454b6a62b9a359af60e564 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasha@npm:^5.0.0": + version: 5.2.2 + resolution: "hasha@npm:5.2.2" + dependencies: + is-stream: "npm:^2.0.0" + type-fest: "npm:^0.8.0" + checksum: 10c0/9d10d4e665a37beea6e18ba3a0c0399a05b26e505c5ff2fe9115b64fedb3ca95f68c89cf15b08ee4d09fd3064b5e1bfc8e8247353c7aa6b7388471d0f86dca74 + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 + languageName: node + linkType: hard + +"highlight.js@npm:^10.7.1": + version: 10.7.3 + resolution: "highlight.js@npm:10.7.3" + checksum: 10c0/073837eaf816922427a9005c56c42ad8786473dc042332dfe7901aa065e92bc3d94ebf704975257526482066abb2c8677cc0326559bb8621e046c21c5991c434 + languageName: node + linkType: hard + +"hosted-git-info@npm:^2.1.4": + version: 2.8.9 + resolution: "hosted-git-info@npm:2.8.9" + checksum: 10c0/317cbc6b1bbbe23c2a40ae23f3dafe9fa349ce42a89a36f930e3f9c0530c179a3882d2ef1e4141a4c3674d6faaea862138ec55b43ad6f75e387fda2483a13c70 + languageName: node + linkType: hard + +"hosted-git-info@npm:^4.0.1": + version: 4.1.0 + resolution: "hosted-git-info@npm:4.1.0" + dependencies: + lru-cache: "npm:^6.0.0" + checksum: 10c0/150fbcb001600336d17fdbae803264abed013548eea7946c2264c49ebe2ebd8c4441ba71dd23dd8e18c65de79d637f98b22d4760ba5fb2e0b15d62543d0fff07 + languageName: node + linkType: hard + +"hpack.js@npm:^2.1.6": + version: 2.1.6 + resolution: "hpack.js@npm:2.1.6" + dependencies: + inherits: "npm:^2.0.1" + obuf: "npm:^1.0.0" + readable-stream: "npm:^2.0.1" + wbuf: "npm:^1.1.0" + checksum: 10c0/55b9e824430bab82a19d079cb6e33042d7d0640325678c9917fcc020c61d8a08ca671b6c942c7f0aae9bb6e4b67ffb50734a72f9e21d66407c3138c1983b70f0 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-deceiver@npm:^1.2.7": + version: 1.2.7 + resolution: "http-deceiver@npm:1.2.7" + checksum: 10c0/8bb9b716f5fc55f54a451da7f49b9c695c3e45498a789634daec26b61e4add7c85613a4a9e53726c39d09de7a163891ecd6eb5809adb64500a840fd86fe81d03 + languageName: node + linkType: hard + +"http-errors@npm:~1.8.0": + version: 1.8.1 + resolution: "http-errors@npm:1.8.1" + dependencies: + depd: "npm:~1.1.2" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:>= 1.5.0 < 2" + toidentifier: "npm:1.0.1" + checksum: 10c0/f01aeecd76260a6fe7f08e192fcbe9b2f39ed20fc717b852669a69930167053b01790998275c6297d44f435cf0e30edd50c05223d1bec9bc484e6cf35b2d6f43 + languageName: node + linkType: hard + +"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 + languageName: node + linkType: hard + +"http-parser-js@npm:>=0.5.1": + version: 0.5.10 + resolution: "http-parser-js@npm:0.5.10" + checksum: 10c0/8bbcf1832a8d70b2bd515270112116333add88738a2cc05bfb94ba6bde3be4b33efee5611584113818d2bcf654fdc335b652503be5a6b4c0b95e46f214187d93 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"http-proxy-middleware@npm:^2.0.9": + version: 2.0.9 + resolution: "http-proxy-middleware@npm:2.0.9" + dependencies: + "@types/http-proxy": "npm:^1.17.8" + http-proxy: "npm:^1.18.1" + is-glob: "npm:^4.0.1" + is-plain-obj: "npm:^3.0.0" + micromatch: "npm:^4.0.2" + peerDependencies: + "@types/express": ^4.17.13 + peerDependenciesMeta: + "@types/express": + optional: true + checksum: 10c0/8e9032af625f7c9f2f0d318f6cdb14eb725cc16ffe7b4ccccea25cf591fa819bb7c3bb579e0b543e0ae9c73059b505a6d728290c757bff27bae526a6ed11c05e + languageName: node + linkType: hard + +"http-proxy@npm:^1.18.1": + version: 1.18.1 + resolution: "http-proxy@npm:1.18.1" + dependencies: + eventemitter3: "npm:^4.0.0" + follow-redirects: "npm:^1.0.0" + requires-port: "npm:^1.0.0" + checksum: 10c0/148dfa700a03fb421e383aaaf88ac1d94521dfc34072f6c59770528c65250983c2e4ec996f2f03aa9f3fe46cd1270a593126068319311e3e8d9e610a37533e94 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"hyperdyperid@npm:^1.2.0": + version: 1.2.0 + resolution: "hyperdyperid@npm:1.2.0" + checksum: 10c0/885ba3177c7181d315a856ee9c0005ff8eb5dcb1ce9e9d61be70987895d934d84686c37c981cceeb53216d4c9c15c1cc25f1804e84cc6a74a16993c5d7fd0893 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.2": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 + languageName: node + linkType: hard + +"iconv-lite@npm:~0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:^7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.3, inherits@npm:~2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"ipaddr.js@npm:^2.1.0": + version: 2.3.0 + resolution: "ipaddr.js@npm:2.3.0" + checksum: 10c0/084bab99e2f6875d7a62adc3325e1c64b038a12c9521e35fb967b5e263a8b3afb1b8884dd77c276092331f5d63298b767491e10997ef147c62da01b143780bbd + languageName: node + linkType: hard + +"irregular-plurals@npm:^3.2.0": + version: 3.5.0 + resolution: "irregular-plurals@npm:3.5.0" + checksum: 10c0/7c033bbe7325e5a6e0a26949cc6863b6ce273403d4cd5b93bd99b33fecb6605b0884097c4259c23ed0c52c2133bf7d1cdcdd7a0630e8c325161fe269b3447918 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.1, is-core-module@npm:^2.5.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + +"is-network-error@npm:^1.0.0": + version: 1.3.1 + resolution: "is-network-error@npm:1.3.1" + checksum: 10c0/389b4a4cc6838bc5764c1d4ab8af11ec68c63825d53f7ce9f5a31aa4d2c9e5d33896c052f4c44100911e8db47bcf854c4aae6c03d6b1d84700f7c6aa72d16693 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-plain-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "is-plain-obj@npm:1.1.0" + checksum: 10c0/daaee1805add26f781b413fdf192fc91d52409583be30ace35c82607d440da63cc4cac0ac55136716688d6c0a2c6ef3edb2254fecbd1fe06056d6bd15975ee8c + languageName: node + linkType: hard + +"is-plain-obj@npm:^2.1.0": + version: 2.1.0 + resolution: "is-plain-obj@npm:2.1.0" + checksum: 10c0/e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 + languageName: node + linkType: hard + +"is-plain-obj@npm:^3.0.0": + version: 3.0.0 + resolution: "is-plain-obj@npm:3.0.0" + checksum: 10c0/8e6483bfb051d42ec9c704c0ede051a821c6b6f9a6c7a3e3b55aa855e00981b0580c8f3b1f5e2e62649b39179b1abfee35d6f8086d999bfaa32c1908d29b07bc + languageName: node + linkType: hard + +"is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-typedarray@npm:^1.0.0": + version: 1.0.0 + resolution: "is-typedarray@npm:1.0.0" + checksum: 10c0/4c096275ba041a17a13cca33ac21c16bc4fd2d7d7eb94525e7cd2c2f2c1a3ab956e37622290642501ff4310601e413b675cf399ad6db49855527d2163b3eeeec + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 + languageName: node + linkType: hard + +"is-windows@npm:^1.0.2": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10c0/b32f418ab3385604a66f1b7a3ce39d25e8881dee0bd30816dc8344ef6ff9df473a732bcc1ec4e84fe99b2f229ae474f7133e8e93f9241686cfcf7eebe53ba7a5 + languageName: node + linkType: hard + +"is-wsl@npm:^3.1.0": + version: 3.1.1 + resolution: "is-wsl@npm:3.1.1" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/7e5023522bfb8f27de4de960b0d82c4a8146c0bddb186529a3616d78b5bbbfc19ef0c5fc60d0b3a3cc0bf95a415fbdedc18454310ea3049587c879b07ace5107 + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isbinaryfile@npm:^4.0.8": + version: 4.0.10 + resolution: "isbinaryfile@npm:4.0.10" + checksum: 10c0/0703d8cfeb69ed79e6d173120f327450011a066755150a6bbf97ffecec1069a5f2092777868315b21359098c84b54984871cad1abce877ad9141fb2caf3dcabf + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-hook@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-hook@npm:3.0.0" + dependencies: + append-transform: "npm:^2.0.0" + checksum: 10c0/0029bdbc4ae82c2a5a0b48a2f4ba074de72601a5d27505493c9be83d4c7952039ad787d2f6d1321710b75a05059c4335a0eb7c8857ca82e7e6d19f8d88d03b46 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.2": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-processinfo@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-processinfo@npm:3.0.0" + dependencies: + archy: "npm:^1.0.0" + cross-spawn: "npm:^7.0.3" + istanbul-lib-coverage: "npm:^3.2.0" + p-map: "npm:^3.0.0" + rimraf: "npm:^6.1.3" + uuid: "npm:^8.3.2" + checksum: 10c0/50ebb4ff325b5fca2cdcff465dc7eacb0b0e93bb3d4bd2fb922777f5a54485146e24d615cb9f4dd32568763f95d492129aee36c43f9b410ee8f1ba94f30b4235 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.0.2": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jest-diff@npm:^29.0.3": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"jest-worker@npm:^27.4.5": + version: 27.5.1 + resolution: "jest-worker@npm:27.5.1" + dependencies: + "@types/node": "npm:*" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/8c4737ffd03887b3c6768e4cc3ca0269c0336c1e4b1b120943958ddb035ed2a0fc6acab6dc99631720a3720af4e708ff84fb45382ad1e83c27946adf3623969b + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/3261f25912f5dd76605e5993d0a126c2b6c346311885d3c483706cd722efe34f697ea0331f654ce27c00a42b426e524518ec89d65ed02ea47df8ad26dcc8ce69 + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2, jsesc@npm:~3.1.0": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + +"karma-mocha-reporter@npm:^2.2.5": + version: 2.2.5 + resolution: "karma-mocha-reporter@npm:2.2.5" + dependencies: + chalk: "npm:^2.1.0" + log-symbols: "npm:^2.1.0" + strip-ansi: "npm:^4.0.0" + peerDependencies: + karma: ">=0.13" + checksum: 10c0/5a26ea58fb683a6d6a7f8b6f6e51f8678432adfa6425356fb125c9e8dba4489061385b819dfd19e943ffe8f3e0f86a529386b7793d3065cd59d4756517023aba + languageName: node + linkType: hard + +"karma-mocha@npm:^2.0.1": + version: 2.0.1 + resolution: "karma-mocha@npm:2.0.1" + dependencies: + minimist: "npm:^1.2.3" + checksum: 10c0/99ef62d863f6bf8cb11df0f4a9d47615369a0ce8a937d9a0cd7fb83fdbb0ef7420c7ea396de514be48500fac1563a00ab964b7d1adc4ee3f5a875ebf07eb012d + languageName: node + linkType: hard + +"karma-sourcemap-loader@npm:^0.4.0": + version: 0.4.0 + resolution: "karma-sourcemap-loader@npm:0.4.0" + dependencies: + graceful-fs: "npm:^4.2.10" + checksum: 10c0/8f77516330bc78d7c4d22469bdfd96b82acf7a285e2c19b8f30ae9d9bb21c7d817b0c32b9e5ea6ae002b16365e124afea121910c089c641320ce5595cafaadac + languageName: node + linkType: hard + +"karma-webpack@npm:^5.0.1": + version: 5.0.1 + resolution: "karma-webpack@npm:5.0.1" + dependencies: + glob: "npm:^7.1.3" + minimatch: "npm:^9.0.3" + webpack-merge: "npm:^4.1.5" + peerDependencies: + webpack: ^5.0.0 + checksum: 10c0/ef7208a6b2746819693c654d6da4a1c7794edb72690006181e1c2897ff8415644e6745ca46bdf170e30c2b82d4b9f16284af5e71fcae026b32de0a08adb14c7d + languageName: node + linkType: hard + +"karma@npm:^6.4.4": + version: 6.4.4 + resolution: "karma@npm:6.4.4" + dependencies: + "@colors/colors": "npm:1.5.0" + body-parser: "npm:^1.19.0" + braces: "npm:^3.0.2" + chokidar: "npm:^3.5.1" + connect: "npm:^3.7.0" + di: "npm:^0.0.1" + dom-serialize: "npm:^2.2.1" + glob: "npm:^7.1.7" + graceful-fs: "npm:^4.2.6" + http-proxy: "npm:^1.18.1" + isbinaryfile: "npm:^4.0.8" + lodash: "npm:^4.17.21" + log4js: "npm:^6.4.1" + mime: "npm:^2.5.2" + minimatch: "npm:^3.0.4" + mkdirp: "npm:^0.5.5" + qjobs: "npm:^1.2.0" + range-parser: "npm:^1.2.1" + rimraf: "npm:^3.0.2" + socket.io: "npm:^4.7.2" + source-map: "npm:^0.6.1" + tmp: "npm:^0.2.1" + ua-parser-js: "npm:^0.7.30" + yargs: "npm:^16.1.1" + bin: + karma: bin/karma + checksum: 10c0/1658c4b7396c0edf6f048289182e075b561902e02992e1a3eb72f56f67090ff0c7ad7c91ab099e88a790c60f9500c5a6f974d75f1769e3ea2dfccda52876ec0b + languageName: node + linkType: hard + +"keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kind-of@npm:^6.0.2, kind-of@npm:^6.0.3": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 + languageName: node + linkType: hard + +"launch-editor@npm:^2.6.1": + version: 2.13.2 + resolution: "launch-editor@npm:2.13.2" + dependencies: + picocolors: "npm:^1.1.1" + shell-quote: "npm:^1.8.3" + checksum: 10c0/5057fc8d3d0b0a92055b09b99192ffb5860b3e8a3f8ba56ef9b7f252fd78650d6b4182b725f4a1dcb8b04e350fa053874d819bb84362f2cfd6c3e84f556066dd + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"loader-runner@npm:^4.3.1": + version: 4.3.1 + resolution: "loader-runner@npm:4.3.1" + checksum: 10c0/a523b6329f114e0a98317158e30a7dfce044b731521be5399464010472a93a15ece44757d1eaed1d8845019869c5390218bc1c7c3110f4eeaef5157394486eac + languageName: node + linkType: hard + +"locate-path@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-path@npm:3.0.0" + dependencies: + p-locate: "npm:^3.0.0" + path-exists: "npm:^3.0.0" + checksum: 10c0/3db394b7829a7fe2f4fbdd25d3c4689b85f003c318c5da4052c7e56eed697da8f1bce5294f685c69ff76e32cba7a33629d94396976f6d05fb7f4c755c5e2ae8b + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: 10c0/762998a63e095412b6099b8290903e0a8ddcb353ac6e2e0f2d7e7d03abd4275fe3c689d88960eb90b0dde4f177554d51a690f22a343932ecbc50a5d111849987 + languageName: node + linkType: hard + +"lodash.flattendeep@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.flattendeep@npm:4.4.0" + checksum: 10c0/83cb80754b921fb4ed2c222b91a82b2524f3bdc60c3ae91e00688bd4bf1bcc28b8a2cc250e11fdc1b6da3a2de09e57008e13f15a209cafdd4f9163d047f97544 + languageName: node + linkType: hard + +"lodash@npm:^4.17.15, lodash@npm:^4.17.21": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 + languageName: node + linkType: hard + +"log-symbols@npm:^2.1.0": + version: 2.2.0 + resolution: "log-symbols@npm:2.2.0" + dependencies: + chalk: "npm:^2.0.1" + checksum: 10c0/574eb4205f54f0605021aa67ebb372c30ca64e8ddd439efeb8507af83c776dce789e83614e80059014d9e48dcc94c4b60cef2e85f0dc944eea27c799cec62353 + languageName: node + linkType: hard + +"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 + languageName: node + linkType: hard + +"log4js@npm:^6.4.1": + version: 6.9.1 + resolution: "log4js@npm:6.9.1" + dependencies: + date-format: "npm:^4.0.14" + debug: "npm:^4.3.4" + flatted: "npm:^3.2.7" + rfdc: "npm:^1.3.0" + streamroller: "npm:^3.1.5" + checksum: 10c0/05846e48f72d662800c8189bd178c42b4aa2f0c574cfc90a1942cf90b76f621c44019e26796c8fd88da1b6f0fe8272cba607cbaad6ae6ede50a7a096b58197ea + languageName: node + linkType: hard + +"lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.0.1, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.3.5 + resolution: "lru-cache@npm:11.3.5" + checksum: 10c0/5b54ef7b88afb4bd25b7a778f1b2b1cde32d9770913e530da34ab203cf0442413bcaa6e372800cbab9562557a4480e4d8bf32e3a368bb5a91b12218eca085c66 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: "npm:^4.0.1" + semver: "npm:^5.6.0" + checksum: 10c0/ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8 + languageName: node + linkType: hard + +"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: "npm:^6.0.0" + checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.5 + resolution: "make-fetch-happen@npm:15.0.5" + dependencies: + "@gar/promise-retry": "npm:^1.0.0" + "@npmcli/agent": "npm:^4.0.0" + "@npmcli/redact": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + ssri: "npm:^13.0.0" + checksum: 10c0/527580eb5e5476e6ad07a4e3bd017d13e935f4be815674b442081ae5a721c13d3af5715006619e6be79a85723067e047f83a0c9e699f41d8cec43609a8de4f7b + languageName: node + linkType: hard + +"map-obj@npm:^1.0.0": + version: 1.0.1 + resolution: "map-obj@npm:1.0.1" + checksum: 10c0/ccca88395e7d38671ed9f5652ecf471ecd546924be2fb900836b9da35e068a96687d96a5f93dcdfa94d9a27d649d2f10a84595590f89a347fb4dda47629dcc52 + languageName: node + linkType: hard + +"map-obj@npm:^4.0.0": + version: 4.3.0 + resolution: "map-obj@npm:4.3.0" + checksum: 10c0/1c19e1c88513c8abdab25c316367154c6a0a6a0f77e3e8c391bb7c0e093aefed293f539d026dc013d86219e5e4c25f23b0003ea588be2101ccd757bacc12d43b + languageName: node + linkType: hard + +"marked-terminal@npm:^7.1.0": + version: 7.3.0 + resolution: "marked-terminal@npm:7.3.0" + dependencies: + ansi-escapes: "npm:^7.0.0" + ansi-regex: "npm:^6.1.0" + chalk: "npm:^5.4.1" + cli-highlight: "npm:^2.1.11" + cli-table3: "npm:^0.6.5" + node-emoji: "npm:^2.2.0" + supports-hyperlinks: "npm:^3.1.0" + peerDependencies: + marked: ">=1 <16" + checksum: 10c0/59d23c2ed9488c40856d828f431ae1d5d57426e791bbce8f05ec5a7d3a1f848cdb3b8d8880d76ae45570415f8b48ae459f50bbbd88ece5a31306f1e3de57f021 + languageName: node + linkType: hard + +"marked@npm:^9.1.2": + version: 9.1.6 + resolution: "marked@npm:9.1.6" + bin: + marked: bin/marked.js + checksum: 10c0/010bbd33c0f38300259c5d3bf0063deb36bab098d37ac0a3be5a35a65674a4c693427fc6704f486a89f638e9b36c36b8e220a93d47163f4e70e45a1fa8ca7b60 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"memfs@npm:^4.43.1": + version: 4.57.1 + resolution: "memfs@npm:4.57.1" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.57.1" + "@jsonjoy.com/fs-fsa": "npm:4.57.1" + "@jsonjoy.com/fs-node": "npm:4.57.1" + "@jsonjoy.com/fs-node-builtins": "npm:4.57.1" + "@jsonjoy.com/fs-node-to-fsa": "npm:4.57.1" + "@jsonjoy.com/fs-node-utils": "npm:4.57.1" + "@jsonjoy.com/fs-print": "npm:4.57.1" + "@jsonjoy.com/fs-snapshot": "npm:4.57.1" + "@jsonjoy.com/json-pack": "npm:^1.11.0" + "@jsonjoy.com/util": "npm:^1.9.0" + glob-to-regex.js: "npm:^1.0.1" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.0.3" + tslib: "npm:^2.0.0" + peerDependencies: + tslib: 2 + checksum: 10c0/5cbfcf07945a1eef8dacb31d2516f4adbc7989ef7f2ab57255a2ec69905010108b37b72fe132f8710a41d3a2eef2e5f1e7a63b54de6d272e34b579bbe8620ec9 + languageName: node + linkType: hard + +"meow@npm:^9.0.0": + version: 9.0.0 + resolution: "meow@npm:9.0.0" + dependencies: + "@types/minimist": "npm:^1.2.0" + camelcase-keys: "npm:^6.2.2" + decamelize: "npm:^1.2.0" + decamelize-keys: "npm:^1.1.0" + hard-rejection: "npm:^2.1.0" + minimist-options: "npm:4.1.0" + normalize-package-data: "npm:^3.0.0" + read-pkg-up: "npm:^7.0.1" + redent: "npm:^3.0.0" + trim-newlines: "npm:^3.0.0" + type-fest: "npm:^0.18.0" + yargs-parser: "npm:^20.2.3" + checksum: 10c0/998955ecff999dc3f3867ef3b51999218212497f27d75b9cbe10bdb73aac4ee308d484f7801fd1b3cfa4172819065f65f076ca018c1412fab19d0ea486648722 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.27, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34, mime-types@npm:~2.1.35": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime-types@npm:^3.0.1": + version: 3.0.2 + resolution: "mime-types@npm:3.0.2" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10c0/35a0dd1035d14d185664f346efcdb72e93ef7a9b6e9ae808bd1f6358227010267fab52657b37562c80fc888ff76becb2b2938deb5e730818b7983bf8bd359767 + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"mime@npm:^2.5.2": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10c0/a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + +"min-indent@npm:^1.0.0": + version: 1.0.1 + resolution: "min-indent@npm:1.0.1" + checksum: 10c0/7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c + languageName: node + linkType: hard + +"minimalistic-assert@npm:^1.0.0": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + +"minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": + version: 10.2.5 + resolution: "minimatch@npm:10.2.5" + dependencies: + brace-expansion: "npm:^5.0.5" + checksum: 10c0/6bb058bd6324104b9ec2f763476a35386d05079c1f5fe4fbf1f324a25237cd4534d6813ecd71f48208f4e635c1221899bef94c3c89f7df55698fe373aaae20fd + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.3, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": + version: 9.0.9 + resolution: "minimatch@npm:9.0.9" + dependencies: + brace-expansion: "npm:^2.0.2" + checksum: 10c0/0b6a58530dbb00361745aa6c8cffaba4c90f551afe7c734830bd95fd88ebf469dd7355a027824ea1d09e37181cfeb0a797fb17df60c15ac174303ac110eb7e86 + languageName: node + linkType: hard + +"minimist-options@npm:4.1.0": + version: 4.1.0 + resolution: "minimist-options@npm:4.1.0" + dependencies: + arrify: "npm:^1.0.1" + is-plain-obj: "npm:^1.1.0" + kind-of: "npm:^6.0.3" + checksum: 10c0/7871f9cdd15d1e7374e5b013e2ceda3d327a06a8c7b38ae16d9ef941e07d985e952c589e57213f7aa90a8744c60aed9524c0d85e501f5478382d9181f2763f54 + languageName: node + linkType: hard + +"minimist@npm:^1.2.3, minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.2 + resolution: "minipass-fetch@npm:5.0.2" + dependencies: + iconv-lite: "npm:^0.7.2" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^2.0.0" + minizlib: "npm:^3.0.1" + dependenciesMeta: + iconv-lite: + optional: true + checksum: 10c0/ce4ab9f21cfabaead2097d95dd33f485af8072fbc6b19611bce694965393453a1639d641c2bcf1c48f2ea7d41ea7fab8278373f1d0bee4e63b0a5b2cdd0ef649 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.7 + resolution: "minipass-flush@npm:1.0.7" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/960915c02aa0991662c37c404517dd93708d17f96533b2ca8c1e776d158715d8107c5ced425ffc61674c167d93607f07f48a83c139ce1057f8781e5dfb4b90c2 + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^2.0.0": + version: 2.0.0 + resolution: "minipass-sized@npm:2.0.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/f9201696a6f6d68610d04c9c83e3d2e5cb9c026aae1c8cbf7e17f386105cb79c1bb088dbc21bf0b1eb4f3fb5df384fd1e7aa3bf1f33868c416ae8c8a92679db8 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mkdirp@npm:^0.5.5": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01 + languageName: node + linkType: hard + +"mocha@npm:^11.7.5": + version: 11.7.5 + resolution: "mocha@npm:11.7.5" + dependencies: + browser-stdout: "npm:^1.3.1" + chokidar: "npm:^4.0.1" + debug: "npm:^4.3.5" + diff: "npm:^7.0.0" + escape-string-regexp: "npm:^4.0.0" + find-up: "npm:^5.0.0" + glob: "npm:^10.4.5" + he: "npm:^1.2.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + log-symbols: "npm:^4.1.0" + minimatch: "npm:^9.0.5" + ms: "npm:^2.1.3" + picocolors: "npm:^1.1.1" + serialize-javascript: "npm:^6.0.2" + strip-json-comments: "npm:^3.1.1" + supports-color: "npm:^8.1.1" + workerpool: "npm:^9.2.0" + yargs: "npm:^17.7.2" + yargs-parser: "npm:^21.1.1" + yargs-unparser: "npm:^2.0.0" + bin: + _mocha: bin/_mocha + mocha: bin/mocha.js + checksum: 10c0/e6150cba85345aaabbc5b2e7341b1e6706b878f5a9782960cad57fd4cc458730a76d08c5f1a3e05d3ebb49cad93b455764bb00727bd148dcaf0c6dd4fa665b3d + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.3, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"multicast-dns@npm:^7.2.5": + version: 7.2.5 + resolution: "multicast-dns@npm:7.2.5" + dependencies: + dns-packet: "npm:^5.2.2" + thunky: "npm:^1.0.2" + bin: + multicast-dns: cli.js + checksum: 10c0/5120171d4bdb1577764c5afa96e413353bff530d1b37081cb29cccc747f989eb1baf40574fe8e27060fc1aef72b59c042f72b9b208413de33bcf411343c69057 + languageName: node + linkType: hard + +"mz@npm:^2.4.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 10c0/103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"negotiator@npm:~0.6.4": + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"node-emoji@npm:^2.2.0": + version: 2.2.0 + resolution: "node-emoji@npm:2.2.0" + dependencies: + "@sindresorhus/is": "npm:^4.6.0" + char-regex: "npm:^1.0.2" + emojilib: "npm:^2.4.0" + skin-tone: "npm:^2.0.0" + checksum: 10c0/9525defbd90a82a2131758c2470203fa2a2faa8edd177147a8654a26307fe03594e52847ecbe2746d06cfc5c50acd12bd500f035350a7609e8217c9894c19aad + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.2.0 + resolution: "node-gyp@npm:12.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 + languageName: node + linkType: hard + +"node-preload@npm:^0.2.1": + version: 0.2.1 + resolution: "node-preload@npm:0.2.1" + dependencies: + process-on-spawn: "npm:^1.0.0" + checksum: 10c0/7ae3def896626701e2a27b0c8119e0234089db4317b8c16bb8c44bee9abb82c0e38d57e6317d480970f5a2510e44185af81d3ea85be1a78311701f66f912e9e4 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.36": + version: 2.0.37 + resolution: "node-releases@npm:2.0.37" + checksum: 10c0/306df89190b3225d0cb001260de52f0befd225a782ec85311ce97b0aa3b2e22f5e4e4c00395c6dc9bc9ef440c64723f6205fe1e27d32b8dd1d140891fbadf901 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"normalize-package-data@npm:^2.5.0": + version: 2.5.0 + resolution: "normalize-package-data@npm:2.5.0" + dependencies: + hosted-git-info: "npm:^2.1.4" + resolve: "npm:^1.10.0" + semver: "npm:2 || 3 || 4 || 5" + validate-npm-package-license: "npm:^3.0.1" + checksum: 10c0/357cb1646deb42f8eb4c7d42c4edf0eec312f3628c2ef98501963cc4bbe7277021b2b1d977f982b2edce78f5a1014613ce9cf38085c3df2d76730481357ca504 + languageName: node + linkType: hard + +"normalize-package-data@npm:^3.0.0": + version: 3.0.3 + resolution: "normalize-package-data@npm:3.0.3" + dependencies: + hosted-git-info: "npm:^4.0.1" + is-core-module: "npm:^2.5.0" + semver: "npm:^7.3.4" + validate-npm-package-license: "npm:^3.0.1" + checksum: 10c0/e5d0f739ba2c465d41f77c9d950e291ea4af78f8816ddb91c5da62257c40b76d8c83278b0d08ffbcd0f187636ebddad20e181e924873916d03e6e5ea2ef026be + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"nyc@npm:^18.0.0": + version: 18.0.0 + resolution: "nyc@npm:18.0.0" + dependencies: + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + caching-transform: "npm:^4.0.0" + convert-source-map: "npm:^1.7.0" + decamelize: "npm:^1.2.0" + find-cache-dir: "npm:^3.2.0" + find-up: "npm:^4.1.0" + foreground-child: "npm:^3.3.0" + get-package-type: "npm:^0.1.0" + glob: "npm:^13.0.6" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-hook: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.2" + istanbul-lib-processinfo: "npm:^3.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.0.2" + make-dir: "npm:^3.0.0" + node-preload: "npm:^0.2.1" + p-map: "npm:^3.0.0" + process-on-spawn: "npm:^1.0.0" + resolve-from: "npm:^5.0.0" + rimraf: "npm:^6.1.3" + signal-exit: "npm:^3.0.2" + spawn-wrap: "npm:^3.0.0" + test-exclude: "npm:^8.0.0" + yargs: "npm:^15.0.2" + bin: + nyc: bin/nyc.js + checksum: 10c0/1358e390623ec251250ca6786f0640f18b6b4db0b0e945362c91d1919fa3fcc5d065a47fa688fe04d1d8b76f33f6dfd79034e412ea161ba507a6ae7012f4550c + languageName: node + linkType: hard + +"object-assign@npm:^4, object-assign@npm:^4.0.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"obuf@npm:^1.0.0, obuf@npm:^1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 10c0/520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 + languageName: node + linkType: hard + +"on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"on-finished@npm:~2.3.0": + version: 2.3.0 + resolution: "on-finished@npm:2.3.0" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/c904f9e518b11941eb60279a3cbfaf1289bd0001f600a950255b1dede9fe3df8cd74f38483550b3bb9485165166acb5db500c3b4c4337aec2815c88c96fcc2ea + languageName: node + linkType: hard + +"on-headers@npm:~1.1.0": + version: 1.1.0 + resolution: "on-headers@npm:1.1.0" + checksum: 10c0/2c3b6b0d68ec9adbd561dc2d61c9b14da8ac03d8a2f0fd9e97bdf0600c887d5d97f664ff3be6876cf40cda6e3c587d73a4745e10b426ac50c7664fc5a0dfc0a1 + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"open@npm:^10.0.3": + version: 10.2.0 + resolution: "open@npm:10.2.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + wsl-utils: "npm:^0.1.0" + checksum: 10c0/5a36d0c1fd2f74ce553beb427ca8b8494b623fc22c6132d0c1688f246a375e24584ea0b44c67133d9ab774fa69be8e12fbe1ff12504b1142bd960fb09671948f + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^3.0.0": + version: 3.0.0 + resolution: "p-locate@npm:3.0.0" + dependencies: + p-limit: "npm:^2.0.0" + checksum: 10c0/7b7f06f718f19e989ce6280ed4396fb3c34dabdee0df948376483032f9d5ec22fdf7077ec942143a75827bb85b11da72016497fc10dac1106c837ed593969ee8 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^3.0.0": + version: 3.0.0 + resolution: "p-map@npm:3.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/297930737e52412ad9f5787c52774ad6496fad9a8be5f047e75fd0a3dc61930d8f7a9b2bbe1c4d1404e54324228a4f69721da2538208dadaa4ef4c81773c9f20 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"p-retry@npm:^6.2.0": + version: 6.2.1 + resolution: "p-retry@npm:6.2.1" + dependencies: + "@types/retry": "npm:0.12.2" + is-network-error: "npm:^1.0.0" + retry: "npm:^0.13.1" + checksum: 10c0/10d014900107da2c7071ad60fffe4951675f09930b7a91681643ea224ae05649c05001d9e78436d902fe8b116d520dd1f60e72e091de097e2640979d56f3fb60 + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-hash@npm:^4.0.0": + version: 4.0.0 + resolution: "package-hash@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.15" + hasha: "npm:^5.0.0" + lodash.flattendeep: "npm:^4.4.0" + release-zalgo: "npm:^1.0.0" + checksum: 10c0/2108b685fd5b2a32323aeed5caf2afef8c5fcf680527b09c7e2eaa05cf04b09a7c586860319097fc589ad028a3d94b2da68e8ab1935249aa95e8162ffd622729 + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0, package-json-from-dist@npm:^1.0.1": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse5-htmlparser2-tree-adapter@npm:^6.0.0": + version: 6.0.1 + resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1" + dependencies: + parse5: "npm:^6.0.1" + checksum: 10c0/dfa5960e2aaf125707e19a4b1bc333de49232eba5a6ffffb95d313a7d6087c3b7a274b58bee8d3bd41bdf150638815d1d601a42bbf2a0345208c3c35b1279556 + languageName: node + linkType: hard + +"parse5@npm:^5.1.1": + version: 5.1.1 + resolution: "parse5@npm:5.1.1" + checksum: 10c0/b0f87a77a7fea5f242e3d76917c983bbea47703b9371801d51536b78942db6441cbda174bf84eb30e47315ddc6f8a0b57d68e562c790154430270acd76c1fa03 + languageName: node + linkType: hard + +"parse5@npm:^6.0.1": + version: 6.0.1 + resolution: "parse5@npm:6.0.1" + checksum: 10c0/595821edc094ecbcfb9ddcb46a3e1fe3a718540f8320eff08b8cf6742a5114cce2d46d45f95c26191c11b184dcaf4e2960abcd9c5ed9eb9393ac9a37efcfdecb + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 10c0/17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 + languageName: node + linkType: hard + +"path-to-regexp@npm:~0.1.12": + version: 0.1.13 + resolution: "path-to-regexp@npm:0.1.13" + checksum: 10c0/1cae3921739c154a8926e136185a10c916f79a249b9072a5001b266d96e193860ca03867e8e8cc808b786862d750f427ed93686bc259355442c3407a62deab1a + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + +"pirates@npm:^4.0.6": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + +"pkg-dir@npm:^3.0.0": + version: 3.0.0 + resolution: "pkg-dir@npm:3.0.0" + dependencies: + find-up: "npm:^3.0.0" + checksum: 10c0/902a3d0c1f8ac43b1795fa1ba6ffeb37dfd53c91469e969790f6ed5e29ff2bdc50b63ba6115dc056d2efb4a040aa2446d512b3804bdafdf302f734fb3ec21847 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.1.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"pkijs@npm:^3.3.3": + version: 3.4.0 + resolution: "pkijs@npm:3.4.0" + dependencies: + "@noble/hashes": "npm:1.4.0" + asn1js: "npm:^3.0.6" + bytestreamjs: "npm:^2.0.1" + pvtsutils: "npm:^1.3.6" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.8.1" + checksum: 10c0/33cfab9283702782ae228bd2d4a51b1e9b2e0d6e2141207f29ee95716101ac4fe6e6821882da5f5eca28c74be3964b181b09e95cbbb757b2bd9dca918a5765fd + languageName: node + linkType: hard + +"plur@npm:^4.0.0": + version: 4.0.0 + resolution: "plur@npm:4.0.0" + dependencies: + irregular-plurals: "npm:^3.2.0" + checksum: 10c0/4d3010843dac60b980c9b83d4cdecc2c6bb4bf0a1d7620ba7229b5badcbc3c3767fddfdb61746f6253c3bd33ada3523375983cbe0b3f94868f4a475b9b6bd7d7 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"process-on-spawn@npm:^1.0.0": + version: 1.1.0 + resolution: "process-on-spawn@npm:1.1.0" + dependencies: + fromentries: "npm:^1.2.0" + checksum: 10c0/d7379a78e2ecc482d1f79be480505b68449b46c8736bcd94ae839c979f39517425b23d44d4170a8dc0ed5fe5f795e00fdff701c305d06d92dd899e132e3ee8b0 + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"punycode@npm:^1.4.1": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 10c0/354b743320518aef36f77013be6e15da4db24c2b4f62c5f1eb0529a6ed02fbaf1cb52925785f6ab85a962f2b590d9cd5ad730b70da72b5f180e2556b8bd3ca08 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pvtsutils@npm:^1.3.6": + version: 1.3.6 + resolution: "pvtsutils@npm:1.3.6" + dependencies: + tslib: "npm:^2.8.1" + checksum: 10c0/b1b42646370505ccae536dcffa662303b2c553995211330c8e39dec9ab8c197585d7751c2c5b9ab2f186feda0219d9bb23c34ee1e565573be96450f79d89a13c + languageName: node + linkType: hard + +"pvutils@npm:^1.1.3": + version: 1.1.5 + resolution: "pvutils@npm:1.1.5" + checksum: 10c0/e968b07b78a58fec9377fe7aa6342c8cfa21c8fb4afc4e51e1489bd42bec6dc71b8a52541d0aede0aea17adec7ca3f89f29f56efdc31d0083cc02e9bb5721bcf + languageName: node + linkType: hard + +"qjobs@npm:^1.2.0": + version: 1.2.0 + resolution: "qjobs@npm:1.2.0" + checksum: 10c0/772207772b856a3b1ec673b11a6cda074f1b82821644f2d042504b438ea3ea1fe918555547491e717e8694ec105379fe5139fc5ddd7937b21f7712bb648ed01d + languageName: node + linkType: hard + +"qs@npm:~6.14.0": + version: 6.14.2 + resolution: "qs@npm:6.14.2" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/646110124476fc9acf3c80994c8c3a0600cbad06a4ede1c9e93341006e8426d64e85e048baf8f0c4995f0f1bf0f37d1f3acc5ec1455850b81978792969a60ef6 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quick-lru@npm:^4.0.1": + version: 4.0.1 + resolution: "quick-lru@npm:4.0.1" + checksum: 10c0/f9b1596fa7595a35c2f9d913ac312fede13d37dc8a747a51557ab36e11ce113bbe88ef4c0154968845559a7709cb6a7e7cbe75f7972182451cd45e7f057a334d + languageName: node + linkType: hard + +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: "npm:^5.1.0" + checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 + languageName: node + linkType: hard + +"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" + dependencies: + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 10c0/449844344fc90547fb994383a494b83300e4f22199f146a79f68d78a199a8f2a923ea9fd29c3be979bfd50291a3884733619ffc15ba02a32e703b612f8d3f74a + languageName: node + linkType: hard + +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"read-pkg-up@npm:^7.0.0, read-pkg-up@npm:^7.0.1": + version: 7.0.1 + resolution: "read-pkg-up@npm:7.0.1" + dependencies: + find-up: "npm:^4.1.0" + read-pkg: "npm:^5.2.0" + type-fest: "npm:^0.8.1" + checksum: 10c0/82b3ac9fd7c6ca1bdc1d7253eb1091a98ff3d195ee0a45386582ce3e69f90266163c34121e6a0a02f1630073a6c0585f7880b3865efcae9c452fa667f02ca385 + languageName: node + linkType: hard + +"read-pkg@npm:^5.2.0": + version: 5.2.0 + resolution: "read-pkg@npm:5.2.0" + dependencies: + "@types/normalize-package-data": "npm:^2.4.0" + normalize-package-data: "npm:^2.5.0" + parse-json: "npm:^5.0.0" + type-fest: "npm:^0.6.0" + checksum: 10c0/b51a17d4b51418e777029e3a7694c9bd6c578a5ab99db544764a0b0f2c7c0f58f8a6bc101f86a6fceb8ba6d237d67c89acf6170f6b98695d0420ddc86cf109fb + languageName: node + linkType: hard + +"readable-stream@npm:^2.0.1": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"readable-stream@npm:^3.0.6": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"redent@npm:^3.0.0": + version: 3.0.0 + resolution: "redent@npm:3.0.0" + dependencies: + indent-string: "npm:^4.0.0" + strip-indent: "npm:^3.0.0" + checksum: 10c0/d64a6b5c0b50eb3ddce3ab770f866658a2b9998c678f797919ceb1b586bab9259b311407280bd80b804e2a7c7539b19238ae6a2a20c843f1a7fcff21d48c2eae + languageName: node + linkType: hard + +"reflect-metadata@npm:^0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10c0/1cd93a15ea291e420204955544637c264c216e7aac527470e393d54b4bb075f10a17e60d8168ec96600c7e0b9fcc0cb0bb6e91c3fbf5b0d8c9056f04e6ac1ec2 + languageName: node + linkType: hard + +"regenerate-unicode-properties@npm:^10.2.2": + version: 10.2.2 + resolution: "regenerate-unicode-properties@npm:10.2.2" + dependencies: + regenerate: "npm:^1.4.2" + checksum: 10c0/66a1d6a1dbacdfc49afd88f20b2319a4c33cee56d245163e4d8f5f283e0f45d1085a78f7f7406dd19ea3a5dd7a7799cd020cd817c97464a7507f9d10fbdce87c + languageName: node + linkType: hard + +"regenerate@npm:^1.4.2": + version: 1.4.2 + resolution: "regenerate@npm:1.4.2" + checksum: 10c0/f73c9eba5d398c818edc71d1c6979eaa05af7a808682749dd079f8df2a6d91a9b913db216c2c9b03e0a8ba2bba8701244a93f45211afbff691c32c7b275db1b8 + languageName: node + linkType: hard + +"regexpu-core@npm:^6.3.1": + version: 6.4.0 + resolution: "regexpu-core@npm:6.4.0" + dependencies: + regenerate: "npm:^1.4.2" + regenerate-unicode-properties: "npm:^10.2.2" + regjsgen: "npm:^0.8.0" + regjsparser: "npm:^0.13.0" + unicode-match-property-ecmascript: "npm:^2.0.0" + unicode-match-property-value-ecmascript: "npm:^2.2.1" + checksum: 10c0/1eed9783c023dd06fb1f3ce4b6e3fdf0bc1e30cb036f30aeb2019b351e5e0b74355b40462282ea5db092c79a79331c374c7e9897e44a5ca4509e9f0b570263de + languageName: node + linkType: hard + +"regjsgen@npm:^0.8.0": + version: 0.8.0 + resolution: "regjsgen@npm:0.8.0" + checksum: 10c0/44f526c4fdbf0b29286101a282189e4dbb303f4013cf3fea058668d96d113b9180d3d03d1e13f6d4cbde38b7728bf951aecd9dc199938c080093a9a6f0d7a6bd + languageName: node + linkType: hard + +"regjsparser@npm:^0.13.0": + version: 0.13.1 + resolution: "regjsparser@npm:0.13.1" + dependencies: + jsesc: "npm:~3.1.0" + bin: + regjsparser: bin/parser + checksum: 10c0/1276c983f00de49e37ef76b181df4390699b46e3666fbe6b8b5512bd75919112574cf023f28ac720d427be158af60dba6a77cce9a8aaff14967263636e667356 + languageName: node + linkType: hard + +"release-zalgo@npm:^1.0.0": + version: 1.0.0 + resolution: "release-zalgo@npm:1.0.0" + dependencies: + es6-error: "npm:^4.0.1" + checksum: 10c0/9e161feb073f9e3aa714bb077d67592c34ee578f5b9cff8e2d492423fe2002d5b1e6d11ffcd5c564b9a0ee9435f25569567b658a82b9af931e7ac1313925628a + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10c0/db91467d9ead311b4111cbd73a4e67fa7820daed2989a32f7023785a2659008c6d119752d9c4ac011ae07e537eb86523adff99804c5fdb39cd3a017f9b401bb6 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve@npm:^1.10.0, resolve@npm:^1.22.11": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e + languageName: node + linkType: hard + +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"rfdc@npm:^1.3.0": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 + languageName: node + linkType: hard + +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"rimraf@npm:^6.1.3": + version: 6.1.3 + resolution: "rimraf@npm:6.1.3" + dependencies: + glob: "npm:^13.0.3" + package-json-from-dist: "npm:^1.0.1" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/4a56537850102e20ba5d5eb49f366b4b7b2435389734b4b8480cf0e0eb0f6f5d0c44120a171aeb0d8f9ab40312a10d2262f3f50acbad803e32caef61b6cf86fc + languageName: node + linkType: hard + +"rollup@npm:^4.60.1": + version: 4.60.1 + resolution: "rollup@npm:4.60.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.60.1" + "@rollup/rollup-android-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-x64": "npm:4.60.1" + "@rollup/rollup-freebsd-arm64": "npm:4.60.1" + "@rollup/rollup-freebsd-x64": "npm:4.60.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.60.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.60.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-musl": "npm:4.60.1" + "@rollup/rollup-openbsd-x64": "npm:4.60.1" + "@rollup/rollup-openharmony-arm64": "npm:4.60.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.60.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.60.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.60.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.60.1" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/48d3f2216b5533639b007e6756e2275c7f594e45adee21ce03674aa2e004406c661f8b86c7a0b471c9e889c6a9efbb29240ca0b7673c50e391406c490c309833 + languageName: node + linkType: hard + +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: 10c0/ab826c57c20f244b2ee807704b1ef4ba7f566aa766481ae5922aac785e2570809e297c69afcccc3593095b538a8a77d26f2b2e9a1d9dffee24e0e039502d1a03 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": + version: 4.3.3 + resolution: "schema-utils@npm:4.3.3" + dependencies: + "@types/json-schema": "npm:^7.0.9" + ajv: "npm:^8.9.0" + ajv-formats: "npm:^2.1.1" + ajv-keywords: "npm:^5.1.0" + checksum: 10c0/1c8d2c480a026d7c02ab2ecbe5919133a096d6a721a3f201fa50663e4f30f6d6ba020dfddd93cb828b66b922e76b342e103edd19a62c95c8f60e9079cc403202 + languageName: node + linkType: hard + +"select-hose@npm:^2.0.0": + version: 2.0.0 + resolution: "select-hose@npm:2.0.0" + checksum: 10c0/01cc52edd29feddaf379efb4328aededa633f0ac43c64b11a8abd075ff34f05b0d280882c4fbcbdf1a0658202c9cd2ea8d5985174dcf9a2dac7e3a4996fa9b67 + languageName: node + linkType: hard + +"selfsigned@npm:^5.5.0": + version: 5.5.0 + resolution: "selfsigned@npm:5.5.0" + dependencies: + "@peculiar/x509": "npm:^1.14.2" + pkijs: "npm:^3.3.3" + checksum: 10c0/a31e9d928e22cd6f4e14759a099feba79d9d789c852c7cf65ff8e2f62d7f6313fe477639590e7ed06115b4516a4bebbe0dec5d072a2d01cc372a9cfd58eb893b + languageName: node + linkType: hard + +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.3": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"send@npm:~0.19.0, send@npm:~0.19.1": + version: 0.19.2 + resolution: "send@npm:0.19.2" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.1" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:~2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:~2.0.2" + checksum: 10c0/20c2389fe0fdf3fc499938cac598bc32272287e993c4960717381a10de8550028feadfb9076f959a3a3ebdea42e1f690e116f0d16468fa56b9fd41866d3dc267 + languageName: node + linkType: hard + +"serialize-javascript@npm:^6.0.2": + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" + dependencies: + randombytes: "npm:^2.1.0" + checksum: 10c0/2dd09ef4b65a1289ba24a788b1423a035581bef60817bea1f01eda8e3bda623f86357665fe7ac1b50f6d4f583f97db9615b3f07b2a2e8cbcb75033965f771dd2 + languageName: node + linkType: hard + +"serve-index@npm:^1.9.1": + version: 1.9.2 + resolution: "serve-index@npm:1.9.2" + dependencies: + accepts: "npm:~1.3.8" + batch: "npm:0.6.1" + debug: "npm:2.6.9" + escape-html: "npm:~1.0.3" + http-errors: "npm:~1.8.0" + mime-types: "npm:~2.1.35" + parseurl: "npm:~1.3.3" + checksum: 10c0/b4e48da75c9262cfcf6a4707748a33a127f6c3cd3a095782c22312c4915545b7695071fedc8f5717bae165e6e63053cd963847013b1f1e984213f07186f78a74 + languageName: node + linkType: hard + +"serve-static@npm:~1.16.2": + version: 1.16.3 + resolution: "serve-static@npm:1.16.3" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:~0.19.1" + checksum: 10c0/36320397a073c71bedf58af48a4a100fe6d93f07459af4d6f08b9a7217c04ce2a4939e0effd842dc7bece93ffcd59eb52f58c4fff2a8e002dc29ae6b219cd42b + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"shallow-clone@npm:^3.0.0": + version: 3.0.1 + resolution: "shallow-clone@npm:3.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10c0/7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shell-quote@npm:^1.8.3": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + checksum: 10c0/d346c787fd2f9f1c2fdea14f00e8250118db0e7596d85a6cb9faa75f105d31a73a8f7a341c93d7df2a2429098c3d37a77bd3be9e88c37094b8c01807bc77c7a2 + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"skin-tone@npm:^2.0.0": + version: 2.0.0 + resolution: "skin-tone@npm:2.0.0" + dependencies: + unicode-emoji-modifier-base: "npm:^1.0.0" + checksum: 10c0/82d4c2527864f9cbd6cb7f3c4abb31e2224752234d5013b881d3e34e9ab543545b05206df5a17d14b515459fcb265ce409f9cfe443903176b0360cd20e4e4ba5 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socket.io-adapter@npm:~2.5.2": + version: 2.5.6 + resolution: "socket.io-adapter@npm:2.5.6" + dependencies: + debug: "npm:~4.4.1" + ws: "npm:~8.18.3" + checksum: 10c0/2af9827c3e8e2a445d7d1523f7ad4fcc37009da44f72042e8a9af27e4caf29fe0a34de6771a6e9971a0ff8d527631fe25b80230ff6c42c045e2913f0ac308059 + languageName: node + linkType: hard + +"socket.io-parser@npm:~4.2.4": + version: 4.2.6 + resolution: "socket.io-parser@npm:4.2.6" + dependencies: + "@socket.io/component-emitter": "npm:~3.1.0" + debug: "npm:~4.4.1" + checksum: 10c0/ba0a0b541b0a8e9d02b45c04c4c93a02331be5ea3478073c65bb9ff87032f12469c9adb309728eb90c0a352618d645ab88999c167a11c783cac861d7fd35c9d1 + languageName: node + linkType: hard + +"socket.io@npm:^4.7.2": + version: 4.8.3 + resolution: "socket.io@npm:4.8.3" + dependencies: + accepts: "npm:~1.3.4" + base64id: "npm:~2.0.0" + cors: "npm:~2.8.5" + debug: "npm:~4.4.1" + engine.io: "npm:~6.6.0" + socket.io-adapter: "npm:~2.5.2" + socket.io-parser: "npm:~4.2.4" + checksum: 10c0/1f7c4118cdbcb346e63db9d8fd657c3dc5caf148404762ed98ac14c4e7b74984a65fe51657bfe1696adcf7c168d1a3aad4a26b52864ce8491556f38218598f0b + languageName: node + linkType: hard + +"sockjs@npm:^0.3.24": + version: 0.3.24 + resolution: "sockjs@npm:0.3.24" + dependencies: + faye-websocket: "npm:^0.11.3" + uuid: "npm:^8.3.2" + websocket-driver: "npm:^0.7.4" + checksum: 10c0/aa102c7d921bf430215754511c81ea7248f2dcdf268fbdb18e4d8183493a86b8793b164c636c52f474a886f747447c962741df2373888823271efdb9d2594f33 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"spawn-wrap@npm:^3.0.0": + version: 3.0.0 + resolution: "spawn-wrap@npm:3.0.0" + dependencies: + cross-spawn: "npm:^7.0.6" + foreground-child: "npm:^2.0.0" + is-windows: "npm:^1.0.2" + make-dir: "npm:^3.0.0" + rimraf: "npm:^6.1.3" + signal-exit: "npm:^3.0.2" + which: "npm:^2.0.1" + checksum: 10c0/46a6cfd37a5b59b198a19be1b8a4205a3a79eeeb98d36cd7d459aa5cbae0e91c5f58a67a023a0bd7f309929569faa370f22e3abd746782b535bdedd1f4d351b4 + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.2.0 + resolution: "spdx-correct@npm:3.2.0" + dependencies: + spdx-expression-parse: "npm:^3.0.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/49208f008618b9119208b0dadc9208a3a55053f4fd6a0ae8116861bd22696fc50f4142a35ebfdb389e05ccf2de8ad142573fefc9e26f670522d899f7b2fe7386 + languageName: node + linkType: hard + +"spdx-exceptions@npm:^2.1.0": + version: 2.5.0 + resolution: "spdx-exceptions@npm:2.5.0" + checksum: 10c0/37217b7762ee0ea0d8b7d0c29fd48b7e4dfb94096b109d6255b589c561f57da93bf4e328c0290046115961b9209a8051ad9f525e48d433082fc79f496a4ea940 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: "npm:^2.1.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/6f8a41c87759fa184a58713b86c6a8b028250f158159f1d03ed9d1b6ee4d9eefdc74181c8ddc581a341aa971c3e7b79e30b59c23b05d2436d5de1c30bdef7171 + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.23 + resolution: "spdx-license-ids@npm:3.0.23" + checksum: 10c0/8495620f6f2a237749cce922ea2d593a66f7885c301b1a0f5542183e7041182f27f616a8f13345cefdea0c9b3e0899328e0aa8cec100cf4f3fac4bb3bd975515 + languageName: node + linkType: hard + +"spdy-transport@npm:^3.0.0": + version: 3.0.0 + resolution: "spdy-transport@npm:3.0.0" + dependencies: + debug: "npm:^4.1.0" + detect-node: "npm:^2.0.4" + hpack.js: "npm:^2.1.6" + obuf: "npm:^1.1.2" + readable-stream: "npm:^3.0.6" + wbuf: "npm:^1.7.3" + checksum: 10c0/eaf7440fa90724fffc813c386d4a8a7427d967d6e46d7c51d8f8a533d1a6911b9823ea9218703debbae755337e85f110185d7a00ae22ec5c847077b908ce71bb + languageName: node + linkType: hard + +"spdy@npm:^4.0.2": + version: 4.0.2 + resolution: "spdy@npm:4.0.2" + dependencies: + debug: "npm:^4.1.0" + handle-thing: "npm:^2.0.0" + http-deceiver: "npm:^1.2.7" + select-hose: "npm:^2.0.0" + spdy-transport: "npm:^3.0.0" + checksum: 10c0/983509c0be9d06fd00bb9dff713c5b5d35d3ffd720db869acdd5ad7aa6fc0e02c2318b58f75328957d8ff772acdf1f7d19382b6047df342044ff3e2d6805ccdf + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.1 + resolution: "ssri@npm:13.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/cf6408a18676c57ff2ed06b8a20dc64bb3e748e5c7e095332e6aecaa2b8422b1e94a739a8453bf65156a8a47afe23757ba4ab52d3ea3b62322dc40875763e17a + languageName: node + linkType: hard + +"statuses@npm:>= 1.5.0 < 2, statuses@npm:~1.5.0": + version: 1.5.0 + resolution: "statuses@npm:1.5.0" + checksum: 10c0/e433900956357b3efd79b1c547da4d291799ac836960c016d10a98f6a810b1b5c0dcc13b5a7aa609a58239b5190e1ea176ad9221c2157d2fd1c747393e6b2940 + languageName: node + linkType: hard + +"statuses@npm:~2.0.1, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + +"streamroller@npm:^3.1.5": + version: 3.1.5 + resolution: "streamroller@npm:3.1.5" + dependencies: + date-format: "npm:^4.0.14" + debug: "npm:^4.3.4" + fs-extra: "npm:^8.1.0" + checksum: 10c0/0bdeec34ad37487d959ba908f17067c938f544db88b5bb1669497a67a6b676413229ce5a6145c2812d06959ebeb8842e751076647d4b323ca06be612963b9099 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-ansi@npm:4.0.0" + dependencies: + ansi-regex: "npm:^3.0.0" + checksum: 10c0/d75d9681e0637ea316ddbd7d4d3be010b1895a17e885155e0ed6a39755ae0fd7ef46e14b22162e66a62db122d3a98ab7917794e255532ab461bb0a04feb03e7d + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-indent@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-indent@npm:3.0.0" + dependencies: + min-indent: "npm:^1.0.0" + checksum: 10c0/ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679 + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-hyperlinks@npm:^2.0.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 10c0/4057f0d86afb056cd799602f72d575b8fdd79001c5894bcb691176f14e870a687e7981e50bc1484980e8b688c6d5bcd4931e1609816abb5a7dc1486b7babf6a1 + languageName: node + linkType: hard + +"supports-hyperlinks@npm:^3.1.0": + version: 3.2.0 + resolution: "supports-hyperlinks@npm:3.2.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 10c0/bca527f38d4c45bc95d6a24225944675746c515ddb91e2456d00ae0b5c537658e9dd8155b996b191941b0c19036195a098251304b9082bbe00cd1781f3cd838e + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tapable@npm:^2.3.0": + version: 2.3.2 + resolution: "tapable@npm:2.3.2" + checksum: 10c0/45ec8bd8963907f35bba875f9b3e9a5afa5ba11a9a4e4a2d7b2313d983cb2741386fd7dd3e54b13055b2be942971aac369d197e02263ec9216c59c0a8069ed7f + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.13 + resolution: "tar@npm:7.5.13" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b + languageName: node + linkType: hard + +"terser-webpack-plugin@npm:^5.3.17": + version: 5.4.0 + resolution: "terser-webpack-plugin@npm:5.4.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + jest-worker: "npm:^27.4.5" + schema-utils: "npm:^4.3.0" + terser: "npm:^5.31.1" + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 10c0/1feed4b9575af795dae6af0c8f0d76d6e1fb7b357b8628d90e834c23a651b918a58cdc48d0ae6c1f0581f74bc8169b33c3b8d049f2d2190bac4e310964e59fde + languageName: node + linkType: hard + +"terser@npm:^5.31.1": + version: 5.46.1 + resolution: "terser@npm:5.46.1" + dependencies: + "@jridgewell/source-map": "npm:^0.3.3" + acorn: "npm:^8.15.0" + commander: "npm:^2.20.0" + source-map-support: "npm:~0.5.20" + bin: + terser: bin/terser + checksum: 10c0/45ba6566af976c518ff4e140250348d606761af822e23ea28d95b30fcf60fb69d3fabd93c6fafa4085d9fe31b67207e58b8480a64370b6cca07066c434101602 + languageName: node + linkType: hard + +"test-exclude@npm:^7.0.1": + version: 7.0.2 + resolution: "test-exclude@npm:7.0.2" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^10.2.2" + checksum: 10c0/b79b855af9168c6a362146015ccf40f5e3a25e307304ba9bea930818507f6319d230380d5d7b5baa659c981ccc11f1bd21b6f012f85606353dec07e02dee67c9 + languageName: node + linkType: hard + +"test-exclude@npm:^8.0.0": + version: 8.0.0 + resolution: "test-exclude@npm:8.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^13.0.6" + minimatch: "npm:^10.2.2" + checksum: 10c0/f2b613cb5ddc05d1357892f5da965a6f7af42b19a6b2fc30c9b93cb74adf5059a3a9f29818adb75c96c1747b3934caac90a9058f73ce0640ea101de828a11600 + languageName: node + linkType: hard + +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 10c0/9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: 10c0/f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + +"thingies@npm:^2.5.0": + version: 2.6.0 + resolution: "thingies@npm:2.6.0" + peerDependencies: + tslib: ^2 + checksum: 10c0/6357247872cfd0ef5407455eab2724ccbf591f0b1a56a230c66ab139dc0a8bb4acaf85c177af0eee7a49740a4674c424529eca3e573b439eb256afed4e433fac + languageName: node + linkType: hard + +"thunky@npm:^1.0.2": + version: 1.1.0 + resolution: "thunky@npm:1.1.0" + checksum: 10c0/369764f39de1ce1de2ba2fa922db4a3f92e9c7f33bcc9a713241bc1f4a5238b484c17e0d36d1d533c625efb00e9e82c3e45f80b47586945557b45abb890156d2 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": + version: 0.2.16 + resolution: "tinyglobby@npm:0.2.16" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b + languageName: node + linkType: hard + +"tmp@npm:^0.2.1": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10c0/cee5bb7d674bb4ba3ab3f3841c2ca7e46daeb2109eec395c1ec7329a91d52fcb21032b79ac25161a37b2565c4858fefab927af9735926a113ef7bac9091a6e0e + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/079f0f0163b68ee2eedc65cab1de6fb121487eba9ae135c106a8bc5e4ab7906ae0b57d86016e4a7da8c0ee906da1eae8c6a1490cd6e2a5e5ccbca321e1f959ca + languageName: node + linkType: hard + +"trim-newlines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-newlines@npm:3.0.1" + checksum: 10c0/03cfefde6c59ff57138412b8c6be922ecc5aec30694d784f2a65ef8dcbd47faef580b7de0c949345abdc56ec4b4abf64dd1e5aea619b200316e471a3dd5bf1f6 + languageName: node + linkType: hard + +"ts-api-utils@npm:^2.5.0": + version: 2.5.0 + resolution: "ts-api-utils@npm:2.5.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c + languageName: node + linkType: hard + +"tsd@npm:^0.33.0": + version: 0.33.0 + resolution: "tsd@npm:0.33.0" + dependencies: + "@tsd/typescript": "npm:^5.9.2" + eslint-formatter-pretty: "npm:^4.1.0" + globby: "npm:^11.0.1" + jest-diff: "npm:^29.0.3" + meow: "npm:^9.0.0" + path-exists: "npm:^4.0.0" + read-pkg-up: "npm:^7.0.0" + bin: + tsd: dist/cli.js + checksum: 10c0/24cd7a07abcd339fc80152a3dae910cd276bc70c081357b0165b6ece845b1dbf321767fcb9627794c10385161368a671a0c80b5ad18547ad9668b46071288d4b + languageName: node + linkType: hard + +"tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2.0.0, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"tsyringe@npm:^4.10.0": + version: 4.10.0 + resolution: "tsyringe@npm:4.10.0" + dependencies: + tslib: "npm:^1.9.3" + checksum: 10c0/918594b4dfac97beb8be2c041c6ec45f078ef3768ed4edfe35ae2c709ab503e2e6b454b2b37e692c658572d1972a428fbfdbc0a2b42fee727a83c1c685fbe5e1 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-fest@npm:^0.18.0": + version: 0.18.1 + resolution: "type-fest@npm:0.18.1" + checksum: 10c0/303f5ecf40d03e1d5b635ce7660de3b33c18ed8ebc65d64920c02974d9e684c72483c23f9084587e9dd6466a2ece1da42ddc95b412a461794dd30baca95e2bac + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^0.6.0": + version: 0.6.0 + resolution: "type-fest@npm:0.6.0" + checksum: 10c0/0c585c26416fce9ecb5691873a1301b5aff54673c7999b6f925691ed01f5b9232db408cdbb0bd003d19f5ae284322523f44092d1f81ca0a48f11f7cf0be8cd38 + languageName: node + linkType: hard + +"type-fest@npm:^0.8.0, type-fest@npm:^0.8.1": + version: 0.8.1 + resolution: "type-fest@npm:0.8.1" + checksum: 10c0/dffbb99329da2aa840f506d376c863bd55f5636f4741ad6e65e82f5ce47e6914108f44f340a0b74009b0cb5d09d6752ae83203e53e98b1192cf80ecee5651636 + languageName: node + linkType: hard + +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + languageName: node + linkType: hard + +"typedarray-to-buffer@npm:^3.1.5": + version: 3.1.5 + resolution: "typedarray-to-buffer@npm:3.1.5" + dependencies: + is-typedarray: "npm:^1.0.0" + checksum: 10c0/4ac5b7a93d604edabf3ac58d3a2f7e07487e9f6e98195a080e81dbffdc4127817f470f219d794a843b87052cedef102b53ac9b539855380b8c2172054b7d5027 + languageName: node + linkType: hard + +"typescript-eslint@npm:^8.58.1": + version: 8.58.1 + resolution: "typescript-eslint@npm:8.58.1" + dependencies: + "@typescript-eslint/eslint-plugin": "npm:8.58.1" + "@typescript-eslint/parser": "npm:8.58.1" + "@typescript-eslint/typescript-estree": "npm:8.58.1" + "@typescript-eslint/utils": "npm:8.58.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/26a71e120e216bdd5c5535043bbbd90c15c23f1d25e130677c3f2007e42501427049b98a874ad6d2c9cb785bf6ce2f2e71458f9db918dcb341f4898d771ff26f + languageName: node + linkType: hard + +"typescript@npm:5.6.1-rc": + version: 5.6.1-rc + resolution: "typescript@npm:5.6.1-rc" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/9d6e99b7ddbc797ebc8d09e5d7f2f81ce5f288a4e607bcded3c545ced8582796c937fba31bede5660a56f978b2f68e7c4ee85614b307425a2b4617535721509f + languageName: node + linkType: hard + +"typescript@npm:^6.0.2": + version: 6.0.2 + resolution: "typescript@npm:6.0.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/4b860b0bf87cc0fee0f66d8ef2640b5a8a8a8c74d1129adb82e389e5f97124383823c47946bef8a73ede371461143a3aa8544399d2133c7b2e4f07e81860af7f + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.6.1-rc#optional!builtin": + version: 5.6.1-rc + resolution: "typescript@patch:typescript@npm%3A5.6.1-rc#optional!builtin::version=5.6.1-rc&hash=8c6c40" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/9c6f8d864bc2efc964d1bfc94bf2e14f35cd2ad3df5e92d5304c8759674ba77ae927078a5fc06a527c087953615465dd5decc2d4d28ca8e13c11f9b29e068d93 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^6.0.2#optional!builtin": + version: 6.0.2 + resolution: "typescript@patch:typescript@npm%3A6.0.2#optional!builtin::version=6.0.2&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/49f0b84fc6ca55653e77752b8a61beabc09ee3dae5d965c31596225aa6ef213c5727b1d2e895b900416dc603854ba0872ac4a812c2a4ed6793a601f9c675de02 + languageName: node + linkType: hard + +"ua-parser-js@npm:^0.7.30": + version: 0.7.41 + resolution: "ua-parser-js@npm:0.7.41" + bin: + ua-parser-js: script/cli.js + checksum: 10c0/b134bc0d8da10c76e07740a0ade61c193fd4c4d120ba2cb2530e26931f6b550dd60b6e801d8891f6d9c23dfebadf5590294739069edff94396f173cc8cc5767e + languageName: node + linkType: hard + +"uglify-js@npm:^3.19.3": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10c0/83b0a90eca35f778e07cad9622b80c448b6aad457c9ff8e568afed978212b42930a95f9e1be943a1ffa4258a3340fbb899f41461131c05bb1d0a9c303aed8479 + languageName: node + linkType: hard + +"undici-types@npm:~7.19.0": + version: 7.19.2 + resolution: "undici-types@npm:7.19.2" + checksum: 10c0/7159f10546f9f6c47d36776bb1bbf8671e87c1e587a6fee84ae1f111ae8de4f914efa8ca0dfcd224f4f4a9dfc3f6028f627ccb5ddaccf82d7fd54671b89fac3e + languageName: node + linkType: hard + +"unicode-canonical-property-names-ecmascript@npm:^2.0.0": + version: 2.0.1 + resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" + checksum: 10c0/f83bc492fdbe662860795ef37a85910944df7310cac91bd778f1c19ebc911e8b9cde84e703de631e5a2fcca3905e39896f8fc5fc6a44ddaf7f4aff1cda24f381 + languageName: node + linkType: hard + +"unicode-emoji-modifier-base@npm:^1.0.0": + version: 1.0.0 + resolution: "unicode-emoji-modifier-base@npm:1.0.0" + checksum: 10c0/b37623fcf0162186debd20f116483e035a2d5b905b932a2c472459d9143d446ebcbefb2a494e2fe4fa7434355396e2a95ec3fc1f0c29a3bc8f2c827220e79c66 + languageName: node + linkType: hard + +"unicode-match-property-ecmascript@npm:^2.0.0": + version: 2.0.0 + resolution: "unicode-match-property-ecmascript@npm:2.0.0" + dependencies: + unicode-canonical-property-names-ecmascript: "npm:^2.0.0" + unicode-property-aliases-ecmascript: "npm:^2.0.0" + checksum: 10c0/4d05252cecaf5c8e36d78dc5332e03b334c6242faf7cf16b3658525441386c0a03b5f603d42cbec0f09bb63b9fd25c9b3b09667aee75463cac3efadae2cd17ec + languageName: node + linkType: hard + +"unicode-match-property-value-ecmascript@npm:^2.2.1": + version: 2.2.1 + resolution: "unicode-match-property-value-ecmascript@npm:2.2.1" + checksum: 10c0/93acd1ad9496b600e5379d1aaca154cf551c5d6d4a0aefaf0984fc2e6288e99220adbeb82c935cde461457fb6af0264a1774b8dfd4d9a9e31548df3352a4194d + languageName: node + linkType: hard + +"unicode-property-aliases-ecmascript@npm:^2.0.0": + version: 2.2.0 + resolution: "unicode-property-aliases-ecmascript@npm:2.2.0" + checksum: 10c0/b338529831c988ac696f2bdbcd4579d1c5cc844b24eda7269973c457fa81989bdb49a366af37a448eb1a60f1dae89559ea2a5854db2797e972a0162eee0778c6 + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + +"unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.2.3": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard + +"validate-npm-package-license@npm:^3.0.1": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: "npm:^3.0.0" + spdx-expression-parse: "npm:^3.0.0" + checksum: 10c0/7b91e455a8de9a0beaa9fe961e536b677da7f48c9a493edf4d4d4a87fd80a7a10267d438723364e432c2fcd00b5650b5378275cded362383ef570276e6312f4f + languageName: node + linkType: hard + +"validate-npm-package-name@npm:^5.0.0": + version: 5.0.1 + resolution: "validate-npm-package-name@npm:5.0.1" + checksum: 10c0/903e738f7387404bb72f7ac34e45d7010c877abd2803dc2d614612527927a40a6d024420033132e667b1bade94544b8a1f65c9431a4eb30d0ce0d80093cd1f74 + languageName: node + linkType: hard + +"vary@npm:^1, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"void-elements@npm:^2.0.0": + version: 2.0.1 + resolution: "void-elements@npm:2.0.1" + checksum: 10c0/23b4f35bbeabcaa5c87a9f638ae80862a9313dccbaa8973b0eada81dbe97488ae11baf4d8aa2846bc397d31456afdfd8d791bb44c542f83735e6d04af6996f4d + languageName: node + linkType: hard + +"watchpack@npm:^2.5.1": + version: 2.5.1 + resolution: "watchpack@npm:2.5.1" + dependencies: + glob-to-regexp: "npm:^0.4.1" + graceful-fs: "npm:^4.1.2" + checksum: 10c0/dffbb483d1f61be90dc570630a1eb308581e2227d507d783b1d94a57ac7b705ecd9a1a4b73d73c15eab596d39874e5276a3d9cb88bbb698bafc3f8d08c34cf17 + languageName: node + linkType: hard + +"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": + version: 1.7.3 + resolution: "wbuf@npm:1.7.3" + dependencies: + minimalistic-assert: "npm:^1.0.0" + checksum: 10c0/56edcc5ef2b3d30913ba8f1f5cccc364d180670b24d5f3f8849c1e6fb514e5c7e3a87548ae61227a82859eba6269c11393ae24ce12a2ea1ecb9b465718ddced7 + languageName: node + linkType: hard + +"webpack-dev-middleware@npm:^7.4.2": + version: 7.4.5 + resolution: "webpack-dev-middleware@npm:7.4.5" + dependencies: + colorette: "npm:^2.0.10" + memfs: "npm:^4.43.1" + mime-types: "npm:^3.0.1" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + schema-utils: "npm:^4.0.0" + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: 10c0/e72fa7de3b1589c0c518976358f946d9ec97699a3eb90bfd40718f4be3e9d5d13dc80f748c5c16662efbf1400cedbb523c79f56a778e6e8ffbdf1bd93be547eb + languageName: node + linkType: hard + +"webpack-dev-server@npm:^5.2.3": + version: 5.2.3 + resolution: "webpack-dev-server@npm:5.2.3" + dependencies: + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.25" + "@types/express-serve-static-core": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" + ansi-html-community: "npm:^0.0.8" + bonjour-service: "npm:^1.2.1" + chokidar: "npm:^3.6.0" + colorette: "npm:^2.0.10" + compression: "npm:^1.8.1" + connect-history-api-fallback: "npm:^2.0.0" + express: "npm:^4.22.1" + graceful-fs: "npm:^4.2.6" + http-proxy-middleware: "npm:^2.0.9" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" + p-retry: "npm:^6.2.0" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^5.5.0" + serve-index: "npm:^1.9.1" + sockjs: "npm:^0.3.24" + spdy: "npm:^4.0.2" + webpack-dev-middleware: "npm:^7.4.2" + ws: "npm:^8.18.0" + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + bin: + webpack-dev-server: bin/webpack-dev-server.js + checksum: 10c0/a716f1d509635ad9f2779baf242657740e6ad516ce210fe094cbf3b16f25f114e477c45a751ad2bbf1c601cbbe67b6ba9b8b43159b7c01fc3342c95b985fe963 + languageName: node + linkType: hard + +"webpack-merge@npm:^4.1.5": + version: 4.2.2 + resolution: "webpack-merge@npm:4.2.2" + dependencies: + lodash: "npm:^4.17.15" + checksum: 10c0/283cb4ffe4d4ae6de23d595154868780126835ded241748da0b070c6cca6974c229493ac0b6b7160c2c92950c950c8e5edf036a192da78e32e22a9c81593ad16 + languageName: node + linkType: hard + +"webpack-sources@npm:^3.3.4": + version: 3.3.4 + resolution: "webpack-sources@npm:3.3.4" + checksum: 10c0/94a42508531338eb41939cf1d48a4a8a6db97f3a47e5453cff2133a68d3169ca779d4bcbe9dfed072ce16611959eba1e16f085bc2dc56714e1a1c1783fd661a3 + languageName: node + linkType: hard + +"webpack@npm:^5.106.1": + version: 5.106.1 + resolution: "webpack@npm:5.106.1" + dependencies: + "@types/eslint-scope": "npm:^3.7.7" + "@types/estree": "npm:^1.0.8" + "@types/json-schema": "npm:^7.0.15" + "@webassemblyjs/ast": "npm:^1.14.1" + "@webassemblyjs/wasm-edit": "npm:^1.14.1" + "@webassemblyjs/wasm-parser": "npm:^1.14.1" + acorn: "npm:^8.16.0" + acorn-import-phases: "npm:^1.0.3" + browserslist: "npm:^4.28.1" + chrome-trace-event: "npm:^1.0.2" + enhanced-resolve: "npm:^5.20.0" + es-module-lexer: "npm:^2.0.0" + eslint-scope: "npm:5.1.1" + events: "npm:^3.2.0" + glob-to-regexp: "npm:^0.4.1" + graceful-fs: "npm:^4.2.11" + json-parse-even-better-errors: "npm:^2.3.1" + loader-runner: "npm:^4.3.1" + mime-types: "npm:^2.1.27" + neo-async: "npm:^2.6.2" + schema-utils: "npm:^4.3.3" + tapable: "npm:^2.3.0" + terser-webpack-plugin: "npm:^5.3.17" + watchpack: "npm:^2.5.1" + webpack-sources: "npm:^3.3.4" + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 10c0/eb56ba99564e185c83fb8d898253c0e6b91fcfcab54e3716bc6bc4df2636511686edb79073313ad9394b757c912b9489e28c7a5bd33d4d386d3103ba9608ca47 + languageName: node + linkType: hard + +"websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": + version: 0.7.4 + resolution: "websocket-driver@npm:0.7.4" + dependencies: + http-parser-js: "npm:>=0.5.1" + safe-buffer: "npm:>=5.1.0" + websocket-extensions: "npm:>=0.1.1" + checksum: 10c0/5f09547912b27bdc57bac17b7b6527d8993aa4ac8a2d10588bb74aebaf785fdcf64fea034aae0c359b7adff2044dd66f3d03866e4685571f81b13e548f9021f1 + languageName: node + linkType: hard + +"websocket-extensions@npm:>=0.1.1": + version: 0.1.4 + resolution: "websocket-extensions@npm:0.1.4" + checksum: 10c0/bbc8c233388a0eb8a40786ee2e30d35935cacbfe26ab188b3e020987e85d519c2009fe07cfc37b7f718b85afdba7e54654c9153e6697301f72561bfe429177e0 + languageName: node + linkType: hard + +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10c0/087038e7992649eaffa6c7a4f3158d5b53b14cf5b6c1f0e043dccfacb1ba179d12f17545d5b85ebd94a42ce280a6fe65d0cbcab70f4fc6daad1dfae85e0e6a3e + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.1 + resolution: "which@npm:6.0.1" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"workerpool@npm:^9.2.0": + version: 9.3.4 + resolution: "workerpool@npm:9.3.4" + checksum: 10c0/b09d80c81c6e50dab1bc6cc3a4180d4222068f17ada9b04fb7053bf98fdbe3dbd6bdd04ad1420363f5391cbf57d622ecd2680469ad0137aef990f510ab807a09 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^3.0.0": + version: 3.0.3 + resolution: "write-file-atomic@npm:3.0.3" + dependencies: + imurmurhash: "npm:^0.1.4" + is-typedarray: "npm:^1.0.0" + signal-exit: "npm:^3.0.2" + typedarray-to-buffer: "npm:^3.1.5" + checksum: 10c0/7fb67affd811c7a1221bed0c905c26e28f0041e138fb19ccf02db57a0ef93ea69220959af3906b920f9b0411d1914474cdd90b93a96e5cd9e8368d9777caac0e + languageName: node + linkType: hard + +"ws@npm:^8.18.0": + version: 8.20.0 + resolution: "ws@npm:8.20.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/956ac5f11738c914089b65878b9223692ace77337ba55379ae68e1ecbeae9b47a0c6eb9403688f609999a58c80d83d99865fe0029b229d308b08c1ef93d4ea14 + languageName: node + linkType: hard + +"ws@npm:~8.18.3": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10c0/44318f3585eb97be994fc21a20ddab2649feaf1fbe893f1f866d936eea3d5f8c743bec6dc02e49fbdd3c0e69e9b36f449d90a0b165a4f47dd089747af4cf2377 + languageName: node + linkType: hard + +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10c0/308a2efd7cc296ab2c0f3b9284fd4827be01cfeb647b3ba18230e3a416eb1bc887ac050de9f8c4fd9e7856b2e8246e05d190b53c96c5ad8d8cb56dffb6f81024 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10c0/25df918833592a83f52e7e4f91ba7d7bfaa2b891ebf7fe901923c2ee797534f23a176913ff6ff7ebbc1cc1725a044cc6a6539fed8bfd4e13b5b16376875f9499 + languageName: node + linkType: hard + +"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs-unparser@npm:^2.0.0": + version: 2.0.0 + resolution: "yargs-unparser@npm:2.0.0" + dependencies: + camelcase: "npm:^6.0.0" + decamelize: "npm:^4.0.0" + flat: "npm:^5.0.2" + is-plain-obj: "npm:^2.1.0" + checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 + languageName: node + linkType: hard + +"yargs@npm:^15.0.2": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10c0/f1ca680c974333a5822732825cca7e95306c5a1e7750eb7b973ce6dc4f97a6b0a8837203c8b194f461969bfe1fb1176d1d423036635285f6010b392fa498ab2d + languageName: node + linkType: hard + +"yargs@npm:^16.0.0, yargs@npm:^16.1.1": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: "npm:^7.0.2" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^20.2.2" + checksum: 10c0/b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard