Repository: node-formidable/formidable Branch: master Commit: 44768be86149 Files: 120 Total size: 472.6 KB Directory structure: gitextract_b0r5txre/ ├── .all-contributorsrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github/ │ ├── .kodiak.toml │ ├── dependabot.yml │ └── workflows/ │ ├── codeql-analysis.yml │ └── main.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_pt_BR.md ├── VERSION_NOTES.md ├── benchmark/ │ ├── 2022-11-30-i5-9600k.txt │ ├── e2e.txt │ ├── index.js │ └── server.js ├── examples/ │ ├── express-middleware.js │ ├── forceBuffer.js │ ├── json.js │ ├── log-file-content-to-console.js │ ├── multipart-parser.js │ ├── multiples.js │ ├── store-files-on-s3.js │ ├── upload-multiple-files.js │ ├── urlencoded-no-enctype.js │ ├── with-express.js │ ├── with-http.js │ └── with-koa2.js ├── nyc.config.js ├── package.json ├── pnpm-lock.yaml ├── src/ │ ├── Formidable.js │ ├── FormidableError.js │ ├── PersistentFile.js │ ├── VolatileFile.js │ ├── helpers/ │ │ ├── firstValues.js │ │ └── readBooleans.js │ ├── index.js │ ├── parsers/ │ │ ├── Dummy.js │ │ ├── JSON.js │ │ ├── Multipart.js │ │ ├── OctetStream.js │ │ ├── Querystring.js │ │ ├── StreamingQuerystring.js │ │ └── index.js │ └── plugins/ │ ├── index.js │ ├── json.js │ ├── multipart.js │ ├── octetstream.js │ └── querystring.js ├── test/ │ ├── fixture/ │ │ ├── file/ │ │ │ ├── funkyfilename.txt │ │ │ ├── plain.txt │ │ │ └── second-plaintext.txt │ │ ├── http/ │ │ │ ├── encoding/ │ │ │ │ ├── beta-sticker-1.png.http │ │ │ │ ├── binaryfile.tar.gz.http │ │ │ │ ├── blank.gif.http │ │ │ │ ├── menu_separator.png.http │ │ │ │ └── plain.txt.http │ │ │ ├── misc/ │ │ │ │ ├── boundary-substring-json.http │ │ │ │ ├── empty-multipart.http │ │ │ │ ├── empty-multipart2.http │ │ │ │ ├── empty-urlencoded.http │ │ │ │ ├── empty.http │ │ │ │ └── minimal.http │ │ │ ├── no-filename/ │ │ │ │ ├── filename-name.http │ │ │ │ └── generic.http │ │ │ ├── preamble/ │ │ │ │ ├── crlf.http │ │ │ │ └── preamble.http │ │ │ ├── special-chars-in-filename/ │ │ │ │ ├── info.md │ │ │ │ ├── line-separator.http │ │ │ │ ├── osx-chrome-13.http │ │ │ │ ├── osx-firefox-3.6.http │ │ │ │ ├── osx-safari-5.http │ │ │ │ ├── xp-chrome-12.http │ │ │ │ ├── xp-ie-7.http │ │ │ │ ├── xp-ie-8.http │ │ │ │ └── xp-safari-5.http │ │ │ └── workarounds/ │ │ │ ├── missing-hyphens1.http │ │ │ └── missing-hyphens2.http │ │ ├── js/ │ │ │ ├── encoding.js │ │ │ ├── misc.js │ │ │ ├── no-filename.js │ │ │ ├── preamble.js │ │ │ ├── special-chars-in-filename.js │ │ │ └── workarounds.js │ │ ├── multi_video.upload │ │ └── multipart.js │ ├── integration/ │ │ ├── file-write-stream-handler-option.test.js │ │ ├── fixtures.test.js │ │ ├── json.test.js │ │ ├── octet-stream.test.js │ │ └── store-files-option.test.js │ ├── standalone/ │ │ ├── connection-aborted.test.js │ │ ├── content-transfer-encoding.test.js │ │ ├── issue-46.test.js │ │ └── keep-alive-error.test.js │ ├── tools/ │ │ └── base64.html │ └── unit/ │ ├── custom-plugins.test.js │ ├── formidable.test.js │ ├── multipart-parser.test.js │ ├── persistent-file.disabled-test.js │ ├── querystring-parser.test.js │ └── volatile-file.test.js ├── test-legacy/ │ ├── .DS_Store │ ├── README.md │ ├── common.js │ ├── integration/ │ │ └── test-multipart-parser.js │ ├── simple/ │ │ ├── test-file.js │ │ └── test-incoming-form.js │ └── system/ │ └── test-multi-video-upload.js ├── test-node/ │ └── standalone/ │ ├── createDirsFromUploads.test.js │ ├── end-event-emitted-twice.test.js │ ├── multipart_parser.test.js │ └── promise.test.js └── tool/ ├── record.js └── rollup.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .all-contributorsrc ================================================ { "projectName": "node-formidable", "projectOwner": "node-formidable", "repoType": "github", "repoHost": "https://github.com", "imageSize": 100, "contributorsPerLine": 6, "commitConvention": "angular", "commit": true, "skipCi": true, "files": [ "README.md" ], "contributors": [ { "login": "felixge", "name": "Felix Geisendörfer", "avatar_url": "https://avatars3.githubusercontent.com/u/15000?s=460&v=4", "profile": "https://twitter.com/felixge", "contributions": [ "code", "design", "ideas", "doc" ] }, { "login": "tunnckoCore", "name": "Charlike Mike Reagent", "avatar_url": "https://avatars3.githubusercontent.com/u/5038030?v=4", "profile": "https://tunnckoCore.com", "contributions": [ "bug", "infra", "design", "code", "doc", "example", "ideas", "maintenance", "test" ] }, { "login": "kedarv", "name": "Kedar", "avatar_url": "https://avatars1.githubusercontent.com/u/1365665?v=4", "profile": "https://github.com/kedarv", "contributions": [ "code", "test", "question", "bug" ] }, { "login": "GrosSacASac", "name": "Walle Cyril", "avatar_url": "https://avatars0.githubusercontent.com/u/5721194?v=4", "profile": "https://github.com/GrosSacASac", "contributions": [ "question", "bug", "code", "financial", "ideas", "maintenance" ] }, { "login": "xarguments", "name": "Xargs", "avatar_url": "https://avatars2.githubusercontent.com/u/40522463?v=4", "profile": "https://github.com/xarguments", "contributions": [ "question", "bug", "code", "maintenance" ] }, { "login": "Amit-A", "name": "Amit-A", "avatar_url": "https://avatars1.githubusercontent.com/u/7987238?v=4", "profile": "https://github.com/Amit-A", "contributions": [ "question", "bug", "code" ] }, { "login": "charmander", "name": "Charmander", "avatar_url": "https://avatars1.githubusercontent.com/u/1889843?v=4", "profile": "https://charmander.me/", "contributions": [ "question", "bug", "code", "ideas", "maintenance" ] }, { "login": "DylanPiercey", "name": "Dylan Piercey", "avatar_url": "https://avatars2.githubusercontent.com/u/4985201?v=4", "profile": "https://twitter.com/dylan_piercey", "contributions": [ "ideas" ] }, { "login": "ad-m", "name": "Adam Dobrawy", "avatar_url": "https://avatars1.githubusercontent.com/u/3618479?v=4", "profile": "http://ochrona.jawne.info.pl", "contributions": [ "bug", "doc" ] }, { "login": "amitrohatgi", "name": "amitrohatgi", "avatar_url": "https://avatars3.githubusercontent.com/u/12177021?v=4", "profile": "https://github.com/amitrohatgi", "contributions": [ "ideas" ] }, { "login": "fengxinming", "name": "Jesse Feng", "avatar_url": "https://avatars2.githubusercontent.com/u/6262382?v=4", "profile": "https://github.com/fengxinming", "contributions": [ "bug" ] }, { "login": "quantumsheep", "name": "Nathanael Demacon", "avatar_url": "https://avatars1.githubusercontent.com/u/7271496?v=4", "profile": "https://qtmsheep.com", "contributions": [ "question", "code", "review" ] }, { "login": "MunMunMiao", "name": "MunMunMiao", "avatar_url": "https://avatars1.githubusercontent.com/u/18216142?v=4", "profile": "https://github.com/MunMunMiao", "contributions": [ "bug" ] }, { "login": "gabipetrovay", "name": "Gabriel Petrovay", "avatar_url": "https://avatars0.githubusercontent.com/u/1170398?v=4", "profile": "https://github.com/gabipetrovay", "contributions": [ "bug", "code" ] }, { "login": "Elzair", "name": "Philip Woods", "avatar_url": "https://avatars0.githubusercontent.com/u/2352818?v=4", "profile": "https://github.com/Elzair", "contributions": [ "code", "ideas" ] }, { "login": "dmolim", "name": "Dmitry Ivonin", "avatar_url": "https://avatars2.githubusercontent.com/u/7090374?v=4", "profile": "https://github.com/dmolim", "contributions": [ "doc" ] }, { "login": "masterkain", "name": "Claudio Poli", "avatar_url": "https://avatars1.githubusercontent.com/u/12844?v=4", "profile": "https://audiobox.fm", "contributions": [ "code" ] } ] } ================================================ FILE: .editorconfig ================================================ # http://editorconfig.org/ root = true [*] indent_style = space indent_size = 2 tab_width = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = true ================================================ FILE: .eslintignore ================================================ # Ignore everything! * # de-ignores: add here what you want to be committed !*.*js* !*.ts* !*.md* !**/src !**/src/** !**/test !**/test/** !**/*tests* !**/*tests*/** !**/benchmark* !**/benchmark*/** !**/example* !**/example*/** # re-ignores: add here what you want to be ignored again test/tmp ================================================ FILE: .eslintrc.cjs ================================================ 'use strict'; const airbnbBase = require('eslint-config-airbnb-base'); // eslint-disable-next-line import/no-dynamic-require const bestPractices = require(airbnbBase.extends[0]); const ignoredProps = bestPractices.rules[ 'no-param-reassign' ][1].ignorePropertyModificationsFor.concat( 'err', 'x', '_', 'opts', 'options', 'settings', 'config', 'cfg', ); // Additional rules that are specific and overriding previous const additionalChanges = { strict: 'off', // Enforce using named functions when regular function is used, // otherwise use arrow functions 'func-names': ['error', 'always'], // Always use parens (for consistency). // https://eslint.org/docs/rules/arrow-parens 'arrow-parens': ['error', 'always', { requireForBlockBody: true }], 'prefer-arrow-callback': [ 'error', { allowNamedFunctions: true, allowUnboundThis: true }, ], // http://eslint.org/docs/rules/max-params 'max-params': ['error', { max: 3 }], // http://eslint.org/docs/rules/max-statements 'max-statements': ['error', { max: 20 }], // http://eslint.org/docs/rules/max-statements-per-line 'max-statements-per-line': ['error', { max: 1 }], // http://eslint.org/docs/rules/max-nested-callbacks 'max-nested-callbacks': ['error', { max: 4 }], // http://eslint.org/docs/rules/max-depth 'max-depth': ['error', { max: 4 }], // enforces no braces where they can be omitted // https://eslint.org/docs/rules/arrow-body-style // Never enable for object literal. 'arrow-body-style': [ 'error', 'as-needed', { requireReturnForObjectLiteral: false }, ], // Allow functions to be use before define because: // 1) they are hoisted, // 2) because ensure read flow is from top to bottom // 3) logically order of the code. // 4) the only addition is 'typedefs' option, see overrides for TS files 'no-use-before-define': [ 'error', { functions: false, classes: true, variables: true, }, ], // Same as AirBnB, but adds `opts`, `options`, `x` and `err` to exclusions! // disallow reassignment of function parameters // disallow parameter object manipulation except for specific exclusions // rule: https://eslint.org/docs/rules/no-param-reassign.html 'no-param-reassign': [ 'error', { props: true, ignorePropertyModificationsFor: ignoredProps, }, ], // disallow declaration of variables that are not used in the code 'no-unused-vars': [ 'error', { ignoreRestSiblings: true, // airbnb's default vars: 'all', // airbnb's default varsIgnorePattern: '^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))', args: 'after-used', // airbnb's default argsIgnorePattern: '^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))', // catch blocks are handled by Unicorns caughtErrors: 'none', // caughtErrorsIgnorePattern: '^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))', }, ], }; const importRules = { 'import/namespace': ['error', { allowComputed: true }], 'import/no-absolute-path': 'error', 'import/no-webpack-loader-syntax': 'error', 'import/no-self-import': 'error', // Enable this sometime in the future when Node.js has ES2015 module support // 'import/no-cycle': 'error', // Disabled as it doesn't work with TypeScript // 'import/newline-after-import': 'error', 'import/no-amd': 'error', 'import/no-duplicates': 'error', // Enable this sometime in the future when Node.js has ES2015 module support // 'import/unambiguous': 'error', // Enable this sometime in the future when Node.js has ES2015 module support // 'import/no-commonjs': 'error', // Looks useful, but too unstable at the moment // 'import/no-deprecated': 'error', 'import/no-extraneous-dependencies': 'off', 'import/no-mutable-exports': 'error', 'import/no-named-as-default-member': 'error', 'import/no-named-as-default': 'error', // Disabled because it's buggy and it also doesn't work with TypeScript // 'import/no-unresolved': [ // 'error', // { // commonjs: true // } // ], 'import/order': 'error', 'import/no-unassigned-import': [ 'error', { allow: ['@babel/polyfill', '@babel/register'] }, ], 'import/prefer-default-export': 'off', // Ensure more web-compat // ! note that it doesn't work in CommonJS // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md 'import/extensions': 'off', // ? Always use named exports. Enable? // 'import/no-default-export': 'error', // ? enable? 'import/exports-last': 'off', // todo: Enable in future. // Ensures everything is tested (all exports should be used). // For cases when you don't want or can't test, add eslint-ignore comment! // see: https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unused-modules.md 'import/no-unused-modules': 'off', 'import/no-useless-path-segments': ['error', { noUselessIndex: false }], }; module.exports = { env: { es6: true, es2020: true, jest: true, node: true, commonjs: true, }, extends: ['eslint:recommended', 'airbnb-base', 'plugin:prettier/recommended'], plugins: ['prettier'], rules: { ...additionalChanges, ...importRules, }, }; ================================================ FILE: .github/.kodiak.toml ================================================ # .kodiak.toml # Minimal config. version is the only required field. version = 1 [merge] automerge_label = "ship it" require_automerge_label = true block_on_neutral_required_check_runs = true blocking_labels = ["wip", "do not merge"] delete_branch_on_merge = true notify_on_conflict = true optimistic_updates = false prioritize_ready_to_merge = true [merge.message] title = "pull_request_title" body = "pull_request_body" body_type = "markdown" include_pr_number = true include_coauthors = true include_pull_request_url = true cut_body_after = "" cut_body_before = "" [merge.automerge_dependencies] # only auto merge "minor" and "patch" version upgrades. # do not automerge "major" version upgrades. versions = ["minor", "patch"] usernames = ["dependabot", "renovate"] [approve] auto_approve_usernames = ["dependabot", "renovate"] [update] always = true require_automerge_label = true ================================================ FILE: .github/dependabot.yml ================================================ # Versions and updates, dependabot.yml version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" # Maintain dependencies for npm - package-ecosystem: "npm" directory: "/" schedule: interval: "daily" time: "02:00" # No one cares about dependency labels, they should be auto-merged labels: [] allow: # Allow both direct and indirect updates for all packages - dependency-type: "all" rebase-strategy: "auto" # reviewers: [] ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '0 2 * * *' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'javascript' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 ================================================ FILE: .github/workflows/main.yml ================================================ name: ci # on: # push: # branches: [ master ] # pull_request: # branches: [ master ] # jobs: # # lint: # # name: Lint # # runs-on: ubuntu-latest # # steps: # # - uses: actions/checkout@v5 # # - name: Set up Node.js # # uses: actions/setup-node@v5 # # with: # # node-version: '20' # # cache: 'npm' # # - name: Install dependencies # # run: npm install # # - name: Run lint # # run: npm run lint # test: # name: Test on ${{ matrix.os }} # runs-on: ${{ matrix.os }} # strategy: # fail-fast: false # matrix: # os: [ubuntu-latest, macos-latest] # node-version: ['lts/*'] # steps: # - uses: actions/checkout@v5 # - name: Set up Node.js ${{ matrix.node-version }} # uses: actions/setup-node@v5 # with: # node-version: ${{ matrix.node-version }} # cache: 'npm' # - name: Install dependencies # run: npm install # - name: Run tests # run: npm test on: push: branches: - '*' pull_request: branches: - '*' jobs: build-and-test: name: Test on ${{ matrix.os }} with Node.js ${{matrix.node-version}} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] node-version: [18, 20, 22, 'lts/*'] steps: - name: Checkout uses: actions/checkout@v5 - uses: pnpm/action-setup@v4 name: Install pnpm with: run_install: false - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} cache: pnpm - name: Install dependencies run: pnpm install - name: Run tests run: pnpm test ================================================ FILE: .gitignore ================================================ # Ignore everything! * *~* # de-ignores: add here what you want to be committed !logo.png !logo.jpg !test-legacy !tool !*.*js* !*.ts* !*.md* !.*rc !.*ignore !LICENSE !.editorconfig !package.json !yarn.lock !pnpm-lock.yaml !**/src !**/src/** !**/test !**/test/** !**/test-node !**/test-node/** !**/*tests* !**/*tests*/** !**/.github !**/.github/** !**/example* !**/example*/** !**/benchmark* !**/benchmark*/** # re-ignores: add here what you want to be ignored again test/tmp # !src/*.js # !src/*.ts # !test # !test/*.js # !test/*.ts # !test/**/*.js # !test/**/*.ts # !*/__tests__ # *.tsbuildinfo # .*cache # *.cache # test/tmp # *.upload # *.un~ # # Build environment # dist # # Package managers lockfiles # package-lock.json # shrinkwrap.json # # Logs # logs # *.log # *~ # # Runtime data # pids # *.pid # *.seed # *.pid.lock # # Directory for instrumented libs generated by jscoverage/JSCover # lib-cov # # Coverage directory used by tools like istanbul # /coverage # # nyc test coverage # .nyc_output # # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) # .grunt # # Bower dependency directory (https://bower.io/) # bower_components # # node-waf configuration # .lock-wscript # # Compiled binary addons (https://nodejs.org/api/addons.html) # build/Release # # Dependency directories # node_modules/ # jspm_packages/ # # TypeScript v1 declaration files # typings/ # # Optional npm cache directory # .npm # # Optional eslint cache # .eslintcache # # Optional REPL history # .node_repl_history # # Output of 'npm pack' # *.tgz # # Yarn Integrity file # .yarn-integrity # # dotenv environment variables file # .env # # next.js build output # .next benchmark/testuploads/ ================================================ FILE: .prettierignore ================================================ # Ignore everything! * # de-ignores: add here what you want to be committed !*.*js* !*.ts* !*.md* !*.y*ml !**/src !**/src/** !**/test !**/test/** !**/*tests* !**/*tests*/** !**/.github !**/.github/** !**/benchmark* !**/benchmark*/**/*.js !**/example* !**/example*/** # re-ignores: add here what you want to be ignored again test/tmp test/fixture/file test/fixture/http *.upload CHANGELOG.md # CHANGELOG.md # LICENSE* # dist # test/tmp # test/fixture/http # test/fixture/file # test/fixture/multi* # test/tools # # fixtures # # __fixture__ # # __fixtures__ # *.map # *.lock # *.js.snap # coverage # *.ico # *.png # *.svg # *.jpeg # *.jpg # !.all-contributorsrc # !.*rc.js # !.verb*.md # patches # **/static/**/*.css # *.tsbuildinfo # .*cache # *.cache # # Package managers lockfiles # package-lock.json # shrinkwrap.json # pnpm-lock.json # # Logs # logs # *.log # *~ # # Runtime data # pids # *.pid # *.seed # *.pid.lock # # Directory for instrumented libs generated by jscoverage/JSCover # lib-cov # # Coverage directory used by tools like istanbul # coverage # # nyc test coverage # .nyc_output # # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) # .grunt # # Bower dependency directory (https://bower.io/) # bower_components # # node-waf configuration # .lock-wscript # # Compiled binary addons (https://nodejs.org/api/addons.html) # build/Release # # Dependency directories # node_modules/ # jspm_packages/ # # TypeScript v1 declaration files # typings/ # # Optional npm cache directory # .npm # # Optional eslint cache # .eslintcache # # Optional REPL history # .node_repl_history # # Output of 'npm pack' # *.tgz # # Yarn Integrity file # .yarn-integrity # # dotenv environment variables file # .env # # next.js build output # .next ================================================ FILE: .prettierrc.js ================================================ 'use strict'; const config = require('@tunnckocore/prettier-config'); module.exports = { ...config, overrides: [ { files: ['**/*.md*'], options: { proseWrap: 'always', printWidth: 80, }, }, { files: ['**/.all-contributorsrc'], options: { parser: 'json-stringify', singleQuote: false, }, }, ], }; ================================================ FILE: CHANGELOG.md ================================================ # Changelog ### 3.5.4 - fix the `os.machine` breaking some dependents, fix [#994](https://github.com/node-formidable/formidable/issues/994) - add Node 16, 18, 20, 22 to CI/CD ### 3.5.3 - security report by ZAST.AI help for some vulnerabilities addressing (primarily the random names generation) - update failing tests - update CI/CD workflows and actions; - update CodeQL github action for security analysis - update readme, links and badges - update to use cuid2 (battle-tested `@paralleldrive/cuid2` package) for better random names - should not be breaking anything since it's still 25 characters long, but a lot safer and faster. ### 3.5.2 * fix: ([#982](https://github.com/node-formidable/formidable/pull/982)) make it easier to import hexoid with webpack ### 3.5.1 * fix: ([#945](https://github.com/node-formidable/formidable/pull/945)) multipart parser fix: flush or fail always (don't hang) ### 3.5.0 * feature: ([#944](https://github.com/node-formidable/formidable/pull/944)) Dual package: Can be imported as ES module and required as commonjs module ### 3.4.0 * feature: ([#940](https://github.com/node-formidable/formidable/pull/940)) form.parse returns a promise if no callback is provided * it resolves with an array `[fields, files]` ### 3.3.2 * feature: ([#855](https://github.com/node-formidable/formidable/pull/855)) add options.createDirsFromUploads, see README for usage * form.parse is an async function (ignore the promise) * benchmarks: add e2e becnhmark with as many request as possible per second * npm run to display all the commands * mark as latest on npm ### 3.2.5 * fix: ([#881](https://github.com/node-formidable/formidable/pull/881)) fail earlier when maxFiles is exceeded ### 3.2.4 * fix: ([#857](https://github.com/node-formidable/formidable/pull/857)) improve keep extension * The code from before 3.2.4 already removed some characters from the file extension. But not always. So it was inconsistent. * The new code cuts the file extension at the first invalid character (invalid in a file extension). * The characters that are considered invalid inside a file extension are all except the . numbers and a-Z. * This change only has an effect if filename option is not used and keepextension option is used ### 3.2.3 * fix: ([#852](https://github.com/node-formidable/formidable/pull/852)) end event is emitted once ### 3.2.2 * refactor: ([#801](https://github.com/node-formidable/formidable/pull/801)) ### 3.2.1 * fix: do not let empty file on error ([#796](https://github.com/node-formidable/formidable/pull/796)) * it was probably due to the fact that .destroy on a file stream does not always complete on time ### 3.2.0 * feat: maxFileSize option is now per file (as the name suggests) ([#791](https://github.com/node-formidable/formidable/pull/791)) * feat: add maxFiles option, default Infinity * feat: add maxTotalFileSize, default is maxFileSize (for backwards compatibility) * fix: minFileSize is per file * fix: allowEmptyFiles fix in cases where one file is not empty * fix: allowEmptyFiles false option by default * fix: rename wrongly named error * refactor: rename wrongly named maxFileSize into maxTotalFileSize ### 3.1.5 * fix: PersistentFile.toString ([#796](https://github.com/node-formidable/formidable/pull/796)) ### 3.1.4 * fix: add missing pluginFailed error ([#794](https://github.com/node-formidable/formidable/pull/794)) * refactor: use explicit node imports (#786) ### 3.1.1 * feat: handle top level json array, string and number ### 3.1.0 * feat: add firstValues, readBooleans helpers ### 3.0.0 * feat: remove options.multiples ([#730](https://github.com/node-formidable/formidable/pull/730)) * use modern URLSearchParams https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams internally * files and fields values are always arrays * fields with [] in the name do not receive special treatment * remove unused qs and querystring dependency * feat: Use ES modules ([#727](https://github.com/node-formidable/formidable/pull/727)) * options.enabledPlugins must contain the plugin themselves instead of the plugins names ### 2.0.0 * feat: files are detected if a mimetype is present (previously it was based on filename) * feat: add options.filter ([#716](https://github.com/node-formidable/formidable/pull/716)) * feat: add code and httpCode to most errors ([#686](https://github.com/node-formidable/formidable/pull/686)) * rename: option.hash into option.hashAlgorithm ([#689](https://github.com/node-formidable/formidable/pull/689)) * rename: file.path into file.filepath ([#689](https://github.com/node-formidable/formidable/pull/689)) * rename: file.type into file.mimetype ([#689](https://github.com/node-formidable/formidable/pull/689)) * refactor: split file.name into file.newFilename and file.originalFilename ([#689](https://github.com/node-formidable/formidable/pull/689)) * feat: prevent directory traversal attacks by default ([#689](https://github.com/node-formidable/formidable/pull/689)) * meta: stop including test files in npm ([7003c](https://github.com/node-formidable/formidable/commit/7003cd6133f90c384081accb51743688d5e1f4be)) * fix: handle invalid filenames ([d0a34](https://github.com/node-formidable/formidable/commit/d0a3484b048b8c177e62d66aecb03f5928f7a857)) * feat: add fileWriteStreamHandler option * feat: add allowEmptyFiles and minFileSize options * feat: Array support for fields and files ([#380](https://github.com/node-formidable/node-formidable/pull/380), [#340](https://github.com/node-formidable/node-formidable/pull/340), [#367](https://github.com/node-formidable/node-formidable/pull/367), [#33](https://github.com/node-formidable/node-formidable/issues/33), [#498](https://github.com/node-formidable/node-formidable/issues/498), [#280](https://github.com/node-formidable/node-formidable/issues/280), [#483](https://github.com/node-formidable/node-formidable/issues/483)) * possible partial fix of [#386](https://github.com/node-formidable/node-formidable/pull/386) with #380 (need tests and better implementation) * refactor: use hasOwnProperty in check against files/fields ([#522](https://github.com/node-formidable/node-formidable/pull/522)) * meta: do not promote `IncomingForm` and add `exports.default` ([#529](https://github.com/node-formidable/node-formidable/pull/529)) * meta: Improve examples and tests ([#523](https://github.com/node-formidable/node-formidable/pull/523)) * refactor: First step of Code quality improvements ([#525](https://github.com/node-formidable/node-formidable/pull/525)) * chore(funding): remove patreon & add npm funding field ([#525](https://github.com/node-formidable/node-formidable/pull/532) * feat: use Modern Streams API ([#531](https://github.com/node-formidable/node-formidable/pull/531)) * fix: urlencoded parsing to emit end [#543](https://github.com/node-formidable/node-formidable/pull/543), introduced in [#531](https://github.com/node-formidable/node-formidable/pull/531) * fix(tests): include multipart and qs parser unit tests, part of [#415](https://github.com/node-formidable/node-formidable/issues/415) * fix: reorganize exports + move parsers to `src/parsers/` * fix: update docs and examples [#544](https://github.com/node-formidable/node-formidable/pull/544) ([#248](https://github.com/node-formidable/node-formidable/issues/248), [#335](https://github.com/node-formidable/node-formidable/issues/335), [#371](https://github.com/node-formidable/node-formidable/issues/371), [#372](https://github.com/node-formidable/node-formidable/issues/372), [#387](https://github.com/node-formidable/node-formidable/issues/387), partly [#471](https://github.com/node-formidable/node-formidable/issues/471), [#535](https://github.com/node-formidable/node-formidable/issues/535)) * feat: introduce Plugins API, fix silent failing tests ([#545](https://github.com/node-formidable/node-formidable/pull/545), [#391](https://github.com/node-formidable/node-formidable/pull/391), [#407](https://github.com/node-formidable/node-formidable/pull/407), [#386](https://github.com/node-formidable/node-formidable/pull/386), [#374](https://github.com/node-formidable/node-formidable/pull/374), [#521](https://github.com/node-formidable/node-formidable/pull/521), [#267](https://github.com/node-formidable/node-formidable/pull/267)) * fix: exposing file writable stream errors ([#520](https://github.com/node-formidable/node-formidable/pull/520), [#316](https://github.com/node-formidable/node-formidable/pull/316), [#469](https://github.com/node-formidable/node-formidable/pull/469), [#470](https://github.com/node-formidable/node-formidable/pull/470)) * feat: custom file (re)naming, thru options.filename ([#591](https://github.com/node-formidable/node-formidable/pull/591), [#84](https://github.com/node-formidable/node-formidable/issues/84), [#86](https://github.com/node-formidable/node-formidable/issues/86), [#94](https://github.com/node-formidable/node-formidable/issues/94), [#154](https://github.com/node-formidable/node-formidable/issues/154), [#158](https://github.com/node-formidable/node-formidable/issues/158), [#488](https://github.com/node-formidable/node-formidable/issues/488), [#595](https://github.com/node-formidable/node-formidable/issues/595)) ### v1.2.1 (2018-03-20) * `maxFileSize` option with default of 200MB (Charlike Mike Reagent, Nima Shahri) * Simplified buffering in JSON parser to avoid denial of service attack (Kornel) * Fixed upload file cleanup on aborted requests (liaoweiqiang) * Fixed error handling of closed _writeStream (Vitalii) ### v1.1.1 (2017-01-15) * Fix DeprecationWarning about os.tmpDir() (Christian) * Update `buffer.write` order of arguments for Node 7 (Kornel Lesiński) * JSON Parser emits error events to the IncomingForm (alessio.montagnani) * Improved Content-Disposition parsing (Sebastien) * Access WriteStream of fs during runtime instead of include time (Jonas Amundsen) * Use built-in toString to convert buffer to hex (Charmander) * Add hash to json if present (Nick Stamas) * Add license to package.json (Simen Bekkhus) ### v1.0.14 (2013-05-03) * Add failing hash tests. (Ben Trask) * Enable hash calculation again (Eugene Girshov) * Test for immediate data events (Tim Smart) * Re-arrange IncomingForm#parse (Tim Smart) ### v1.0.13 * Only update hash if update method exists (Sven Lito) * According to travis v0.10 needs to go quoted (Sven Lito) * Bumping build node versions (Sven Lito) * Additional fix for empty requests (Eugene Girshov) * Change the default to 1000, to match the new Node behaviour. (OrangeDog) * Add ability to control maxKeys in the querystring parser. (OrangeDog) * Adjust test case to work with node 0.9.x (Eugene Girshov) * Update package.json (Sven Lito) * Path adjustment according to eb4468b (Markus Ast) ### v1.0.12 * Emit error on aborted connections (Eugene Girshov) * Add support for empty requests (Eugene Girshov) * Fix name/filename handling in Content-Disposition (jesperp) * Tolerate malformed closing boundary in multipart (Eugene Girshov) * Ignore preamble in multipart messages (Eugene Girshov) * Add support for application/json (Mike Frey, Carlos Rodriguez) * Add support for Base64 encoding (Elmer Bulthuis) * Add File#toJSON (TJ Holowaychuk) * Remove support for Node.js 0.4 & 0.6 (Andrew Kelley) * Documentation improvements (Sven Lito, Andre Azevedo) * Add support for application/octet-stream (Ion Lupascu, Chris Scribner) * Use os.tmpdir() to get tmp directory (Andrew Kelley) * Improve package.json (Andrew Kelley, Sven Lito) * Fix benchmark script (Andrew Kelley) * Fix scope issue in incoming_forms (Sven Lito) * Fix file handle leak on error (OrangeDog) --- [First commit, #3270eb4b1f8b (May 4th, 2010)](https://github.com/node-formidable/formidable/commit/3270eb4b1f8bb667b8c12f64c36a4e7b854216d8) ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2011-present Felix Geisendörfer, and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

npm formidable package logo

# formidable [![npm version][npmv-img]][npmv-url] [![MIT license][license-img]][license-url] [![Libera Manifesto][libera-manifesto-img]][libera-manifesto-url] [![Twitter][twitter-img]][twitter-url] > A Node.js module for parsing form data, especially file uploads. [![Code style][codestyle-img]][codestyle-url] [![linux build status][linux-build-img]][build-url] [![macos build status][macos-build-img]][build-url] If you have any _how-to_ kind of questions, please read the [Contributing Guide][contributing-url] and [Code of Conduct][code_of_conduct-url] documents.
For bugs reports and feature requests, [please create an issue][open-issue-url] or ping [@wgw_eth / @wgw_lol][twitter-url] at Twitter. [![Conventional Commits][ccommits-img]][ccommits-url] [![Minimum Required Nodejs][nodejs-img]][npmv-url] [![Buy me a Kofi][kofi-img]][kofi-url] [![Make A Pull Request][prs-welcome-img]][prs-welcome-url] This project is [semantically versioned](https://semver.org) and if you want support in migrating between versions you can schedule us for training or support us through donations, so we can prioritize. > [!CAUTION] > As of April 2025, old versions like v1 and v2 are still the most used, while they are deprecated for years -- they are also vulnerable to attacks if you are not implementing it properly. **Please upgrade!** We are here to help, and AI Editors & Agents could help a lot in such codemod-like migrations. > [!TIP] > If you are starting a fresh project, you can check out the `formidable-mini` which is a super minimal version of Formidable (not quite configurable yet, but when it does it could become the basis for `formidable@v4`), using web standards like FormData API and File API, and you can use it to stream uploads directly to S3 or other such services. [![][npm-weekly-img]][npmv-url] [![][npm-monthly-img]][npmv-url] [![][npm-yearly-img]][npmv-url] [![][npm-alltime-img]][npmv-url] ## Project Status: Maintained > [!NOTE] > Check [VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md) for more information on v1, v2, and v3 plans, NPM dist-tags and branches._ This module was initially developed by [**@felixge**](https://github.com/felixge) for [Transloadit](http://transloadit.com/), a service focused on uploading and encoding images and videos. It has been battle-tested against hundreds of GBs of file uploads from a large variety of clients and is considered production-ready and is used in production for years. Currently, we are few maintainers trying to deal with it. :) More contributors are always welcome! :heart: Jump on [issue #412](https://github.com/felixge/node-formidable/issues/412) which is closed, but if you are interested we can discuss it and add you after strict rules, like enabling Two-Factor Auth in your npm and GitHub accounts. ## Highlights - [Fast (~900-2500 mb/sec)](#benchmarks) & streaming multipart parser - Automatically writing file uploads to disk (optional, see [`options.fileWriteStreamHandler`](#options)) - [Plugins API](#useplugin-plugin) - allowing custom parsers and plugins - Low memory footprint - Graceful error handling - Very high test coverage ## Install This package is a dual ESM/commonjs package. > [!NOTE] > This project requires `Node.js >= 20`. Install it using [yarn](https://yarnpkg.com) or [npm](https://npmjs.com).
_We highly recommend to use Yarn when you think to contribute to this project._ This is a low-level package, and if you're using a high-level framework it _may_ already be included. Check the examples below and the [examples/](https://github.com/node-formidable/formidable/tree/master/examples) folder. ``` # v2 npm install formidable@v2 # v3 npm install formidable npm install formidable@v3 ``` _**Note:** Future not ready releases will be published on `*-next` dist-tags for the corresponding version._ ## Examples For more examples look at the `examples/` directory. ### with Node.js http module Parse an incoming file upload, with the [Node.js's built-in `http` module](https://nodejs.org/api/http.html). ```js import http from 'node:http'; import formidable, {errors as formidableErrors} from 'formidable'; const server = http.createServer(async (req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { // parse a file upload const form = formidable({}); let fields; let files; try { [fields, files] = await form.parse(req); } catch (err) { // example to check for a very specific error if (err.code === formidableErrors.maxFieldsExceeded) { } console.error(err); res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); res.end(String(err)); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ fields, files }, null, 2)); return; } // show a file upload form res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`

With Node.js "http" module

Text field title:
File:
`); }); server.listen(8080, () => { console.log('Server listening on http://localhost:8080/ ...'); }); ``` ### with Express.js There are multiple variants to do this, but Formidable just need Node.js Request stream, so something like the following example should work just fine, without any third-party [Express.js](https://ghub.now.sh/express) middleware. Or try the [examples/with-express.js](https://github.com/node-formidable/formidable/blob/master/examples/with-express.js) ```js import express from 'express'; import formidable from 'formidable'; const app = express(); app.get('/', (req, res) => { res.send(`

With "express" npm package

Text field title:
File:
`); }); app.post('/api/upload', (req, res, next) => { const form = formidable({}); form.parse(req, (err, fields, files) => { if (err) { next(err); return; } res.json({ fields, files }); }); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ``` ### with Koa and Formidable Of course, with [Koa v1, v2 or future v3](https://ghub.now.sh/koa) the things are very similar. You can use `formidable` manually as shown below or through the [koa-better-body](https://ghub.now.sh/koa-better-body) package which is using `formidable` under the hood and support more features and different request bodies, check its documentation for more info. _Note: this example is assuming Koa v2. Be aware that you should pass `ctx.req` which is Node.js's Request, and **NOT** the `ctx.request` which is Koa's Request object - there is a difference._ ```js import Koa from 'Koa'; import formidable from 'formidable'; const app = new Koa(); app.on('error', (err) => { console.error('server error', err); }); app.use(async (ctx, next) => { if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { const form = formidable({}); // not very elegant, but that's for now if you don't want to use `koa-better-body` // or other middlewares. await new Promise((resolve, reject) => { form.parse(ctx.req, (err, fields, files) => { if (err) { reject(err); return; } ctx.set('Content-Type', 'application/json'); ctx.status = 200; ctx.state = { fields, files }; ctx.body = JSON.stringify(ctx.state, null, 2); resolve(); }); }); await next(); return; } // show a file upload form ctx.set('Content-Type', 'text/html'); ctx.status = 200; ctx.body = `

With "koa" npm package

Text field title:
File:
`; }); app.use((ctx) => { console.log('The next middleware is called'); console.log('Results:', ctx.state); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ``` ## Benchmarks The benchmark is quite old, from the old codebase. But maybe quite true though. Previously the numbers was around ~500 mb/sec. Currently with moving to the new Node.js Streams API it's faster. You can clearly see the differences between the Node versions. _Note: a lot better benchmarking could and should be done in future._ Benchmarked on 8GB RAM, Xeon X3440 (2.53 GHz, 4 cores, 8 threads) ``` ~/github/node-formidable master ❯ nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js ⬢ Node 8 1261.08 mb/sec ⬢ Node 10 1113.04 mb/sec ⬢ Node 12 2107.00 mb/sec ⬢ Node 13 2566.42 mb/sec ``` ![benchmark January 29th, 2020](./benchmark/2020-01-29_xeon-x3440.png) ## API ### Formidable / IncomingForm All shown are equivalent. _Please pass [`options`](#options) to the function/constructor, not by assigning them to the instance `form`_ ```js import formidable from 'formidable'; const form = formidable(options); ``` ### Options See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js) (the `DEFAULT_OPTIONS` constant). - `options.encoding` **{string}** - default `'utf-8'`; sets encoding for incoming form fields, - `options.uploadDir` **{string}** - default `os.tmpdir()`; the directory for placing file uploads in. You can move them later by using `fs.rename()`. - `options.keepExtensions` **{boolean}** - default `false`; to include the extensions of the original files or not - `options.allowEmptyFiles` **{boolean}** - default `false`; allow upload empty files - `options.minFileSize` **{number}** - default `1` (1byte); the minium size of uploaded file. - `options.maxFiles` **{number}** - default `Infinity`; limit the amount of uploaded files, set Infinity for unlimited - `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb); limit the size of each uploaded file. - `options.maxTotalFileSize` **{number}** - default `options.maxFileSize`; limit the size of the batch of uploaded files. - `options.maxFields` **{number}** - default `1000`; limit the number of fields, set Infinity for unlimited - `options.maxFieldsSize` **{number}** - default `20 * 1024 * 1024` (20mb); limit the amount of memory all fields together (except files) can allocate in bytes. - `options.hashAlgorithm` **{string | false}** - default `false`; include checksums calculated for incoming files, set this to some hash algorithm, see [crypto.createHash](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options) for available algorithms - `options.fileWriteStreamHandler` **{function}** - default `null`, which by default writes to host machine file system every file parsed; The function should return an instance of a [Writable stream](https://nodejs.org/api/stream.html#stream_class_stream_writable) that will receive the uploaded file data. With this option, you can have any custom behavior regarding where the uploaded file data will be streamed for. If you are looking to write the file uploaded in other types of cloud storages (AWS S3, Azure blob storage, Google cloud storage) or private file storage, this is the option you're looking for. When this option is defined the default behavior of writing the file in the host machine file system is lost. - `options.filename` **{function}** - default `undefined` Use it to control newFilename. Must return a string. Will be joined with options.uploadDir. - `options.filter` **{function}** - default function that always returns true. Use it to filter files before they are uploaded. Must return a boolean. Will not make the form.parse error - `options.createDirsFromUploads` **{boolean}** - default false. If true, makes direct folder uploads possible. Use `` to create a form to upload folders. Has to be used with the options `options.uploadDir` and `options.filename` where `options.filename` has to return a string with the character `/` for folders to be created. The base will be `options.uploadDir`. #### `options.filename` **{function}** function (name, ext, part, form) -> string where part can be decomposed as ```js const { originalFilename, mimetype} = part; ``` _**Note:** If this size of combined fields, or size of some file is exceeded, an `'error'` event is fired._ ```js // The amount of bytes received for this form so far. form.bytesReceived; ``` ```js // The expected number of bytes in this form. form.bytesExpected; ``` #### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean Behaves like Array.filter: Returning false will simply ignore the file and go to the next. ```js const options = { filter: function ({name, originalFilename, mimetype}) { // keep only images return mimetype && mimetype.includes("image"); } }; ``` **Note:** use an outside variable to cancel all uploads upon the first error **Note:** use form.emit('error') to make form.parse error ```js let cancelUploads = false;// create variable at the same scope as form const options = { filter: function ({name, originalFilename, mimetype}) { // keep only images const valid = mimetype && mimetype.includes("image"); if (!valid) { form.emit('error', new formidableErrors.default('invalid type', 0, 400)); // optional make form.parse error cancelUploads = true; //variable to make filter return false after the first problem } return valid && !cancelUploads; } }; ``` ### .parse(request, ?callback) Parses an incoming Node.js `request` containing form data. If `callback` is not provided a promise is returned. ```js const form = formidable({ uploadDir: __dirname }); form.parse(req, (err, fields, files) => { console.log('fields:', fields); console.log('files:', files); }); // with Promise const [fields, files] = await form.parse(req); ``` You may overwrite this method if you are interested in directly accessing the multipart stream. Doing so will disable any `'field'` / `'file'` events processing which would occur otherwise, making you fully responsible for handling the processing. About `uploadDir`, given the following directory structure ``` project-name ├── src │ └── server.js │ └── uploads └── image.jpg ``` `__dirname` would be the same directory as the source file itself (src) ```js `${__dirname}/../uploads` ``` to put files in uploads. Omitting `__dirname` would make the path relative to the current working directory. This would be the same if server.js is launched from src but not project-name. `null` will use default which is `os.tmpdir()` Note: If the directory does not exist, the uploaded files are __silently discarded__. To make sure it exists: ```js import {createNecessaryDirectoriesSync} from "filesac"; const uploadPath = `${__dirname}/../uploads`; createNecessaryDirectoriesSync(`${uploadPath}/x`); ``` In the example below, we listen on couple of events and direct them to the `data` listener, so you can do whatever you choose there, based on whether its before the file been emitted, the header value, the header name, on field, on file and etc. Or the other way could be to just override the `form.onPart` as it's shown a bit later. ```js form.once('error', console.error); form.on('fileBegin', (formname, file) => { form.emit('data', { name: 'fileBegin', formname, value: file }); }); form.on('file', (formname, file) => { form.emit('data', { name: 'file', formname, value: file }); }); form.on('field', (fieldName, fieldValue) => { form.emit('data', { name: 'field', key: fieldName, value: fieldValue }); }); form.once('end', () => { console.log('Done!'); }); // If you want to customize whatever you want... form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => { if (name === 'partBegin') { } if (name === 'partData') { } if (name === 'headerField') { } if (name === 'headerValue') { } if (name === 'headerEnd') { } if (name === 'headersEnd') { } if (name === 'field') { console.log('field name:', key); console.log('field value:', value); } if (name === 'file') { console.log('file:', formname, value); } if (name === 'fileBegin') { console.log('fileBegin:', formname, value); } }); ``` ### .use(plugin: Plugin) A method that allows you to extend the Formidable library. By default we include 4 plugins, which essentially are adapters to plug the different built-in parsers. **The plugins added by this method are always enabled.** _See [src/plugins/](./src/plugins/) for more detailed look on default plugins._ The `plugin` param has such signature: ```typescript function(formidable: Formidable, options: Options): void; ``` The architecture is simple. The `plugin` is a function that is passed with the Formidable instance (the `form` across the README examples) and the options. **Note:** the plugin function's `this` context is also the same instance. ```js const form = formidable({ keepExtensions: true }); form.use((self, options) => { // self === this === form console.log('woohoo, custom plugin'); // do your stuff; check `src/plugins` for inspiration }); form.parse(req, (error, fields, files) => { console.log('done!'); }); ``` **Important to note**, is that inside plugin `this.options`, `self.options` and `options` MAY or MAY NOT be the same. General best practice is to always use the `this`, so you can later test your plugin independently and more easily. If you want to disable some parsing capabilities of Formidable, you can disable the plugin which corresponds to the parser. For example, if you want to disable multipart parsing (so the [src/parsers/Multipart.js](./src/parsers/Multipart.js) which is used in [src/plugins/multipart.js](./src/plugins/multipart.js)), then you can remove it from the `options.enabledPlugins`, like so ```js import formidable, {octetstream, querystring, json} from "formidable"; const form = formidable({ hashAlgorithm: 'sha1', enabledPlugins: [octetstream, querystring, json], }); ``` **Be aware** that the order _MAY_ be important too. The names corresponds 1:1 to files in [src/plugins/](./src/plugins) folder. Pull requests for new built-in plugins MAY be accepted - for example, more advanced querystring parser. Add your plugin as a new file in `src/plugins/` folder (lowercased) and follow how the other plugins are made. ### form.onPart If you want to use Formidable to only handle certain parts for you, you can do something similar. Or see [#387](https://github.com/node-formidable/node-formidable/issues/387) for inspiration, you can for example validate the mime-type. ```js const form = formidable(); form.onPart = (part) => { part.on('data', (buffer) => { // do whatever you want here }); }; ``` For example, force Formidable to be used only on non-file "parts" (i.e., html fields) ```js const form = formidable(); form.onPart = function (part) { // let formidable handle only non-file parts if (part.originalFilename === '' || !part.mimetype) { // used internally, please do not override! form._handlePart(part); } }; ``` ### File ```ts export interface File { // The size of the uploaded file in bytes. // If the file is still being uploaded (see `'fileBegin'` event), // this property says how many bytes of the file have been written to disk yet. file.size: number; // The path this file is being written to. You can modify this in the `'fileBegin'` event in // case you are unhappy with the way formidable generates a temporary path for your files. file.filepath: string; // The name this file had according to the uploading client. file.originalFilename: string | null; // calculated based on options provided file.newFilename: string | null; // The mime type of this file, according to the uploading client. file.mimetype: string | null; // A Date object (or `null`) containing the time this file was last written to. // Mostly here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/). file.mtime: Date | null; file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256' // If `options.hashAlgorithm` calculation was set, you can read the hex digest out of this var (at the end it will be a string) file.hash: string | object | null; } ``` #### file.toJSON() This method returns a JSON-representation of the file, allowing you to `JSON.stringify()` the file which is useful for logging and responding to requests. ### Events #### `'progress'` Emitted after each incoming chunk of data that has been parsed. Can be used to roll your own progress bar. **Warning** Use this only for server side progress bar. On the client side better use `XMLHttpRequest` with `xhr.upload.onprogress =` ```js form.on('progress', (bytesReceived, bytesExpected) => {}); ``` #### `'field'` Emitted whenever a field / value pair has been received. ```js form.on('field', (name, value) => {}); ``` #### `'fileBegin'` Emitted whenever a new file is detected in the upload stream. Use this event if you want to stream the file to somewhere else while buffering the upload on the file system. ```js form.on('fileBegin', (formName, file) => { // accessible here // formName the name in the form () or http filename for octetstream // file.originalFilename http filename or null if there was a parsing error // file.newFilename generated hexoid or what options.filename returned // file.filepath default pathname as per options.uploadDir and options.filename // file.filepath = CUSTOM_PATH // to change the final path }); ``` #### `'file'` Emitted whenever a field / file pair has been received. `file` is an instance of `File`. ```js form.on('file', (formname, file) => { // same as fileBegin, except // it is too late to change file.filepath // file.hash is available if options.hash was used }); ``` #### `'error'` Emitted when there is an error processing the incoming form. A request that experiences an error is automatically paused, you will have to manually call `request.resume()` if you want the request to continue firing `'data'` events. May have `error.httpCode` and `error.code` attached. ```js form.on('error', (err) => {}); ``` #### `'aborted'` Emitted when the request was aborted by the user. Right now this can be due to a 'timeout' or 'close' event on the socket. After this event is emitted, an `error` event will follow. In the future there will be a separate 'timeout' event (needs a change in the node core). ```js form.on('aborted', () => {}); ``` #### `'end'` Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response. ```js form.on('end', () => {}); ``` ### Helpers #### firstValues Gets first values of fields, like pre 3.0.0 without multiples pass in a list of optional exceptions where arrays of strings is still wanted (`
File:
`); }); server.listen(8080, () => { console.log('Server listening on http://localhost:8080/ ...'); }); ``` ### com Express.js Existem várias variantes para fazer isso, mas o Formidable só precisa do Node.js Request stream, então algo como o exemplo a seguir deve funcionar bem, sem nenhum middleware [Express.js](https://ghub.now.sh/express) de terceiros. Ou tente o [examples/with-express.js](https://github.com/node-formidable/formidable/blob/master/examples/with-express.js) ```js import express from 'express'; import formidable from 'formidable'; const app = express(); app.get('/', (req, res) => { res.send(`

With "express" npm package

Text field title:
File:
`); }); app.post('/api/upload', (req, res, next) => { const form = formidable({}); form.parse(req, (err, fields, files) => { if (err) { next(err); return; } res.json({ fields, files }); }); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ``` ### com Koa e Formidable Claro, com [Koa v1, v2 ou future v3](https://ghub.now.sh/koa) as coisas sao muito parecidas. Você pode usar `formidable` manualmente como mostrado abaixo ou através do pacote [koa-better-body](https://ghub.now.sh/koa-better-body) que é usando `formidable` sob o capô e suporte a mais recursos e diferentes corpos de solicitação, verifique sua documentação para mais informações. _Nota: este exemplo está assumindo Koa v2. Esteja ciente de que você deve passar `ctx.req` que é a solicitação do Node.js e **NÃO** o `ctx.request` que é a solicitação do Koa objeto - há uma diferença._ ```js import Koa from 'Koa'; import formidable from 'formidable'; const app = new Koa(); app.on('error', (err) => { console.error('server error', err); }); app.use(async (ctx, next) => { if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { const form = formidable({}); // não muito elegante, mas é por enquanto se você não quiser usar `koa-better-body` // ou outros middlewares. await new Promise((resolve, reject) => { form.parse(ctx.req, (err, fields, files) => { if (err) { reject(err); return; } ctx.set('Content-Type', 'application/json'); ctx.status = 200; ctx.state = { fields, files }; ctx.body = JSON.stringify(ctx.state, null, 2); resolve(); }); }); await next(); return; } // mostrar um formulário de upload de arquivo ctx.set('Content-Type', 'text/html'); ctx.status = 200; ctx.body = `

With "koa" npm package

Text field title:
File:
`; }); app.use((ctx) => { console.log('The next middleware is called'); console.log('Results:', ctx.state); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ``` ## Benchmarks O benchmark é bastante antigo, da antiga base de código. Mas talvez seja bem verdade. Anteriormente, os números giravam em torno de ~ 500 mb/s. Atualmente com a mudança para o novo Node.js Streams API, é mais rápido. Você pode ver claramente as diferenças entre as versões do Node. _Observação: um benchmarking muito melhor pode e deve ser feito no futuro._ Benchmark realizado em 8 GB de RAM, Xeon X3440 (2,53 GHz, 4 núcleos, 8 threads) ``` ~/github/node-formidable master ❯ nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js ⬢ Node 8 1261.08 mb/sec ⬢ Node 10 1113.04 mb/sec ⬢ Node 12 2107.00 mb/sec ⬢ Node 13 2566.42 mb/sec ``` ![benchmark 29 de janeiro de 2020](./benchmark/2020-01-29_xeon-x3440.png) ## API ### Formidable / IncomingForm Todos os mostrados são equivalentes. _Por favor, passe [`options`](#options) para a função/construtor, não atribuindo eles para a instância `form`_ ```js import formidable from 'formidable'; const form = formidable(options); ``` ### Opções Veja seus padrões em [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js) (a constante `DEFAULT_OPTIONS`). - `options.encoding` **{string}** - padrão `'utf-8'`; define a codificação para campos de formulário de entrada, - `options.uploadDir` **{string}** - padrão `os.tmpdir()`; o diretório para colocar os uploads de arquivos. Você pode movê-los mais tarde usando `fs.rename()`. - `options.keepExtensions` **{boolean}** - padrão `false`; incluir as extensões dos arquivos originais ou não - `options.allowEmptyFiles` **{boolean}** - padrão `false`; permitir upload de arquivos vazios - `options.minFileSize` **{number}** - padrão `1` (1byte); o tamanho mínimo do arquivo carregado. - `options.maxFiles` **{number}** - padrão `Infinity`; limitar a quantidade de arquivos carregados, defina Infinity para ilimitado - `options.maxFileSize` **{number}** - padrão `200 * 1024 * 1024` (200mb); limitar o tamanho de cada arquivo carregado. - `options.maxTotalFileSize` **{number}** - padrão `options.maxFileSize`; limitar o tamanho do lote de arquivos carregados. - `options.maxFields` **{number}** - padrão `1000`; limite o número de campos, defina Infinity para ilimitado - `options.maxFieldsSize` **{number}** - padrão `20 * 1024 * 1024` (20mb); limitar a quantidade de memória que todos os campos juntos (exceto arquivos) podem alocar em bytes. - `options.hashAlgorithm` **{string | false}** - padrão `false`; incluir checksums calculados para arquivos recebidos, defina isso para algum algoritmo de hash, consulte [crypto.createHash](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options) para algoritmos disponíveis - `options.fileWriteStreamHandler` **{function}** - padrão `null`, que por padrão grava no sistema de arquivos da máquina host cada arquivo analisado; A função deve retornar uma instância de um [fluxo gravável](https://nodejs.org/api/stream.html#stream_class_stream_writable) que receberá os dados do arquivo carregado. Com esta opção, você pode ter qualquer comportamento personalizado em relação a onde os dados do arquivo carregado serão transmitidos. Se você deseja gravar o arquivo carregado em outros tipos de armazenamento em nuvem (AWS S3, armazenamento de blob do Azure, armazenamento em nuvem do Google) ou armazenamento de arquivo privado, esta é a opção que você está procurando. Quando esta opção é definida, o comportamento padrão de gravar o arquivo no sistema de arquivos da máquina host é perdido. - `options.filename` **{function}** - padrão `undefined` Use-o para controlar newFilename. Deve retornar uma string. Será associado a options.uploadDir. - `options.filter` **{function}** - função padrão que sempre retorna verdadeiro. Use-o para filtrar arquivos antes de serem carregados. Deve retornar um booleano. #### `options.filename` **{function}** function (name, ext, part, form) -> string onde a parte pode ser decomposta como ```js const { originalFilename, mimetype} = part; ``` _**Observação:** Se este tamanho de campos combinados, ou tamanho de algum arquivo for excedido, um O evento `'error'` é disparado._ ```js // A quantidade de bytes recebidos para este formulário até agora. form.bytesReceived; ``` ```js // O número esperado de bytes neste formulário. form.bytesExpected; ``` #### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean **Observação:** use uma variável externa para cancelar todos os uploads no primeiro erro ```js const options = { filter: function ({name, originalFilename, mimetype}) { // manter apenas imagens return mimetype && mimetype.includes("image"); } }; ``` ### .parse(request, callback) Analisa uma `request` do Node.js recebida contendo dados de formulário. Se `callback` for fornecido, todos os campos e arquivos são coletados e passados para o retorno de chamada. ```js const form = formidable({ uploadDir: __dirname }); form.parse(req, (err, fields, files) => { console.log('fields:', fields); console.log('files:', files); }); ``` Você pode substituir esse método se estiver interessado em acessar diretamente o fluxo de várias partes. Fazer isso desativará qualquer processamento de eventos `'field'` / `'file'` que ocorreria de outra forma, tornando você totalmente responsável por lidar com o processamento. Sobre `uploadDir`, dada a seguinte estrutura de diretório ``` project-name ├── src │ └── server.js │ └── uploads └── image.jpg ``` `__dirname` seria o mesmo diretório que o próprio arquivo de origem (src) ```js `${__dirname}/../uploads` ``` para colocar arquivos em uploads. Omitir `__dirname` tornaria o caminho relativo ao diretório de trabalho atual. Isso seria o mesmo se server.js fosse iniciado a partir de src, mas não de project-name. `null` usará o padrão que é `os.tmpdir()` Nota: Se o diretório não existir, os arquivos carregados são __silenciosamente descartados__. Para ter certeza de que existe: ```js import {createNecessaryDirectoriesSync} from "filesac"; const uploadPath = `${__dirname}/../uploads`; createNecessaryDirectoriesSync(`${uploadPath}/x`); ``` No exemplo abaixo, escutamos alguns eventos e os direcionamos para o ouvinte `data`, para que você possa fazer o que quiser lá, com base em se é antes do arquivo ser emitido, o valor do cabeçalho, o nome do cabeçalho, no campo , em arquivo e etc. Ou a outra maneira poderia ser apenas substituir o `form.onPart` como é mostrado um pouco mais tarde. ```js form.once('error', console.error); form.on('fileBegin', (formname, file) => { form.emit('data', { name: 'fileBegin', formname, value: file }); }); form.on('file', (formname, file) => { form.emit('data', { name: 'file', formname, value: file }); }); form.on('field', (fieldName, fieldValue) => { form.emit('data', { name: 'field', key: fieldName, value: fieldValue }); }); form.once('end', () => { console.log('Done!'); }); // Se você quiser personalizar o que quiser... form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => { if (name === 'partBegin') { } if (name === 'partData') { } if (name === 'headerField') { } if (name === 'headerValue') { } if (name === 'headerEnd') { } if (name === 'headersEnd') { } if (name === 'field') { console.log('field name:', key); console.log('field value:', value); } if (name === 'file') { console.log('file:', formname, value); } if (name === 'fileBegin') { console.log('fileBegin:', formname, value); } }); ``` ### .use(plugin: Plugin) Um método que permite estender a biblioteca Formidable. Por padrão, incluímos 4 plug-ins, que são essencialmente adaptadores para conectar os diferentes analisadores integrados. **Os plugins adicionados por este método estão sempre ativados.** _Consulte [src/plugins/](./src/plugins/) para uma visão mais detalhada dos plug-ins padrão._ O parâmetro `plugin` tem essa assinatura: ```typescript function(formidable: Formidable, options: Options): void; ``` A arquitetura é simples. O `plugin` é uma função que é passada com a instância Formidable (o `form` nos exemplos README) e as opções. **Observação:** o contexto `this` da função do plug-in também é a mesma instância. ```js const form = formidable({ keepExtensions: true }); form.use((self, options) => { // self === this === form console.log('woohoo, custom plugin'); // faça suas coisas; verifique `src/plugins` para inspiração }); form.parse(req, (error, fields, files) => { console.log('done!'); }); ``` **Importante observar**, é que dentro do plugin `this.options`, `self.options` e `options` PODEM ou NÃO ser iguais. A melhor prática geral é sempre usar o `this`, para que você possa testar seu plugin mais tarde de forma independente e mais fácil. Se você quiser desabilitar alguns recursos de análise do Formidable, você pode desabilitar o plugin que corresponde ao analisador. Por exemplo, se você deseja desabilitar a análise de várias partes (para que o [src/parsers/Multipart.js](./src/parsers/Multipart.js) que é usado em [src/plugins/multipart.js](./src/plugins/multipart.js)), então você pode removê-lo do `options.enabledPlugins`, assim ```js import formidable, {octetstream, querystring, json} from "formidable"; const form = formidable({ hashAlgorithm: 'sha1', enabledPlugins: [octetstream, querystring, json], }); ``` **Esteja ciente** de que a ordem _PODE_ ser importante também. Os nomes correspondem 1:1 a arquivos na pasta [src/plugins/](./src/plugins). Solicitações pull para novos plug-ins integrados PODEM ser aceitas - por exemplo, analisador de querystring mais avançado. Adicione seu plugin como um novo arquivo na pasta `src/plugins/` (em letras minúsculas) e siga como os outros plugins são feitos. ### form.onPart Se você quiser usar Formidable para manipular apenas algumas partes para você, você pode fazer alguma coisa similar. ou ver [#387](https://github.com/node-formidable/node-formidable/issues/387) para inspiração, você pode, por exemplo, validar o tipo mime. ```js const form = formidable(); form.onPart = (part) => { part.on('data', (buffer) => { // faça o que quiser aqui }); }; ``` Por exemplo, force Formidable a ser usado apenas em "partes" que não sejam de arquivo (ou seja, html Campos) ```js const form = formidable(); form.onPart = function (part) { // deixe formidável lidar apenas com partes não arquivadas if (part.originalFilename === '' || !part.mimetype) { // usado internamente, por favor, não substitua! form._handlePart(part); } }; ``` ### Arquivo ```ts export interface File { // O tamanho do arquivo enviado em bytes. // Se o arquivo ainda estiver sendo carregado (veja o evento `'fileBegin'`), // esta propriedade diz quantos bytes do arquivo já foram gravados no disco. file.size: number; // O caminho em que este arquivo está sendo gravado. Você pode modificar isso no evento `'fileBegin'` // caso você esteja insatisfeito com a forma como o formidable gera um caminho temporário para seus arquivos. file.filepath: string; // O nome que este arquivo tinha de acordo com o cliente de upload. file.originalFilename: string | null; // calculado com base nas opções fornecidas. file.newFilename: string | null; // O tipo mime deste arquivo, de acordo com o cliente de upload. file.mimetype: string | null; // Um objeto Date (ou `null`) contendo a hora em que este arquivo foi gravado pela última vez. // Principalmente aqui para compatibilidade com o [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/). file.mtime: Date | null; file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256' // Se o cálculo `options.hashAlgorithm` foi definido, você pode ler o resumo hexadecimal desta var (no final, será uma string) file.hash: string | object | null; } ``` #### file.toJSON() Este método retorna uma representação JSON do arquivo, permitindo que você `JSON.stringify()` o arquivo que é útil para registrar e responder a solicitações. ### Eventos #### `'progress'` Emitido após cada bloco de entrada de dados que foi analisado. Pode ser usado para rolar sua própria barra de progresso. **Aviso** Use isso apenas para a barra de progresso do lado do servidor. No lado do cliente, é melhor usar `XMLHttpRequest` com `xhr.upload.onprogress =` ```js form.on('progress', (bytesReceived, bytesExpected) => {}); ``` #### `'field'` Emitido sempre que um par campo/valor é recebido. ```js form.on('field', (name, value) => {}); ``` #### `'fileBegin'` Emitido sempre que um novo arquivo é detectado no fluxo de upload. Use este evento se desejar transmitir o arquivo para outro lugar enquanto armazena o upload no sistema de arquivos. ```js form.on('fileBegin', (formName, file) => { // acessível aqui // formName o nome no formulário () ou http filename para octetstream // file.originalFilename http filename ou null se houver um erro de análise // file.newFilename gerou hexoid ou o que options.filename retornou // file.filepath nome do caminho padrão de acordo com options.uploadDir e options.filename // file.filepath = CUSTOM_PATH // para alterar o caminho final }); ``` #### `'file'` Emitido sempre que um par campo/arquivo é recebido. `file` é uma instância de `File`. ```js form.on('file', (formname, file) => { // o mesmo que fileBegin, exceto // é muito tarde para alterar file.filepath // file.hash está disponível se options.hash foi usado }); ``` #### `'error'` Emitido quando há um erro no processamento do formulário recebido. Uma solicitação que apresenta um erro é pausada automaticamente, você terá que chamar manualmente `request.resume()` se você quiser que a requisição continue disparando eventos `'data'`. Pode ter `error.httpCode` e `error.code` anexados. ```js form.on('error', (err) => {}); ``` #### `'aborted'` Emitido quando a requisição foi abortada pelo usuário. Agora isso pode ser devido a um evento 'timeout' ou 'close' no soquete. Após este evento ser emitido, um O evento `error` seguirá. No futuro, haverá um 'timeout' separado evento (precisa de uma mudança no núcleo do nó). ```js form.on('aborted', () => {}); ``` #### `'end'` Emitido quando toda a solicitação foi recebida e todos os arquivos contidos foram liberados para o disco. Este é um ótimo lugar para você enviar sua resposta. ```js form.on('end', () => {}); ``` ### Helpers #### firstValues Obtém os primeiros valores dos campos, como pré 3.0.0 sem passar múltiplos em uma lista de exceções opcionais onde arrays de strings ainda são desejados (`
File:
`); }); // use middleware app.post('/api/upload', formMiddleWare, (req, res, next) => { res.json({ fields: req.fields, files: req.files, }); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/forceBuffer.js ================================================ // warning: forcing file into a Buffer elminates the benefits of using streams and may cause memory overflow import http from 'node:http'; import { Buffer } from 'node:buffer' import { Writable } from 'node:stream'; import formidable from '../src/index.js'; const server = http.createServer((req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { // parse a file upload const endBuffers = {}; const form = formidable({ fileWriteStreamHandler: (file) => { const chunks = []; const writable = new Writable({ write (chunk, enc, next) { chunks.push(chunk); next(); }, destroy() { endBuffers = {}; }, final(cb) { const buffer = Buffer.concat(chunks); // if filename option is not provided file.newFilename will be a random string endBuffers[file.newFilename] = buffer; cb(); }, }) return writable; }, }); form.parse(req, (err, fields, files) => { // available here endBuffers if (err) { console.error(err); res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); res.end(String(err)); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ fields, files }, null, 2)); Object.entries(endBuffers).map(([key, value]) => { console.log(key); console.log(value.toString("utf8")); }); }); return; } // show a file upload form res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`

With Node.js "http" module

Text field title:
File:
`); }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/json.js ================================================ import http from 'node:http'; import util from 'node:util'; import formidable from '../src/index.js'; const PORT = 3000; const server = http.createServer((req, res) => { if (req.method !== 'POST') { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`Please POST a JSON payload to http://localhost:${PORT}/`); return; } const form = formidable(); const fields = {}; form .on('error', (err) => { console.error(err); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(`error:\n\n${util.inspect(err)}`); }) .on('field', (field, value) => { console.log(field, value); fields[field] = value; }) .on('end', () => { console.log('-> post done from "end" event'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`received fields:\n\n${util.inspect(fields)}`); }); form.parse(req); }); server.listen(PORT, () => { const chosenPort = server.address().port; console.log(`Listening on http://localhost:${chosenPort}/`); const body = JSON.stringify({ numbers: [1, 2, 3, 4, 5], nested: { key: 'some val' }, }); const request = http.request( { host: 'localhost', path: '/', port: chosenPort, method: 'POST', headers: { 'Content-Type': 'application/json', 'content-length': body.length, }, }, (response) => { console.log('\nServer responded with:'); console.log('Status:', response.statusCode); response.pipe(process.stdout); response.on('end', () => { console.log('\n'); process.exit(); }); // const data = ''; // response.on('data', function(chunk) { // data += chunk.toString('utf8'); // }); // response.on('end', function() { // console.log('Response Data:'); // console.log(data); // process.exit(); // }); }, ); request.end(body); }); ================================================ FILE: examples/log-file-content-to-console.js ================================================ import http from 'node:http'; import { Writable } from 'node:stream'; import formidable from '../src/index.js'; const server = http.createServer((req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { // parse a file upload const form = formidable({ fileWriteStreamHandler: (/* file */) => { const writable = Writable(); // eslint-disable-next-line no-underscore-dangle writable._write = (chunk, enc, next) => { console.log(chunk.toString()); next(); }; return writable; }, }); form.parse(req, () => { res.writeHead(200); res.end(); }); return; } // show a file upload form res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`

With Node.js "http" module

Text field title:
File:
`); }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/multipart-parser.js ================================================ import { Blob } from 'node:buffer'; import { Readable } from 'node:stream'; import { FormData, formDataToBlob } from 'formdata-polyfill/esm.min.js' import { MultipartParser } from '../src/index.js'; const blob1 = new Blob( ['Content of a.txt.'], { type: 'text/plain' } ); const blob2 = new Blob( ['Content of a.html.'], { type: 'text/html' } ); const fd = new FormData(); fd.set('text', 'some text ...'); fd.set('z', 'text inside z'); fd.set('file1', blob1, 'a.txt'); fd.set('file2', blob2, 'a.html'); const multipartParser = new MultipartParser(); multipartParser.on('data', ({ name, buffer, start, end }) => { console.log(`${name}:`); if (buffer && start && end) { console.log(String(buffer.slice(start, end))); } console.log(); }); multipartParser.on('error', console.error); const blob = formDataToBlob(fd); const boundary = blob.type.split('boundary=')[1]; multipartParser.initWithBoundary(boundary); Readable.from(blob.stream()).pipe(multipartParser); ================================================ FILE: examples/multiples.js ================================================ import http from 'node:http'; import os from 'node:os'; import formidable from '../src/index.js'; const server = http.createServer((req, res) => { if (req.url === '/') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`










`); } else if (req.url === '/upload') { const form = formidable({ uploadDir: os.tmpdir() }); form.parse(req, (err, fields, files) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ err, fields, files }, null, 2)); }); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('404'); } }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/store-files-on-s3.js ================================================ // To test this example you have to install aws-sdk nodejs package and create a bucket named "demo-bucket" import http from 'node:http'; import { PassThrough } from 'node:stream'; import AWS from 'aws-sdk'; import formidable from '../src/index.js'; const s3Client = new AWS.S3({ credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_KEY, }, }); const uploadStream = (file) => { const pass = new PassThrough(); s3Client.upload( { Bucket: 'demo-bucket', Key: file.newFilename, Body: pass, }, (err, data) => { console.log(err, data); }, ); return pass; }; const server = http.createServer((req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { // parse a file upload const form = formidable({ fileWriteStreamHandler: uploadStream, }); form.parse(req, () => { res.writeHead(200); res.end(); }); return; } // show a file upload form res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`

With Node.js "http" module

Text field title:
File:
`); }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/upload-multiple-files.js ================================================ import http from 'node:http'; import util from 'node:util'; import os from 'node:os'; import formidable from '../src/index.js'; const server = http.createServer((req, res) => { if (req.url === '/') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`


`); } else if (req.url === '/upload') { const form = formidable({ uploadDir: os.tmpdir() }); const files = []; const fields = []; form .on('field', (fieldName, value) => { console.log(fieldName, value); fields.push({ fieldName, value }); }) .on('file', (fieldName, file) => { console.log(fieldName, file); files.push({ fieldName, file }); }) .on('end', () => { console.log('-> upload done'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write(`received fields:\n\n${util.inspect(fields)}`); res.write('\n\n'); res.end(`received files:\n\n${util.inspect(files)}`); }); form.parse(req); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('404'); } }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/urlencoded-no-enctype.js ================================================ import http from 'node:http'; import util from 'node:util'; import formidable from '../src/index.js'; const server = http.createServer((req, res) => { if (req.url === '/') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`
Title:
Data:
`); } else if (req.url === '/post') { const form = formidable(); const fields = []; form .on('error', (err) => { console.log('err!', err); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`error:\n\n${util.inspect(err)}`); }) .on('field', (fieldName, fieldValue) => { console.log('fieldName:', fieldName); console.log('fieldValue:', fieldValue); fields.push({ fieldName, fieldValue }); }) .on('end', () => { console.log('-> post done from "end" event'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`received fields:\n\n${util.inspect(fields)}`); }); form.parse(req, () => { console.log('-> post done from callback'); // res.writeHead(200, { 'Content-Type': 'text/plain' }); // res.end(`received fields:\n\n${util.inspect(fields)}`); }); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('404'); } }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/with-express.js ================================================ import express from 'express'; import formidable from '../src/index.js'; const app = express(); app.get('/', (req, res) => { res.send(`

With "express" npm package

Text field title:
File:
`); }); app.post('/api/upload', (req, res, next) => { const form = formidable({ }); form.parse(req, (err, fields, files) => { if (err) { next(err); return; } res.json({ fields, files }); }); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/with-http.js ================================================ import http from 'node:http'; import slugify from '@sindresorhus/slugify'; import formidable, {errors as formidableErrors} from '../src/index.js'; const server = http.createServer((req, res) => { // handle common internet errors // to avoid server crash req.on('error', console.error); res.on('error', console.error); if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { // parse a file upload const form = formidable({ defaultInvalidName: 'invalid', uploadDir: `uploads`, keepExtensions: true, createDirsFromUploads: true, allowEmptyFiles: true, minFileSize: 0, filename(name, ext, part, form) { /* name basename of the http originalFilename ext with the dot ".txt" only if keepExtensions is true */ // originalFilename will have slashes with relative path if a // directory was uploaded const {originalFilename} = part; if (!originalFilename) { return 'invalid'; } // return 'yo.txt'; // or completly different name // return 'z/yo.txt'; // subdirectory return originalFilename.split("/").map((subdir) => { return slugify(subdir, {separator: ''}); // slugify to avoid invalid filenames }).join("/").substr(0, 100); // substr to define a maximum }, filter: function ({name, originalFilename, mimetype}) { return Boolean(originalFilename); // keep only images // return mimetype?.includes("image"); } // maxTotalFileSize: 4000, // maxFileSize: 1000, }); form.parse(req, (err, fields, files) => { if (err) { console.error(err); res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); res.end(String(err)); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ fields, files }, null, 2)); }); return; } // else show a file upload form res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`

With Node.js "http" module

Text field title:
File:
Folders:
Text field title:
Text field with same name:
Other field
`); }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: examples/with-koa2.js ================================================ import Koa from 'koa'; import formidable from '../src/index.js'; const app = new Koa(); app.on('error', (err) => { console.error('server error', err); }); app.use(async (ctx, next) => { if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { let i = 0; const form = formidable({ keepExtensions: true, // must return absolute path filename: (part, $self) => { i += 1; return `${$self.uploadDir}/sasasa${i}`; }, }); // not very elegant, but that's for now if you don't want touse `koa-better-body` // or other middlewares. await new Promise((resolve, reject) => { form.parse(ctx.req, (err, fields, files) => { if (err) { reject(err); return; } ctx.set('Content-Type', 'application/json'); ctx.status = 200; ctx.state = { fields, files }; ctx.body = JSON.stringify(ctx.state, null, 2); resolve(); }); }); await next(); return; } // show a file upload form ctx.set('Content-Type', 'text/html'); ctx.status = 200; ctx.body = `

With "koa" npm package

Text field title:
File:
`; }); app.use((ctx) => { console.log('The next middleware is called'); console.log('Results:', ctx.state); }); app.listen(3000, () => { console.log('Server listening on http://localhost:3000 ...'); }); ================================================ FILE: nyc.config.js ================================================ 'use strict'; module.exports = { statements: 70, branches: 70, functions: 70, lines: 70, 'check-coverage': true, exclude: ['test'], include: ['src'], reporter: ['text', 'text-summary', 'lcov', 'clover'], }; ================================================ FILE: package.json ================================================ { "name": "formidable", "version": "3.5.4", "license": "MIT", "description": "A node.js module for parsing form data, especially file uploads.", "homepage": "https://github.com/node-formidable/formidable", "funding": "https://ko-fi.com/tunnckoCore/commissions", "repository": "node-formidable/formidable", "type": "module", "main": "./dist/index.cjs", "exports": { ".": { "import": { "default": "./src/index.js" }, "require": { "default": "./dist/index.cjs" }, "default": "./dist/index.cjs" }, "./src/helpers/*.js": { "import": { "default": "./src/helpers/*.js" }, "require": { "default": "./dist/helpers/*.cjs" } }, "./src/parsers/*.js": { "import": { "default": "./src/parsers/*.js" }, "require": { "default": "./dist/index.cjs" } } }, "files": [ "src", "./dist", "./CHANGELOG", "./README.md", "./README_pt_BR.md" ], "publishConfig": { "access": "public", "tag": "latest" }, "scripts": { "build-package": "rollup --config ./tool/rollup.config.js", "prepublishOnly": "pnpm run build-package", "bench": "node benchmark", "bench2prep": "node benchmark/server.js", "bench2": "bombardier --body-file=\"./README.md\" --method=POST --duration=10s --connections=100 http://localhost:3000/api/upload", "fmt": "pnpm run fmt:prepare '**/*'", "fmt:prepare": "prettier --write", "lint": "pnpm run lint:prepare .", "lint:prepare": "eslint --cache --fix --quiet --format codeframe", "fresh": "rm -rf ./node_modules", "test-specific": "node --disable-warning=ExperimentalWarning --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/standalone/keep-alive-error.test.js", "test-jest": "node --disable-warning=ExperimentalWarning --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/ --coverage", "test-node": "node --disable-warning=ExperimentalWarning --test ./test-node/**/*.test.js", "test-jest:ci": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/ --coverage", "test:local": "pnpm run test-node && pnpm run test-jest", "audit": "pnpm audit --prod --fix", "pretest": "rm -rf ./test/tmp && mkdir ./test/tmp", "test": "pnpm run audit && node --test ./test-node/**/*.test.js && pnpm run test-jest:ci" }, "dependencies": { "@paralleldrive/cuid2": "2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" }, "packageManager": "pnpm@10.8.1", "devDependencies": { "@rollup/plugin-commonjs": "^25.0.2", "@rollup/plugin-node-resolve": "^15.1.0", "@sindresorhus/slugify": "^2.1.0", "@tunnckocore/prettier-config": "1.3.8", "eslint": "6.8.0", "eslint-config-airbnb-base": "14.1.0", "eslint-config-prettier": "6.11.0", "eslint-plugin-import": "2.20.2", "eslint-plugin-prettier": "3.1.3", "express": "^4.21.1", "formdata-polyfill": "^4.0.10", "jest": "27.2.4", "koa": "2.16.1", "nyc": "15.1.0", "prettier": "2.0.5", "prettier-plugin-pkgjson": "0.2.8", "rollup": "^3.25.3", "supertest": "6.1.6" }, "engines": { "node": ">=14.0.0" }, "jest": { "verbose": true }, "keywords": [ "multipart", "form", "data", "querystring", "www", "json", "ulpoad", "file" ] } ================================================ FILE: pnpm-lock.yaml ================================================ lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: .: dependencies: '@paralleldrive/cuid2': specifier: 2.2.2 version: 2.2.2 dezalgo: specifier: ^1.0.4 version: 1.0.4 once: specifier: ^1.4.0 version: 1.4.0 devDependencies: '@rollup/plugin-commonjs': specifier: ^25.0.2 version: 25.0.8(rollup@3.29.5) '@rollup/plugin-node-resolve': specifier: ^15.1.0 version: 15.3.1(rollup@3.29.5) '@sindresorhus/slugify': specifier: ^2.1.0 version: 2.2.1 '@tunnckocore/prettier-config': specifier: 1.3.8 version: 1.3.8(prettier-plugin-pkgjson@0.2.8(prettier@2.0.5))(prettier@2.0.5) eslint: specifier: 6.8.0 version: 6.8.0 eslint-config-airbnb-base: specifier: 14.1.0 version: 14.1.0(eslint-plugin-import@2.20.2(eslint@6.8.0))(eslint@6.8.0) eslint-config-prettier: specifier: 6.11.0 version: 6.11.0(eslint@6.8.0) eslint-plugin-import: specifier: 2.20.2 version: 2.20.2(eslint@6.8.0) eslint-plugin-prettier: specifier: 3.1.3 version: 3.1.3(eslint@6.8.0)(prettier@2.0.5) express: specifier: ^4.21.1 version: 4.21.2 formdata-polyfill: specifier: ^4.0.10 version: 4.0.10 jest: specifier: 27.2.4 version: 27.2.4 koa: specifier: 2.16.1 version: 2.16.1 nyc: specifier: 15.1.0 version: 15.1.0 prettier: specifier: 2.0.5 version: 2.0.5 prettier-plugin-pkgjson: specifier: 0.2.8 version: 0.2.8(prettier@2.0.5) rollup: specifier: ^3.25.3 version: 3.29.5 supertest: specifier: 6.1.6 version: 6.1.6 packages: '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} '@babel/compat-data@7.26.8': resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} '@babel/core@7.26.10': resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} engines: {node: '>=6.9.0'} '@babel/generator@7.27.0': resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.0': resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.25.9': resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} '@babel/helper-module-transforms@7.26.0': resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 '@babel/helper-plugin-utils@7.26.5': resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.25.9': resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.25.9': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} '@babel/helpers@7.27.0': resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} engines: {node: '>=6.9.0'} '@babel/parser@7.27.0': resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} hasBin: true '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-bigint@7.8.3': resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-class-properties@7.12.13': resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-class-static-block@7.14.5': resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-import-attributes@7.26.0': resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-json-strings@7.8.3': resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-logical-assignment-operators@7.10.4': resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-numeric-separator@7.10.4': resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-object-rest-spread@7.8.3': resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-optional-catch-binding@7.8.3': resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-optional-chaining@7.8.3': resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-private-property-in-object@7.14.5': resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-top-level-await@7.14.5': resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-typescript@7.25.9': resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/template@7.27.0': resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} engines: {node: '>=6.9.0'} '@babel/traverse@7.27.0': resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} engines: {node: '>=6.9.0'} '@babel/types@7.27.0': resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} '@jest/console@27.5.1': resolution: {integrity: sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/core@27.5.1': resolution: {integrity: sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true '@jest/environment@27.5.1': resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/fake-timers@27.5.1': resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/globals@27.5.1': resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/reporters@27.5.1': resolution: {integrity: sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true '@jest/source-map@27.5.1': resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/test-result@27.5.1': resolution: {integrity: sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/test-sequencer@27.5.1': resolution: {integrity: sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/transform@27.5.1': resolution: {integrity: sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jest/types@27.5.1': resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} '@jridgewell/set-array@1.2.1': resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} '@nodelib/fs.walk@1.2.8': resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} '@rollup/plugin-commonjs@25.0.8': resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true '@rollup/plugin-node-resolve@15.3.1': resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.78.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true '@sindresorhus/slugify@2.2.1': resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} engines: {node: '>=12'} '@sindresorhus/transliterate@1.6.0': resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} engines: {node: '>=12'} '@sinonjs/commons@1.8.6': resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} '@sinonjs/fake-timers@8.1.0': resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} '@tunnckocore/prettier-config@1.3.8': resolution: {integrity: sha512-vhWfo1bgHMGR8qj8w9clxsanIDXYKTge/8kL23qVDfIMLOjam4SCyWgJi5ueOZ+drQJL7y3ni0y+o9lFvxaW+Q==} engines: {node: '>=10.13'} peerDependencies: prettier: ^2.0.2 prettier-plugin-pkgjson: ^0.2.3 '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} '@types/babel__generator@7.27.0': resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} '@types/istanbul-lib-report@3.0.3': resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} '@types/node@22.14.1': resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} '@types/prettier@2.7.3': resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} '@types/yargs@16.0.9': resolution: {integrity: sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==} abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} deprecated: Use your platform's native atob() and btoa() methods instead accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} acorn-globals@6.0.0: resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn-walk@7.2.0: resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} engines: {node: '>=0.4.0'} acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} hasBin: true acorn@8.14.1: resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} hasBin: true agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} ansi-regex@4.1.1: resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} engines: {node: '>=6'} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} append-transform@2.0.0: resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} engines: {node: '>=8'} archy@1.0.0: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} array-includes@3.1.8: resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} array.prototype.flat@1.3.3: resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} engines: {node: '>= 0.4'} arraybuffer.prototype.slice@1.0.4: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} astral-regex@1.0.0: resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} engines: {node: '>=4'} async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} babel-jest@27.5.1: resolution: {integrity: sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: '@babel/core': ^7.8.0 babel-plugin-istanbul@6.1.1: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} babel-plugin-jest-hoist@27.5.1: resolution: {integrity: sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} babel-preset-current-node-syntax@1.1.0: resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} peerDependencies: '@babel/core': ^7.0.0 babel-preset-jest@27.5.1: resolution: {integrity: sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: '@babel/core': ^7.0.0 balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} browser-process-hrtime@1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} browserslist@4.24.4: resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} cache-content-type@1.0.1: resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} engines: {node: '>= 6.0.0'} caching-transform@4.0.0: resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} engines: {node: '>=8'} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} caniuse-lite@1.0.30001715: resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} contains-path@0.1.0: resolution: {integrity: sha512-OKZnPGeMQy2RPaUIBPFFd71iNf4791H12MCRuVQDnzGRwCYNYmTDy5pdafo2SLAcEMKzTOQnLWG4QdcjeJUMEg==} engines: {node: '>=0.10.0'} content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} cookies@0.9.1: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} cssom@0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} cssom@0.4.4: resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} cssstyle@2.3.0: resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} engines: {node: '>=8'} data-urls@2.0.0: resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} engines: {node: '>=10'} data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} data-view-byte-length@1.0.2: resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} engines: {node: '>= 0.4'} data-view-byte-offset@1.0.1: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} deep-equal@1.0.1: resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} default-require-extensions@3.0.1: resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} engines: {node: '>=8'} define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} diff-sequences@27.5.1: resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} doctrine@1.5.0: resolution: {integrity: sha512-lsGyRuYr4/PIB0txi+Fy2xOMI2dGaTguCaotzFGkVZuKR5usKfcRWIFKNM3QNrU7hh/+w2bwTW+ZeXPK5l8uVg==} engines: {node: '>=0.10.0'} doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} domexception@2.0.1: resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} engines: {node: '>=8'} deprecated: Use your platform's native DOMException instead dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} electron-to-chromium@1.5.140: resolution: {integrity: sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==} emittery@0.8.1: resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==} engines: {node: '>=10'} emoji-regex@7.0.3: resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} es-abstract@1.23.9: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} es-shim-unscopables@1.1.0: resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} engines: {node: '>= 0.4'} es-to-primitive@1.3.0: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} escodegen@2.1.0: resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} engines: {node: '>=6.0'} hasBin: true eslint-config-airbnb-base@14.1.0: resolution: {integrity: sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw==} engines: {node: '>= 6'} peerDependencies: eslint: ^5.16.0 || ^6.8.0 eslint-plugin-import: ^2.20.1 eslint-config-prettier@6.11.0: resolution: {integrity: sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==} hasBin: true peerDependencies: eslint: '>=3.14.1' eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} eslint-module-utils@2.12.0: resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' eslint: '*' eslint-import-resolver-node: '*' eslint-import-resolver-typescript: '*' eslint-import-resolver-webpack: '*' peerDependenciesMeta: '@typescript-eslint/parser': optional: true eslint: optional: true eslint-import-resolver-node: optional: true eslint-import-resolver-typescript: optional: true eslint-import-resolver-webpack: optional: true eslint-plugin-import@2.20.2: resolution: {integrity: sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' eslint: 2.x - 6.x peerDependenciesMeta: '@typescript-eslint/parser': optional: true eslint-plugin-prettier@3.1.3: resolution: {integrity: sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==} engines: {node: '>=6.0.0'} peerDependencies: eslint: '>= 5.0.0' prettier: '>= 1.13.0' eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} eslint-utils@1.4.3: resolution: {integrity: sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==} engines: {node: '>=6'} eslint-visitor-keys@1.3.0: resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} engines: {node: '>=4'} eslint@6.8.0: resolution: {integrity: sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@6.2.1: resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} engines: {node: '>=6.0.0'} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} expect@27.5.1: resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} file-entry-cache@5.0.1: resolution: {integrity: sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==} engines: {node: '>=4'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} finalhandler@1.3.1: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} engines: {node: '>=8'} find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} flat-cache@2.0.1: resolution: {integrity: sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==} engines: {node: '>=4'} flatted@2.0.2: resolution: {integrity: sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} foreground-child@2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} form-data@3.0.3: resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==} engines: {node: '>= 6'} formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} formidable@1.2.6: resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==} deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau' forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} fromentries@1.3.2: resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} function.prototype.name@1.1.8: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} get-stdin@6.0.0: resolution: {integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==} engines: {node: '>=4'} get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} git-hooks-list@1.0.3: resolution: {integrity: sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} deprecated: Glob versions prior to v9 are no longer supported globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} globals@12.4.0: resolution: {integrity: sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==} engines: {node: '>=8'} globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} globby@10.0.0: resolution: {integrity: sha512-3LifW9M4joGZasyYPz2A1U74zbC/45fvpXUvO/9KbSa+VV0aGZarWkfdgKyR9sExNP0t0x0ss/UMJpNpcaTspw==} engines: {node: '>=8'} gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-proto@1.2.0: resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} has@1.0.4: resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} engines: {node: '>= 0.4.0'} hasha@5.2.2: resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} engines: {node: '>=8'} hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} html-encoding-sniffer@2.0.1: resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} engines: {node: '>=10'} html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} http-assert@1.5.0: resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} engines: {node: '>= 0.8'} http-errors@1.8.1: resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} engines: {node: '>= 0.6'} http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} http-proxy-agent@4.0.1: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} ignore@4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} engines: {node: '>= 4'} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} hasBin: true imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} is-bigint@1.1.0: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} is-boolean-object@1.2.2: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} is-date-object@1.1.0: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} is-finalizationregistry@1.1.1: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} is-fullwidth-code-point@2.0.0: resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} engines: {node: '>=4'} is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} is-shared-array-buffer@1.0.4: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} is-symbol@1.1.1: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} is-weakref@1.1.1: resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} engines: {node: '>= 0.4'} is-weakset@2.0.4: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} istanbul-lib-hook@3.0.0: resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} engines: {node: '>=8'} istanbul-lib-instrument@4.0.3: resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} engines: {node: '>=8'} istanbul-lib-instrument@5.2.1: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} istanbul-lib-processinfo@2.0.3: resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} engines: {node: '>=8'} istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} istanbul-lib-source-maps@4.0.1: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} istanbul-reports@3.1.7: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} jest-changed-files@27.5.1: resolution: {integrity: sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-circus@27.5.1: resolution: {integrity: sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-cli@27.5.1: resolution: {integrity: sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true jest-config@27.5.1: resolution: {integrity: sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: ts-node: '>=9.0.0' peerDependenciesMeta: ts-node: optional: true jest-diff@27.5.1: resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-docblock@27.5.1: resolution: {integrity: sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-each@27.5.1: resolution: {integrity: sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-environment-jsdom@27.5.1: resolution: {integrity: sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-environment-node@27.5.1: resolution: {integrity: sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-get-type@27.5.1: resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-haste-map@27.5.1: resolution: {integrity: sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-jasmine2@27.5.1: resolution: {integrity: sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-leak-detector@27.5.1: resolution: {integrity: sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-matcher-utils@27.5.1: resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-message-util@27.5.1: resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-mock@27.5.1: resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-pnp-resolver@1.2.3: resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} peerDependencies: jest-resolve: '*' peerDependenciesMeta: jest-resolve: optional: true jest-regex-util@27.5.1: resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-resolve-dependencies@27.5.1: resolution: {integrity: sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-resolve@27.5.1: resolution: {integrity: sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-runner@27.5.1: resolution: {integrity: sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-runtime@27.5.1: resolution: {integrity: sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-serializer@27.5.1: resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-snapshot@27.5.1: resolution: {integrity: sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-util@27.5.1: resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-validate@27.5.1: resolution: {integrity: sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-watcher@27.5.1: resolution: {integrity: sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} jest@27.2.4: resolution: {integrity: sha512-h4uqb1EQLfPulWyUFFWv9e9Nn8sCqsJ/j3wk/KCY0p4s4s0ICCfP3iMf6hRf5hEhsDyvyrCgKiZXma63gMz16A==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true jsdom@16.7.0: resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} engines: {node: '>=10'} peerDependencies: canvas: ^2.5.0 peerDependenciesMeta: canvas: optional: true jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} koa-convert@2.0.0: resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} engines: {node: '>= 10'} koa@2.16.1: resolution: {integrity: sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==} engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} levn@0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} engines: {node: '>= 0.8.0'} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} load-json-file@2.0.0: resolution: {integrity: sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==} engines: {node: '>=4'} locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} lodash.flattendeep@4.4.0: resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} hasBin: true mime@2.6.0: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} engines: {node: '>=4.0.0'} hasBin: true mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} node-preload@0.2.1: resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} engines: {node: '>=8'} node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} nwsapi@2.2.20: resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} nyc@15.1.0: resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} engines: {node: '>=8.9'} hasBin: true object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} object.assign@4.1.7: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} object.entries@1.1.9: resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} engines: {node: '>= 0.4'} object.values@1.2.1: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} only@0.0.2: resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} optionator@0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} engines: {node: '>= 0.8.0'} os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} p-locate@2.0.0: resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} engines: {node: '>=4'} p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} p-map@3.0.0: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} p-try@1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} package-hash@4.0.0: resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} engines: {node: '>=8'} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} parse-json@2.2.0: resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} engines: {node: '>=0.10.0'} parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} path-key@2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} path-type@2.0.0: resolution: {integrity: sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==} engines: {node: '>=4'} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} picomatch@4.0.2: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} prettier-linter-helpers@1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} prettier-plugin-pkgjson@0.2.8: resolution: {integrity: sha512-MBpPCjqQKxKc5SxhLkVeE2Q+3N7KBk+zJLkFzHVL6SMRAZlyiq/44w/NonCmO+24pI7s/LKoeFqcWY2+08vuEg==} engines: {node: '>=10.13'} peerDependencies: prettier: ^2.0.2 prettier@2.0.5: resolution: {integrity: sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==} engines: {node: '>=10.13.0'} hasBin: true pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} process-on-spawn@1.1.0: resolution: {integrity: sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==} engines: {node: '>=8'} progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} raw-body@2.5.2: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} read-pkg-up@2.0.0: resolution: {integrity: sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==} engines: {node: '>=4'} read-pkg@2.0.0: resolution: {integrity: sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==} engines: {node: '>=4'} readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} regexpp@2.0.1: resolution: {integrity: sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==} engines: {node: '>=6.5.0'} release-zalgo@1.0.0: resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} engines: {node: '>=4'} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} resolve.exports@1.1.1: resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==} engines: {node: '>=10'} resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} hasBin: true restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup@3.29.5: resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} saxes@5.0.1: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} set-function-name@2.0.2: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} set-proto@1.0.0: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} shebang-regex@1.0.0: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} engines: {node: '>=0.10.0'} shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} engines: {node: '>= 0.4'} side-channel-weakmap@1.0.2: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} side-channel@1.1.0: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} slice-ansi@2.1.0: resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} engines: {node: '>=6'} sort-object-keys@1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} sort-package-json@1.57.0: resolution: {integrity: sha512-FYsjYn2dHTRb41wqnv+uEqCUvBpK3jZcTp9rbz2qDTmel7Pmdtf+i2rLaaPMRZeSVM60V3Se31GyWFpmKs4Q5Q==} hasBin: true source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} source-map@0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} spawn-wrap@2.0.0: resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} engines: {node: '>=8'} spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} spdx-exceptions@2.5.0: resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} spdx-license-ids@3.0.21: resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} string-width@3.1.0: resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} engines: {node: '>=6'} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} string.prototype.trimend@1.0.9: resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} superagent@6.1.0: resolution: {integrity: sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==} engines: {node: '>= 7.0.0'} deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net supertest@6.1.6: resolution: {integrity: sha512-0hACYGNJ8OHRg8CRITeZOdbjur7NLuNs0mBjVhdpxi7hP6t3QIbOzLON5RTUmZcy2I9riuII3+Pr2C7yztrIIg==} engines: {node: '>=6.0.0'} supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} table@5.4.6: resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==} engines: {node: '>=6.0.0'} terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} throat@6.0.2: resolution: {integrity: sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==} through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} tr46@2.1.0: resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} engines: {node: '>=8'} tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} typed-array-byte-length@1.0.3: resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} typed-array-byte-offset@1.0.4: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} typed-array-length@1.0.7: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true v8-compile-cache@2.4.0: resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} v8-to-istanbul@8.1.1: resolution: {integrity: sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==} engines: {node: '>=10.12.0'} validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} w3c-hr-time@1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} deprecated: Use your platform's native performance.now() and performance.timeOrigin. w3c-xmlserializer@2.0.0: resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} engines: {node: '>=10'} walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} webidl-conversions@5.0.0: resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} engines: {node: '>=8'} webidl-conversions@6.1.0: resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} engines: {node: '>=10.4'} whatwg-encoding@1.0.5: resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} whatwg-mimetype@2.3.0: resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} whatwg-url@8.7.0: resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} engines: {node: '>=10'} which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} which-builtin-type@1.2.1: resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} engines: {node: '>= 0.4'} which-collection@1.0.2: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} write@1.0.3: resolution: {integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==} engines: {node: '>=4'} ws@7.5.10: resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 peerDependenciesMeta: bufferutil: optional: true utf-8-validate: optional: true xml-name-validator@3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} ylru@1.4.0: resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} engines: {node: '>= 4.0.0'} snapshots: '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 picocolors: 1.1.1 '@babel/compat-data@7.26.8': {} '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 '@babel/generator': 7.27.0 '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) '@babel/helpers': 7.27.0 '@babel/parser': 7.27.0 '@babel/template': 7.27.0 '@babel/traverse': 7.27.0 '@babel/types': 7.27.0 convert-source-map: 2.0.0 debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color '@babel/generator@7.27.0': dependencies: '@babel/parser': 7.27.0 '@babel/types': 7.27.0 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.0': dependencies: '@babel/compat-data': 7.26.8 '@babel/helper-validator-option': 7.25.9 browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.27.0 '@babel/types': 7.27.0 transitivePeerDependencies: - supports-color '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color '@babel/helper-plugin-utils@7.26.5': {} '@babel/helper-string-parser@7.25.9': {} '@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-option@7.25.9': {} '@babel/helpers@7.27.0': dependencies: '@babel/template': 7.27.0 '@babel/types': 7.27.0 '@babel/parser@7.27.0': dependencies: '@babel/types': 7.27.0 '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/template@7.27.0': dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.27.0 '@babel/types': 7.27.0 '@babel/traverse@7.27.0': dependencies: '@babel/code-frame': 7.26.2 '@babel/generator': 7.27.0 '@babel/parser': 7.27.0 '@babel/template': 7.27.0 '@babel/types': 7.27.0 debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color '@babel/types@7.27.0': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 '@bcoe/v8-coverage@0.2.3': {} '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.3': {} '@jest/console@27.5.1': dependencies: '@jest/types': 27.5.1 '@types/node': 22.14.1 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 slash: 3.0.0 '@jest/core@27.5.1': dependencies: '@jest/console': 27.5.1 '@jest/reporters': 27.5.1 '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 27.5.1 jest-config: 27.5.1 jest-haste-map: 27.5.1 jest-message-util: 27.5.1 jest-regex-util: 27.5.1 jest-resolve: 27.5.1 jest-resolve-dependencies: 27.5.1 jest-runner: 27.5.1 jest-runtime: 27.5.1 jest-snapshot: 27.5.1 jest-util: 27.5.1 jest-validate: 27.5.1 jest-watcher: 27.5.1 micromatch: 4.0.8 rimraf: 3.0.2 slash: 3.0.0 strip-ansi: 6.0.1 transitivePeerDependencies: - bufferutil - canvas - supports-color - ts-node - utf-8-validate '@jest/environment@27.5.1': dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 jest-mock: 27.5.1 '@jest/fake-timers@27.5.1': dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 '@types/node': 22.14.1 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 '@jest/globals@27.5.1': dependencies: '@jest/environment': 27.5.1 '@jest/types': 27.5.1 expect: 27.5.1 '@jest/reporters@27.5.1': dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 27.5.1 '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 5.2.1 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.7 jest-haste-map: 27.5.1 jest-resolve: 27.5.1 jest-util: 27.5.1 jest-worker: 27.5.1 slash: 3.0.0 source-map: 0.6.1 string-length: 4.0.2 terminal-link: 2.1.1 v8-to-istanbul: 8.1.1 transitivePeerDependencies: - supports-color '@jest/source-map@27.5.1': dependencies: callsites: 3.1.0 graceful-fs: 4.2.11 source-map: 0.6.1 '@jest/test-result@27.5.1': dependencies: '@jest/console': 27.5.1 '@jest/types': 27.5.1 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 '@jest/test-sequencer@27.5.1': dependencies: '@jest/test-result': 27.5.1 graceful-fs: 4.2.11 jest-haste-map: 27.5.1 jest-runtime: 27.5.1 transitivePeerDependencies: - supports-color '@jest/transform@27.5.1': dependencies: '@babel/core': 7.26.10 '@jest/types': 27.5.1 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 1.9.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 jest-haste-map: 27.5.1 jest-regex-util: 27.5.1 jest-util: 27.5.1 micromatch: 4.0.8 pirates: 4.0.7 slash: 3.0.0 source-map: 0.6.1 write-file-atomic: 3.0.3 transitivePeerDependencies: - supports-color '@jest/types@27.5.1': dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 '@types/node': 22.14.1 '@types/yargs': 16.0.9 chalk: 4.1.2 '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 '@nodelib/fs.stat@2.0.5': {} '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 '@paralleldrive/cuid2@2.2.2': dependencies: '@noble/hashes': 1.8.0 '@rollup/plugin-commonjs@25.0.8(rollup@3.29.5)': dependencies: '@rollup/pluginutils': 5.1.4(rollup@3.29.5) commondir: 1.0.1 estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 magic-string: 0.30.17 optionalDependencies: rollup: 3.29.5 '@rollup/plugin-node-resolve@15.3.1(rollup@3.29.5)': dependencies: '@rollup/pluginutils': 5.1.4(rollup@3.29.5) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.10 optionalDependencies: rollup: 3.29.5 '@rollup/pluginutils@5.1.4(rollup@3.29.5)': dependencies: '@types/estree': 1.0.7 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: rollup: 3.29.5 '@sindresorhus/slugify@2.2.1': dependencies: '@sindresorhus/transliterate': 1.6.0 escape-string-regexp: 5.0.0 '@sindresorhus/transliterate@1.6.0': dependencies: escape-string-regexp: 5.0.0 '@sinonjs/commons@1.8.6': dependencies: type-detect: 4.0.8 '@sinonjs/fake-timers@8.1.0': dependencies: '@sinonjs/commons': 1.8.6 '@tootallnate/once@1.1.2': {} '@tunnckocore/prettier-config@1.3.8(prettier-plugin-pkgjson@0.2.8(prettier@2.0.5))(prettier@2.0.5)': dependencies: prettier: 2.0.5 prettier-plugin-pkgjson: 0.2.8(prettier@2.0.5) '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.27.0 '@babel/types': 7.27.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 '@types/babel__generator@7.27.0': dependencies: '@babel/types': 7.27.0 '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.27.0 '@babel/types': 7.27.0 '@types/babel__traverse@7.20.7': dependencies: '@babel/types': 7.27.0 '@types/estree@1.0.7': {} '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 '@types/node': 22.14.1 '@types/graceful-fs@4.1.9': dependencies: '@types/node': 22.14.1 '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports@3.0.4': dependencies: '@types/istanbul-lib-report': 3.0.3 '@types/minimatch@5.1.2': {} '@types/node@22.14.1': dependencies: undici-types: 6.21.0 '@types/prettier@2.7.3': {} '@types/resolve@1.20.2': {} '@types/stack-utils@2.0.3': {} '@types/yargs-parser@21.0.3': {} '@types/yargs@16.0.9': dependencies: '@types/yargs-parser': 21.0.3 abab@2.0.6: {} accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 acorn-globals@6.0.0: dependencies: acorn: 7.4.1 acorn-walk: 7.2.0 acorn-jsx@5.3.2(acorn@7.4.1): dependencies: acorn: 7.4.1 acorn-walk@7.2.0: {} acorn@7.4.1: {} acorn@8.14.1: {} agent-base@6.0.2: dependencies: debug: 4.4.0 transitivePeerDependencies: - supports-color aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 ansi-regex@4.1.1: {} ansi-regex@5.0.1: {} ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 ansi-styles@5.2.0: {} anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 append-transform@2.0.0: dependencies: default-require-extensions: 3.0.1 archy@1.0.0: {} argparse@1.0.10: dependencies: sprintf-js: 1.0.3 array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 array-flatten@1.1.1: {} array-includes@3.1.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 array-union@2.1.0: {} array.prototype.flat@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 asap@2.0.6: {} astral-regex@1.0.0: {} async-function@1.0.0: {} asynckit@0.4.0: {} available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 babel-jest@27.5.1(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 babel-preset-jest: 27.5.1(@babel/core@7.26.10) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.26.5 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 test-exclude: 6.0.0 transitivePeerDependencies: - supports-color babel-plugin-jest-hoist@27.5.1: dependencies: '@babel/template': 7.27.0 '@babel/types': 7.27.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.10) '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.10) '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.10) '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.10) '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.10) '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.10) '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.10) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.10) babel-preset-jest@27.5.1(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 babel-plugin-jest-hoist: 27.5.1 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) balanced-match@1.0.2: {} body-parser@1.20.3: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 qs: 6.13.0 raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 braces@3.0.3: dependencies: fill-range: 7.1.1 browser-process-hrtime@1.0.0: {} browserslist@4.24.4: dependencies: caniuse-lite: 1.0.30001715 electron-to-chromium: 1.5.140 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.4) bser@2.1.1: dependencies: node-int64: 0.4.0 buffer-from@1.1.2: {} bytes@3.1.2: {} cache-content-type@1.0.1: dependencies: mime-types: 2.1.35 ylru: 1.4.0 caching-transform@4.0.0: dependencies: hasha: 5.2.2 make-dir: 3.1.0 package-hash: 4.0.0 write-file-atomic: 3.0.3 call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 get-intrinsic: 1.3.0 set-function-length: 1.2.2 call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 callsites@3.1.0: {} camelcase@5.3.1: {} camelcase@6.3.0: {} caniuse-lite@1.0.30001715: {} chalk@2.4.2: dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 char-regex@1.0.2: {} chardet@0.7.0: {} ci-info@3.9.0: {} cjs-module-lexer@1.4.3: {} clean-stack@2.2.0: {} cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 cli-width@3.0.0: {} cliui@6.0.0: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 cliui@7.0.4: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 co@4.6.0: {} collect-v8-coverage@1.0.2: {} color-convert@1.9.3: dependencies: color-name: 1.1.3 color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.3: {} color-name@1.1.4: {} combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 commondir@1.0.1: {} component-emitter@1.3.1: {} concat-map@0.0.1: {} confusing-browser-globals@1.0.11: {} contains-path@0.1.0: {} content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 content-type@1.0.5: {} convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} cookie@0.7.1: {} cookiejar@2.1.4: {} cookies@0.9.1: dependencies: depd: 2.0.0 keygrip: 1.1.0 cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 path-key: 2.0.1 semver: 5.7.2 shebang-command: 1.2.0 which: 1.3.1 cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 cssom@0.3.8: {} cssom@0.4.4: {} cssstyle@2.3.0: dependencies: cssom: 0.3.8 data-urls@2.0.0: dependencies: abab: 2.0.6 whatwg-mimetype: 2.3.0 whatwg-url: 8.7.0 data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 data-view-byte-length@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 data-view-byte-offset@1.0.1: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 debug@2.6.9: dependencies: ms: 2.0.0 debug@3.2.7: dependencies: ms: 2.1.3 debug@4.4.0: dependencies: ms: 2.1.3 decamelize@1.2.0: {} decimal.js@10.5.0: {} dedent@0.7.0: {} deep-equal@1.0.1: {} deep-is@0.1.4: {} deepmerge@4.3.1: {} default-require-extensions@3.0.1: dependencies: strip-bom: 4.0.0 define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 delayed-stream@1.0.0: {} delegates@1.0.0: {} depd@1.1.2: {} depd@2.0.0: {} destroy@1.2.0: {} detect-indent@6.1.0: {} detect-newline@3.1.0: {} dezalgo@1.0.4: dependencies: asap: 2.0.6 wrappy: 1.0.2 diff-sequences@27.5.1: {} dir-glob@3.0.1: dependencies: path-type: 4.0.0 doctrine@1.5.0: dependencies: esutils: 2.0.3 isarray: 1.0.0 doctrine@3.0.0: dependencies: esutils: 2.0.3 domexception@2.0.1: dependencies: webidl-conversions: 5.0.0 dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 ee-first@1.1.1: {} electron-to-chromium@1.5.140: {} emittery@0.8.1: {} emoji-regex@7.0.3: {} emoji-regex@8.0.0: {} encodeurl@1.0.2: {} encodeurl@2.0.0: {} error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 es-abstract@1.23.9: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 call-bind: 1.0.8 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 globalthis: 1.0.4 gopd: 1.2.0 has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 is-data-view: 1.0.2 is-regex: 1.2.1 is-shared-array-buffer: 1.0.4 is-string: 1.1.1 is-typed-array: 1.1.15 is-weakref: 1.1.1 math-intrinsics: 1.1.0 object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 safe-array-concat: 1.1.3 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 string.prototype.trim: 1.2.10 string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.4 typed-array-length: 1.0.7 unbox-primitive: 1.1.0 which-typed-array: 1.1.19 es-define-property@1.0.1: {} es-errors@1.3.0: {} es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 es-shim-unscopables@1.1.0: dependencies: hasown: 2.0.2 es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 es6-error@4.1.1: {} escalade@3.2.0: {} escape-html@1.0.3: {} escape-string-regexp@1.0.5: {} escape-string-regexp@2.0.0: {} escape-string-regexp@5.0.0: {} escodegen@2.1.0: dependencies: esprima: 4.0.1 estraverse: 5.3.0 esutils: 2.0.3 optionalDependencies: source-map: 0.6.1 eslint-config-airbnb-base@14.1.0(eslint-plugin-import@2.20.2(eslint@6.8.0))(eslint@6.8.0): dependencies: confusing-browser-globals: 1.0.11 eslint: 6.8.0 eslint-plugin-import: 2.20.2(eslint@6.8.0) object.assign: 4.1.7 object.entries: 1.1.9 eslint-config-prettier@6.11.0(eslint@6.8.0): dependencies: eslint: 6.8.0 get-stdin: 6.0.0 eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 is-core-module: 2.16.1 resolve: 1.22.10 transitivePeerDependencies: - supports-color eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint@6.8.0): dependencies: debug: 3.2.7 optionalDependencies: eslint: 6.8.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color eslint-plugin-import@2.20.2(eslint@6.8.0): dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.3 contains-path: 0.1.0 debug: 2.6.9 doctrine: 1.5.0 eslint: 6.8.0 eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint@6.8.0) has: 1.0.4 minimatch: 3.1.2 object.values: 1.2.1 read-pkg-up: 2.0.0 resolve: 1.22.10 transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color eslint-plugin-prettier@3.1.3(eslint@6.8.0)(prettier@2.0.5): dependencies: eslint: 6.8.0 prettier: 2.0.5 prettier-linter-helpers: 1.0.0 eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 eslint-utils@1.4.3: dependencies: eslint-visitor-keys: 1.3.0 eslint-visitor-keys@1.3.0: {} eslint@6.8.0: dependencies: '@babel/code-frame': 7.26.2 ajv: 6.12.6 chalk: 2.4.2 cross-spawn: 6.0.6 debug: 4.4.0 doctrine: 3.0.0 eslint-scope: 5.1.1 eslint-utils: 1.4.3 eslint-visitor-keys: 1.3.0 espree: 6.2.1 esquery: 1.6.0 esutils: 2.0.3 file-entry-cache: 5.0.1 functional-red-black-tree: 1.0.1 glob-parent: 5.1.2 globals: 12.4.0 ignore: 4.0.6 import-fresh: 3.3.1 imurmurhash: 0.1.4 inquirer: 7.3.3 is-glob: 4.0.3 js-yaml: 3.14.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.3.0 lodash: 4.17.21 minimatch: 3.1.2 mkdirp: 0.5.6 natural-compare: 1.4.0 optionator: 0.8.3 progress: 2.0.3 regexpp: 2.0.1 semver: 6.3.1 strip-ansi: 5.2.0 strip-json-comments: 3.1.1 table: 5.4.6 text-table: 0.2.0 v8-compile-cache: 2.4.0 transitivePeerDependencies: - supports-color espree@6.2.1: dependencies: acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) eslint-visitor-keys: 1.3.0 esprima@4.0.1: {} esquery@1.6.0: dependencies: estraverse: 5.3.0 esrecurse@4.3.0: dependencies: estraverse: 5.3.0 estraverse@4.3.0: {} estraverse@5.3.0: {} estree-walker@2.0.2: {} esutils@2.0.3: {} etag@1.8.1: {} execa@5.1.1: dependencies: cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 merge-stream: 2.0.0 npm-run-path: 4.0.1 onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 exit@0.1.2: {} expect@27.5.1: dependencies: '@jest/types': 27.5.1 jest-get-type: 27.5.1 jest-matcher-utils: 27.5.1 jest-message-util: 27.5.1 express@4.21.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 qs: 6.13.0 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.0 serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 transitivePeerDependencies: - supports-color external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} fast-safe-stringify@2.1.1: {} fastq@1.19.1: dependencies: reusify: 1.1.0 fb-watchman@2.0.2: dependencies: bser: 2.1.1 fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 file-entry-cache@5.0.1: dependencies: flat-cache: 2.0.1 fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 finalhandler@1.3.1: dependencies: debug: 2.6.9 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 statuses: 2.0.1 unpipe: 1.0.0 transitivePeerDependencies: - supports-color find-cache-dir@3.3.2: dependencies: commondir: 1.0.1 make-dir: 3.1.0 pkg-dir: 4.2.0 find-up@2.1.0: dependencies: locate-path: 2.0.0 find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 flat-cache@2.0.1: dependencies: flatted: 2.0.2 rimraf: 2.6.3 write: 1.0.3 flatted@2.0.2: {} for-each@0.3.5: dependencies: is-callable: 1.2.7 foreground-child@2.0.0: dependencies: cross-spawn: 7.0.6 signal-exit: 3.0.7 form-data@3.0.3: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 mime-types: 2.1.35 formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 formidable@1.2.6: {} forwarded@0.2.0: {} fresh@0.5.2: {} fromentries@1.3.2: {} fs.realpath@1.0.0: {} fsevents@2.3.3: optional: true function-bind@1.1.2: {} function.prototype.name@1.1.8: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 hasown: 2.0.2 is-callable: 1.2.7 functional-red-black-tree@1.0.1: {} functions-have-names@1.2.3: {} gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 math-intrinsics: 1.1.0 get-package-type@0.1.0: {} get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 get-stdin@6.0.0: {} get-stream@6.0.1: {} get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 git-hooks-list@1.0.3: {} glob-parent@5.1.2: dependencies: is-glob: 4.0.3 glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 glob@8.1.0: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 minimatch: 5.1.6 once: 1.4.0 globals@11.12.0: {} globals@12.4.0: dependencies: type-fest: 0.8.1 globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.2.0 globby@10.0.0: dependencies: '@types/glob': 7.2.0 array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.3 glob: 7.2.3 ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 gopd@1.2.0: {} graceful-fs@4.2.11: {} has-bigints@1.1.0: {} has-flag@3.0.0: {} has-flag@4.0.0: {} has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 has-proto@1.2.0: dependencies: dunder-proto: 1.0.1 has-symbols@1.1.0: {} has-tostringtag@1.0.2: dependencies: has-symbols: 1.1.0 has@1.0.4: {} hasha@5.2.2: dependencies: is-stream: 2.0.1 type-fest: 0.8.1 hasown@2.0.2: dependencies: function-bind: 1.1.2 hosted-git-info@2.8.9: {} html-encoding-sniffer@2.0.1: dependencies: whatwg-encoding: 1.0.5 html-escaper@2.0.2: {} http-assert@1.5.0: dependencies: deep-equal: 1.0.1 http-errors: 1.8.1 http-errors@1.8.1: dependencies: depd: 1.1.2 inherits: 2.0.4 setprototypeof: 1.2.0 statuses: 1.5.0 toidentifier: 1.0.1 http-errors@2.0.0: dependencies: depd: 2.0.0 inherits: 2.0.4 setprototypeof: 1.2.0 statuses: 2.0.1 toidentifier: 1.0.1 http-proxy-agent@4.0.1: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 debug: 4.4.0 transitivePeerDependencies: - supports-color https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 debug: 4.4.0 transitivePeerDependencies: - supports-color human-signals@2.1.0: {} iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 ignore@4.0.6: {} ignore@5.3.2: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 import-local@3.2.0: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 imurmurhash@0.1.4: {} indent-string@4.0.0: {} inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 inherits@2.0.4: {} inquirer@7.3.3: dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 cli-width: 3.0.0 external-editor: 3.1.0 figures: 3.2.0 lodash: 4.17.21 mute-stream: 0.0.8 run-async: 2.4.1 rxjs: 6.6.7 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.1.0 ipaddr.js@1.9.1: {} is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 is-arrayish@0.2.1: {} is-async-function@2.1.1: dependencies: async-function: 1.0.0 call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 is-bigint@1.1.0: dependencies: has-bigints: 1.1.0 is-boolean-object@1.2.2: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 is-callable@1.2.7: {} is-core-module@2.16.1: dependencies: hasown: 2.0.2 is-data-view@1.0.2: dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 is-typed-array: 1.1.15 is-date-object@1.1.0: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 is-fullwidth-code-point@2.0.0: {} is-fullwidth-code-point@3.0.0: {} is-generator-fn@2.1.0: {} is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-map@2.0.3: {} is-module@1.0.0: {} is-number-object@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 is-number@7.0.0: {} is-plain-obj@2.1.0: {} is-potential-custom-element-name@1.0.1: {} is-reference@1.2.1: dependencies: '@types/estree': 1.0.7 is-regex@1.2.1: dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 is-set@2.0.3: {} is-shared-array-buffer@1.0.4: dependencies: call-bound: 1.0.4 is-stream@2.0.1: {} is-string@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 is-symbol@1.1.1: dependencies: call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.19 is-typedarray@1.0.0: {} is-weakmap@2.0.2: {} is-weakref@1.1.1: dependencies: call-bound: 1.0.4 is-weakset@2.0.4: dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 is-windows@1.0.2: {} isarray@1.0.0: {} isarray@2.0.5: {} isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} istanbul-lib-hook@3.0.0: dependencies: append-transform: 2.0.0 istanbul-lib-instrument@4.0.3: dependencies: '@babel/core': 7.26.10 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: - supports-color istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.26.10 '@babel/parser': 7.27.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: - supports-color istanbul-lib-processinfo@2.0.3: dependencies: archy: 1.0.0 cross-spawn: 7.0.6 istanbul-lib-coverage: 3.2.2 p-map: 3.0.0 rimraf: 3.0.2 uuid: 8.3.2 istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 istanbul-lib-source-maps@4.0.1: dependencies: debug: 4.4.0 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 jest-changed-files@27.5.1: dependencies: '@jest/types': 27.5.1 execa: 5.1.1 throat: 6.0.2 jest-circus@27.5.1: dependencies: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 expect: 27.5.1 is-generator-fn: 2.1.0 jest-each: 27.5.1 jest-matcher-utils: 27.5.1 jest-message-util: 27.5.1 jest-runtime: 27.5.1 jest-snapshot: 27.5.1 jest-util: 27.5.1 pretty-format: 27.5.1 slash: 3.0.0 stack-utils: 2.0.6 throat: 6.0.2 transitivePeerDependencies: - supports-color jest-cli@27.5.1: dependencies: '@jest/core': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.2.0 jest-config: 27.5.1 jest-util: 27.5.1 jest-validate: 27.5.1 prompts: 2.4.2 yargs: 16.2.0 transitivePeerDependencies: - bufferutil - canvas - supports-color - ts-node - utf-8-validate jest-config@27.5.1: dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 27.5.1 '@jest/types': 27.5.1 babel-jest: 27.5.1(@babel/core@7.26.10) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 jest-circus: 27.5.1 jest-environment-jsdom: 27.5.1 jest-environment-node: 27.5.1 jest-get-type: 27.5.1 jest-jasmine2: 27.5.1 jest-regex-util: 27.5.1 jest-resolve: 27.5.1 jest-runner: 27.5.1 jest-util: 27.5.1 jest-validate: 27.5.1 micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 27.5.1 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: - bufferutil - canvas - supports-color - utf-8-validate jest-diff@27.5.1: dependencies: chalk: 4.1.2 diff-sequences: 27.5.1 jest-get-type: 27.5.1 pretty-format: 27.5.1 jest-docblock@27.5.1: dependencies: detect-newline: 3.1.0 jest-each@27.5.1: dependencies: '@jest/types': 27.5.1 chalk: 4.1.2 jest-get-type: 27.5.1 jest-util: 27.5.1 pretty-format: 27.5.1 jest-environment-jsdom@27.5.1: dependencies: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.7.0 transitivePeerDependencies: - bufferutil - canvas - supports-color - utf-8-validate jest-environment-node@27.5.1: dependencies: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 jest-mock: 27.5.1 jest-util: 27.5.1 jest-get-type@27.5.1: {} jest-haste-map@27.5.1: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.9 '@types/node': 22.14.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 jest-regex-util: 27.5.1 jest-serializer: 27.5.1 jest-util: 27.5.1 jest-worker: 27.5.1 micromatch: 4.0.8 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 jest-jasmine2@27.5.1: dependencies: '@jest/environment': 27.5.1 '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 is-generator-fn: 2.1.0 jest-each: 27.5.1 jest-matcher-utils: 27.5.1 jest-message-util: 27.5.1 jest-runtime: 27.5.1 jest-snapshot: 27.5.1 jest-util: 27.5.1 pretty-format: 27.5.1 throat: 6.0.2 transitivePeerDependencies: - supports-color jest-leak-detector@27.5.1: dependencies: jest-get-type: 27.5.1 pretty-format: 27.5.1 jest-matcher-utils@27.5.1: dependencies: chalk: 4.1.2 jest-diff: 27.5.1 jest-get-type: 27.5.1 pretty-format: 27.5.1 jest-message-util@27.5.1: dependencies: '@babel/code-frame': 7.26.2 '@jest/types': 27.5.1 '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.8 pretty-format: 27.5.1 slash: 3.0.0 stack-utils: 2.0.6 jest-mock@27.5.1: dependencies: '@jest/types': 27.5.1 '@types/node': 22.14.1 jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): optionalDependencies: jest-resolve: 27.5.1 jest-regex-util@27.5.1: {} jest-resolve-dependencies@27.5.1: dependencies: '@jest/types': 27.5.1 jest-regex-util: 27.5.1 jest-snapshot: 27.5.1 transitivePeerDependencies: - supports-color jest-resolve@27.5.1: dependencies: '@jest/types': 27.5.1 chalk: 4.1.2 graceful-fs: 4.2.11 jest-haste-map: 27.5.1 jest-pnp-resolver: 1.2.3(jest-resolve@27.5.1) jest-util: 27.5.1 jest-validate: 27.5.1 resolve: 1.22.10 resolve.exports: 1.1.1 slash: 3.0.0 jest-runner@27.5.1: dependencies: '@jest/console': 27.5.1 '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.11 jest-docblock: 27.5.1 jest-environment-jsdom: 27.5.1 jest-environment-node: 27.5.1 jest-haste-map: 27.5.1 jest-leak-detector: 27.5.1 jest-message-util: 27.5.1 jest-resolve: 27.5.1 jest-runtime: 27.5.1 jest-util: 27.5.1 jest-worker: 27.5.1 source-map-support: 0.5.21 throat: 6.0.2 transitivePeerDependencies: - bufferutil - canvas - supports-color - utf-8-validate jest-runtime@27.5.1: dependencies: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/globals': 27.5.1 '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 execa: 5.1.1 glob: 7.2.3 graceful-fs: 4.2.11 jest-haste-map: 27.5.1 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-regex-util: 27.5.1 jest-resolve: 27.5.1 jest-snapshot: 27.5.1 jest-util: 27.5.1 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color jest-serializer@27.5.1: dependencies: '@types/node': 22.14.1 graceful-fs: 4.2.11 jest-snapshot@27.5.1: dependencies: '@babel/core': 7.26.10 '@babel/generator': 7.27.0 '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) '@babel/traverse': 7.27.0 '@babel/types': 7.27.0 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/babel__traverse': 7.20.7 '@types/prettier': 2.7.3 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) chalk: 4.1.2 expect: 27.5.1 graceful-fs: 4.2.11 jest-diff: 27.5.1 jest-get-type: 27.5.1 jest-haste-map: 27.5.1 jest-matcher-utils: 27.5.1 jest-message-util: 27.5.1 jest-util: 27.5.1 natural-compare: 1.4.0 pretty-format: 27.5.1 semver: 7.7.1 transitivePeerDependencies: - supports-color jest-util@27.5.1: dependencies: '@jest/types': 27.5.1 '@types/node': 22.14.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 jest-validate@27.5.1: dependencies: '@jest/types': 27.5.1 camelcase: 6.3.0 chalk: 4.1.2 jest-get-type: 27.5.1 leven: 3.1.0 pretty-format: 27.5.1 jest-watcher@27.5.1: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 string-length: 4.0.2 jest-worker@27.5.1: dependencies: '@types/node': 22.14.1 merge-stream: 2.0.0 supports-color: 8.1.1 jest@27.2.4: dependencies: '@jest/core': 27.5.1 import-local: 3.2.0 jest-cli: 27.5.1 transitivePeerDependencies: - bufferutil - canvas - supports-color - ts-node - utf-8-validate js-tokens@4.0.0: {} js-yaml@3.14.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 jsdom@16.7.0: dependencies: abab: 2.0.6 acorn: 8.14.1 acorn-globals: 6.0.0 cssom: 0.4.4 cssstyle: 2.3.0 data-urls: 2.0.0 decimal.js: 10.5.0 domexception: 2.0.1 escodegen: 2.1.0 form-data: 3.0.3 html-encoding-sniffer: 2.0.1 http-proxy-agent: 4.0.1 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.20 parse5: 6.0.1 saxes: 5.0.1 symbol-tree: 3.2.4 tough-cookie: 4.1.4 w3c-hr-time: 1.0.2 w3c-xmlserializer: 2.0.0 webidl-conversions: 6.1.0 whatwg-encoding: 1.0.5 whatwg-mimetype: 2.3.0 whatwg-url: 8.7.0 ws: 7.5.10 xml-name-validator: 3.0.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate jsesc@3.1.0: {} json-parse-even-better-errors@2.3.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} keygrip@1.1.0: dependencies: tsscmp: 1.0.6 kleur@3.0.3: {} koa-compose@4.1.0: {} koa-convert@2.0.0: dependencies: co: 4.6.0 koa-compose: 4.1.0 koa@2.16.1: dependencies: accepts: 1.3.8 cache-content-type: 1.0.1 content-disposition: 0.5.4 content-type: 1.0.5 cookies: 0.9.1 debug: 4.4.0 delegates: 1.0.0 depd: 2.0.0 destroy: 1.2.0 encodeurl: 1.0.2 escape-html: 1.0.3 fresh: 0.5.2 http-assert: 1.5.0 http-errors: 1.8.1 is-generator-function: 1.1.0 koa-compose: 4.1.0 koa-convert: 2.0.0 on-finished: 2.4.1 only: 0.0.2 parseurl: 1.3.3 statuses: 1.5.0 type-is: 1.6.18 vary: 1.1.2 transitivePeerDependencies: - supports-color leven@3.1.0: {} levn@0.3.0: dependencies: prelude-ls: 1.1.2 type-check: 0.3.2 lines-and-columns@1.2.4: {} load-json-file@2.0.0: dependencies: graceful-fs: 4.2.11 parse-json: 2.2.0 pify: 2.3.0 strip-bom: 3.0.0 locate-path@2.0.0: dependencies: p-locate: 2.0.0 path-exists: 3.0.0 locate-path@5.0.0: dependencies: p-locate: 4.1.0 lodash.flattendeep@4.4.0: {} lodash@4.17.21: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 make-dir@3.1.0: dependencies: semver: 6.3.1 make-dir@4.0.0: dependencies: semver: 7.7.1 makeerror@1.0.12: dependencies: tmpl: 1.0.5 math-intrinsics@1.1.0: {} media-typer@0.3.0: {} merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} merge2@1.4.1: {} methods@1.1.2: {} micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 mime-db@1.52.0: {} mime-types@2.1.35: dependencies: mime-db: 1.52.0 mime@1.6.0: {} mime@2.6.0: {} mimic-fn@2.1.0: {} minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 minimist@1.2.8: {} mkdirp@0.5.6: dependencies: minimist: 1.2.8 ms@2.0.0: {} ms@2.1.3: {} mute-stream@0.0.8: {} natural-compare@1.4.0: {} negotiator@0.6.3: {} nice-try@1.0.5: {} node-domexception@1.0.0: {} node-int64@0.4.0: {} node-preload@0.2.1: dependencies: process-on-spawn: 1.1.0 node-releases@2.0.19: {} normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 resolve: 1.22.10 semver: 5.7.2 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} npm-run-path@4.0.1: dependencies: path-key: 3.1.1 nwsapi@2.2.20: {} nyc@15.1.0: dependencies: '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 caching-transform: 4.0.0 convert-source-map: 1.9.0 decamelize: 1.2.0 find-cache-dir: 3.3.2 find-up: 4.1.0 foreground-child: 2.0.0 get-package-type: 0.1.0 glob: 7.2.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-hook: 3.0.0 istanbul-lib-instrument: 4.0.3 istanbul-lib-processinfo: 2.0.3 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.7 make-dir: 3.1.0 node-preload: 0.2.1 p-map: 3.0.0 process-on-spawn: 1.1.0 resolve-from: 5.0.0 rimraf: 3.0.2 signal-exit: 3.0.7 spawn-wrap: 2.0.0 test-exclude: 6.0.0 yargs: 15.4.1 transitivePeerDependencies: - supports-color object-inspect@1.13.4: {} object-keys@1.1.1: {} object.assign@4.1.7: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 object.entries@1.1.9: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 object.values@1.2.1: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 on-finished@2.4.1: dependencies: ee-first: 1.1.1 once@1.4.0: dependencies: wrappy: 1.0.2 onetime@5.1.2: dependencies: mimic-fn: 2.1.0 only@0.0.2: {} optionator@0.8.3: dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.3.0 prelude-ls: 1.1.2 type-check: 0.3.2 word-wrap: 1.2.5 os-tmpdir@1.0.2: {} own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 p-limit@1.3.0: dependencies: p-try: 1.0.0 p-limit@2.3.0: dependencies: p-try: 2.2.0 p-locate@2.0.0: dependencies: p-limit: 1.3.0 p-locate@4.1.0: dependencies: p-limit: 2.3.0 p-map@3.0.0: dependencies: aggregate-error: 3.1.0 p-try@1.0.0: {} p-try@2.2.0: {} package-hash@4.0.0: dependencies: graceful-fs: 4.2.11 hasha: 5.2.2 lodash.flattendeep: 4.4.0 release-zalgo: 1.0.0 parent-module@1.0.1: dependencies: callsites: 3.1.0 parse-json@2.2.0: dependencies: error-ex: 1.3.2 parse-json@5.2.0: dependencies: '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parse5@6.0.1: {} parseurl@1.3.3: {} path-exists@3.0.0: {} path-exists@4.0.0: {} path-is-absolute@1.0.1: {} path-key@2.0.1: {} path-key@3.1.1: {} path-parse@1.0.7: {} path-to-regexp@0.1.12: {} path-type@2.0.0: dependencies: pify: 2.3.0 path-type@4.0.0: {} picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.2: {} pify@2.3.0: {} pirates@4.0.7: {} pkg-dir@4.2.0: dependencies: find-up: 4.1.0 possible-typed-array-names@1.1.0: {} prelude-ls@1.1.2: {} prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 prettier-plugin-pkgjson@0.2.8(prettier@2.0.5): dependencies: prettier: 2.0.5 sort-package-json: 1.57.0 prettier@2.0.5: {} pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 react-is: 17.0.2 process-on-spawn@1.1.0: dependencies: fromentries: 1.3.2 progress@2.0.3: {} prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 psl@1.15.0: dependencies: punycode: 2.3.1 punycode@2.3.1: {} qs@6.13.0: dependencies: side-channel: 1.1.0 qs@6.14.0: dependencies: side-channel: 1.1.0 querystringify@2.2.0: {} queue-microtask@1.2.3: {} range-parser@1.2.1: {} raw-body@2.5.2: dependencies: bytes: 3.1.2 http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 react-is@17.0.2: {} read-pkg-up@2.0.0: dependencies: find-up: 2.1.0 read-pkg: 2.0.0 read-pkg@2.0.0: dependencies: load-json-file: 2.0.0 normalize-package-data: 2.5.0 path-type: 2.0.0 readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 regexpp@2.0.1: {} release-zalgo@1.0.0: dependencies: es6-error: 4.1.1 require-directory@2.1.1: {} require-main-filename@2.0.0: {} requires-port@1.0.0: {} resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 resolve-from@4.0.0: {} resolve-from@5.0.0: {} resolve.exports@1.1.1: {} resolve@1.22.10: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 restore-cursor@3.1.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 reusify@1.1.0: {} rimraf@2.6.3: dependencies: glob: 7.2.3 rimraf@3.0.2: dependencies: glob: 7.2.3 rollup@3.29.5: optionalDependencies: fsevents: 2.3.3 run-async@2.4.1: {} run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 rxjs@6.6.7: dependencies: tslib: 1.14.1 safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 safe-buffer@5.2.1: {} safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 isarray: 2.0.5 safe-regex-test@1.1.0: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 safer-buffer@2.1.2: {} saxes@5.0.1: dependencies: xmlchars: 2.2.0 semver@5.7.2: {} semver@6.3.1: {} semver@7.7.1: {} send@0.19.0: dependencies: debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 encodeurl: 1.0.2 escape-html: 1.0.3 etag: 1.8.1 fresh: 0.5.2 http-errors: 2.0.0 mime: 1.6.0 ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 statuses: 2.0.1 transitivePeerDependencies: - supports-color serve-static@1.16.2: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 send: 0.19.0 transitivePeerDependencies: - supports-color set-blocking@2.0.0: {} set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 set-function-name@2.0.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 set-proto@1.0.0: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 setprototypeof@1.2.0: {} shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@1.0.0: {} shebang-regex@3.0.0: {} side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 side-channel-map@1.0.1: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 side-channel@1.1.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 signal-exit@3.0.7: {} sisteransi@1.0.5: {} slash@3.0.0: {} slice-ansi@2.1.0: dependencies: ansi-styles: 3.2.1 astral-regex: 1.0.0 is-fullwidth-code-point: 2.0.0 sort-object-keys@1.1.3: {} sort-package-json@1.57.0: dependencies: detect-indent: 6.1.0 detect-newline: 3.1.0 git-hooks-list: 1.0.3 globby: 10.0.0 is-plain-obj: 2.1.0 sort-object-keys: 1.1.3 source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 source-map@0.6.1: {} source-map@0.7.4: {} spawn-wrap@2.0.0: dependencies: foreground-child: 2.0.0 is-windows: 1.0.2 make-dir: 3.1.0 rimraf: 3.0.2 signal-exit: 3.0.7 which: 2.0.2 spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.21 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 spdx-license-ids: 3.0.21 spdx-license-ids@3.0.21: {} sprintf-js@1.0.3: {} stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 statuses@1.5.0: {} statuses@2.0.1: {} string-length@4.0.2: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 string-width@3.1.0: dependencies: emoji-regex: 7.0.3 is-fullwidth-code-point: 2.0.0 strip-ansi: 5.2.0 string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 es-abstract: 1.23.9 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.1.1 string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 strip-bom@3.0.0: {} strip-bom@4.0.0: {} strip-final-newline@2.0.0: {} strip-json-comments@3.1.1: {} superagent@6.1.0: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 debug: 4.4.0 fast-safe-stringify: 2.1.1 form-data: 3.0.3 formidable: 1.2.6 methods: 1.1.2 mime: 2.6.0 qs: 6.14.0 readable-stream: 3.6.2 semver: 7.7.1 transitivePeerDependencies: - supports-color supertest@6.1.6: dependencies: methods: 1.1.2 superagent: 6.1.0 transitivePeerDependencies: - supports-color supports-color@5.5.0: dependencies: has-flag: 3.0.0 supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-color@8.1.1: dependencies: has-flag: 4.0.0 supports-hyperlinks@2.3.0: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 supports-preserve-symlinks-flag@1.0.0: {} symbol-tree@3.2.4: {} table@5.4.6: dependencies: ajv: 6.12.6 lodash: 4.17.21 slice-ansi: 2.1.0 string-width: 3.1.0 terminal-link@2.1.1: dependencies: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 text-table@0.2.0: {} throat@6.0.2: {} through@2.3.8: {} tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 tmpl@1.0.5: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 toidentifier@1.0.1: {} tough-cookie@4.1.4: dependencies: psl: 1.15.0 punycode: 2.3.1 universalify: 0.2.0 url-parse: 1.5.10 tr46@2.1.0: dependencies: punycode: 2.3.1 tslib@1.14.1: {} tsscmp@1.0.6: {} type-check@0.3.2: dependencies: prelude-ls: 1.1.2 type-detect@4.0.8: {} type-fest@0.21.3: {} type-fest@0.8.1: {} type-is@1.6.18: dependencies: media-typer: 0.3.0 mime-types: 2.1.35 typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 undici-types@6.21.0: {} universalify@0.2.0: {} unpipe@1.0.0: {} update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: browserslist: 4.24.4 escalade: 3.2.0 picocolors: 1.1.1 uri-js@4.4.1: dependencies: punycode: 2.3.1 url-parse@1.5.10: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 util-deprecate@1.0.2: {} utils-merge@1.0.1: {} uuid@8.3.2: {} v8-compile-cache@2.4.0: {} v8-to-istanbul@8.1.1: dependencies: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 1.9.0 source-map: 0.7.4 validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 vary@1.1.2: {} w3c-hr-time@1.0.2: dependencies: browser-process-hrtime: 1.0.0 w3c-xmlserializer@2.0.0: dependencies: xml-name-validator: 3.0.0 walker@1.0.8: dependencies: makeerror: 1.0.12 web-streams-polyfill@3.3.3: {} webidl-conversions@5.0.0: {} webidl-conversions@6.1.0: {} whatwg-encoding@1.0.5: dependencies: iconv-lite: 0.4.24 whatwg-mimetype@2.3.0: {} whatwg-url@8.7.0: dependencies: lodash: 4.17.21 tr46: 2.1.0 webidl-conversions: 6.1.0 which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 is-boolean-object: 1.2.2 is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 is-generator-function: 1.1.0 is-regex: 1.2.1 is-weakref: 1.1.1 isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.19 which-collection@1.0.2: dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.4 which-module@2.0.1: {} which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 which@1.3.1: dependencies: isexe: 2.0.0 which@2.0.2: dependencies: isexe: 2.0.0 word-wrap@1.2.5: {} wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 wrappy@1.0.2: {} write-file-atomic@3.0.3: dependencies: imurmurhash: 0.1.4 is-typedarray: 1.0.0 signal-exit: 3.0.7 typedarray-to-buffer: 3.1.5 write@1.0.3: dependencies: mkdirp: 0.5.6 ws@7.5.10: {} xml-name-validator@3.0.0: {} xmlchars@2.2.0: {} y18n@4.0.3: {} y18n@5.0.8: {} yallist@3.1.1: {} yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 yargs-parser@20.2.9: {} yargs@15.4.1: dependencies: cliui: 6.0.0 decamelize: 1.2.0 find-up: 4.1.0 get-caller-file: 2.0.5 require-directory: 2.1.1 require-main-filename: 2.0.0 set-blocking: 2.0.0 string-width: 4.2.3 which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 yargs@16.2.0: dependencies: cliui: 7.0.4 escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 ylru@1.4.0: {} ================================================ FILE: src/Formidable.js ================================================ /* eslint-disable class-methods-use-this */ /* eslint-disable no-underscore-dangle */ import { init as cuid2init } from '@paralleldrive/cuid2'; import dezalgo from 'dezalgo'; import { EventEmitter } from 'node:events'; import fsPromises from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { StringDecoder } from 'node:string_decoder'; import once from 'once'; import FormidableError, * as errors from './FormidableError.js'; import PersistentFile from './PersistentFile.js'; import VolatileFile from './VolatileFile.js'; import DummyParser from './parsers/Dummy.js'; import MultipartParser from './parsers/Multipart.js'; import { json, multipart, octetstream, querystring } from './plugins/index.js'; const CUID2_FINGERPRINT = `${process.env.NODE_ENV}-${os.platform()}-${os.hostname()}` const createId = cuid2init({ length: 25, fingerprint: CUID2_FINGERPRINT.toLowerCase() }); const DEFAULT_OPTIONS = { maxFields: 1000, maxFieldsSize: 20 * 1024 * 1024, maxFiles: 1000, maxFileSize: 200 * 1024 * 1024, maxTotalFileSize: undefined, minFileSize: 1, allowEmptyFiles: false, createDirsFromUploads: false, keepExtensions: false, encoding: 'utf-8', hashAlgorithm: false, uploadDir: os.tmpdir(), enabledPlugins: [octetstream, querystring, multipart, json], fileWriteStreamHandler: null, defaultInvalidName: 'invalid-name', filter(_part) { return true; }, filename: undefined, }; function hasOwnProp(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); } const decorateForceSequential = function (promiseCreator) { /* forces a function that returns a promise to be sequential useful for fs for example */ let lastPromise = Promise.resolve(); return async function (...x) { const promiseWeAreWaitingFor = lastPromise; let currentPromise; let callback; // we need to change lastPromise before await anything, // otherwise 2 calls might wait the same thing lastPromise = new Promise(function (resolve) { callback = resolve; }); await promiseWeAreWaitingFor; currentPromise = promiseCreator(...x); currentPromise.then(callback).catch(callback); return currentPromise; }; }; const createNecessaryDirectoriesAsync = decorateForceSequential(function (filePath) { const directoryname = path.dirname(filePath); return fsPromises.mkdir(directoryname, { recursive: true }); }); const invalidExtensionChar = (c) => { const code = c.charCodeAt(0); return !( code === 46 || // . (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) ); }; class IncomingForm extends EventEmitter { constructor(options = {}) { super(); this.options = { ...DEFAULT_OPTIONS, ...options }; if (!this.options.maxTotalFileSize) { this.options.maxTotalFileSize = this.options.maxFileSize } const dir = path.resolve( this.options.uploadDir || this.options.uploaddir || os.tmpdir(), ); this.uploaddir = dir; this.uploadDir = dir; // initialize with null [ 'error', 'headers', 'type', 'bytesExpected', 'bytesReceived', '_parser', 'req', ].forEach((key) => { this[key] = null; }); this._setUpRename(); this._flushing = 0; this._fieldsSize = 0; this._totalFileSize = 0; this._plugins = []; this.openedFiles = []; this.options.enabledPlugins = [] .concat(this.options.enabledPlugins) .filter(Boolean); if (this.options.enabledPlugins.length === 0) { throw new FormidableError( 'expect at least 1 enabled builtin plugin, see options.enabledPlugins', errors.missingPlugin, ); } this.options.enabledPlugins.forEach((plugin) => { this.use(plugin); }); this._setUpMaxFields(); this._setUpMaxFiles(); this.ended = undefined; this.type = undefined; } use(plugin) { if (typeof plugin !== 'function') { throw new FormidableError( '.use: expect `plugin` to be a function', errors.pluginFunction, ); } this._plugins.push(plugin.bind(this)); return this; } pause () { try { this.req.pause(); } catch (err) { // the stream was destroyed if (!this.ended) { // before it was completed, crash & burn this._error(err); } return false; } return true; } resume () { try { this.req.resume(); } catch (err) { // the stream was destroyed if (!this.ended) { // before it was completed, crash & burn this._error(err); } return false; } return true; } // returns a promise if no callback is provided async parse(req, cb) { this.req = req; let promise; // Setup callback first, so we don't miss anything from data events emitted immediately. if (!cb) { let resolveRef; let rejectRef; promise = new Promise((resolve, reject) => { resolveRef = resolve; rejectRef = reject; }); cb = (err, fields, files) => { if (err) { rejectRef(err); } else { resolveRef([fields, files]); } } } const callback = once(dezalgo(cb)); this.fields = {}; const files = {}; this.on('field', (name, value) => { if (this.type === 'multipart' || this.type === 'urlencoded') { if (!hasOwnProp(this.fields, name)) { this.fields[name] = [value]; } else { this.fields[name].push(value); } } else { this.fields[name] = value; } }); this.on('file', (name, file) => { if (!hasOwnProp(files, name)) { files[name] = [file]; } else { files[name].push(file); } }); this.on('error', (err) => { callback(err, this.fields, files); }); this.on('end', () => { callback(null, this.fields, files); }); // Parse headers and setup the parser, ready to start listening for data. await this.writeHeaders(req.headers); let datafn = (buffer) => { try { this.write(buffer); } catch (err) { this._error(err); } } let endfn = () => { if (this.error) { return; } if (this._parser) { this._parser.end(); } } let pipe = null; // Start listening for data. req .on('error', (err) => { this._error(err); }) .on('aborted', () => { this.emit('aborted'); this._error(new FormidableError('Request aborted', errors.aborted)); }) switch (this.headers['content-encoding']) { case "gzip": pipe = require("zlib").createGunzip(); break; case "deflate": pipe = require("zlib").createInflate(); break; case "br": pipe = require("zlib").createBrotliDecompress(); break; case "compress": pipe = require("zlib").createUnzip(); break; default: pipe = node_stream.Transform({ transform: function (chunk, encoding, callback) { callback(null, chunk); } }) } pipe.on("data", datafn).on('end', endfn); req.pipe(pipe) if (promise) { return promise; } return this; } async writeHeaders(headers) { this.headers = headers; this._parseContentLength(); await this._parseContentType(); if (!this._parser) { this._error( new FormidableError( 'no parser found', errors.noParser, 415, // Unsupported Media Type ), ); return; } this._parser.once('error', (error) => { this._error(error); }); } write(buffer) { if (this.error) { return null; } if (!this._parser) { this._error( new FormidableError('uninitialized parser', errors.uninitializedParser), ); return null; } this.bytesReceived += buffer.length; this.emit('progress', this.bytesReceived, this.bytesExpected); this._parser.write(buffer); return this.bytesReceived; } onPart(part) { // this method can be overwritten by the user return this._handlePart(part); } async _handlePart(part) { if (part.originalFilename && typeof part.originalFilename !== 'string') { this._error( new FormidableError( `the part.originalFilename should be string when it exists`, errors.filenameNotString, ), ); return; } // This MUST check exactly for undefined. You can not change it to !part.originalFilename. // todo: uncomment when switch tests to Jest // console.log(part); // ? NOTE(@tunnckocore): no it can be any falsey value, it most probably depends on what's returned // from somewhere else. Where recently I changed the return statements // and such thing because code style // ? NOTE(@tunnckocore): or even better, if there is no mimetype, then it's for sure a field // ? NOTE(@tunnckocore): originalFilename is an empty string when a field? if (!part.mimetype) { let value = ''; const decoder = new StringDecoder( part.transferEncoding || this.options.encoding, ); part.on('data', (buffer) => { this._fieldsSize += buffer.length; if (this._fieldsSize > this.options.maxFieldsSize) { this._error( new FormidableError( `options.maxFieldsSize (${this.options.maxFieldsSize} bytes) exceeded, received ${this._fieldsSize} bytes of field data`, errors.maxFieldsSizeExceeded, 413, // Payload Too Large ), ); return; } value += decoder.write(buffer); }); part.on('end', () => { this.emit('field', part.name, value); }); return; } if (!this.options.filter(part)) { return; } this._flushing += 1; let fileSize = 0; const newFilename = this._getNewName(part); const filepath = this._joinDirectoryName(newFilename); const file = await this._newFile({ newFilename, filepath, originalFilename: part.originalFilename, mimetype: part.mimetype, }); file.on('error', (err) => { this._error(err); }); this.emit('fileBegin', part.name, file); file.open(); this.openedFiles.push(file); part.on('data', (buffer) => { this._totalFileSize += buffer.length; fileSize += buffer.length; if (this._totalFileSize > this.options.maxTotalFileSize) { this._error( new FormidableError( `options.maxTotalFileSize (${this.options.maxTotalFileSize} bytes) exceeded, received ${this._totalFileSize} bytes of file data`, errors.biggerThanTotalMaxFileSize, 413, ), ); return; } if (buffer.length === 0) { return; } this.pause(); file.write(buffer, () => { this.resume(); }); }); part.on('end', () => { if (!this.options.allowEmptyFiles && fileSize === 0) { this._error( new FormidableError( `options.allowEmptyFiles is false, file size should be greater than 0`, errors.noEmptyFiles, 400, ), ); return; } if (fileSize < this.options.minFileSize) { this._error( new FormidableError( `options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${fileSize} bytes of file data`, errors.smallerThanMinFileSize, 400, ), ); return; } if (fileSize > this.options.maxFileSize) { this._error( new FormidableError( `options.maxFileSize (${this.options.maxFileSize} bytes), received ${fileSize} bytes of file data`, errors.biggerThanMaxFileSize, 413, ), ); return; } file.end(() => { this._flushing -= 1; this.emit('file', part.name, file); this._maybeEnd(); }); }); } // eslint-disable-next-line max-statements async _parseContentType() { if (this.bytesExpected === 0) { this._parser = new DummyParser(this, this.options); return; } if (!this.headers['content-type']) { this._error( new FormidableError( 'bad content-type header, no content-type', errors.missingContentType, 400, ), ); return; } new DummyParser(this, this.options); const results = []; await Promise.all(this._plugins.map(async (plugin, idx) => { let pluginReturn = null; try { pluginReturn = await plugin(this, this.options) || this; } catch (err) { // directly throw from the `form.parse` method; // there is no other better way, except a handle through options const error = new FormidableError( `plugin on index ${idx} failed with: ${err.message}`, errors.pluginFailed, 500, ); error.idx = idx; throw error; } Object.assign(this, pluginReturn); // todo: use Set/Map and pass plugin name instead of the `idx` index this.emit('plugin', idx, pluginReturn); })); this.emit('pluginsResults', results); } _error(err, eventName = 'error') { if (this.error || this.ended) { return; } this.req = null; this.error = err; this.emit(eventName, err); this.openedFiles.forEach((file) => { file.destroy(); }); } _parseContentLength() { this.bytesReceived = 0; if (this.headers['content-length']) { this.bytesExpected = parseInt(this.headers['content-length'], 10); } else if (this.headers['transfer-encoding'] === undefined) { this.bytesExpected = 0; } if (this.bytesExpected !== null) { this.emit('progress', this.bytesReceived, this.bytesExpected); } } _newParser() { return new MultipartParser(this.options); } async _newFile({ filepath, originalFilename, mimetype, newFilename }) { if (this.options.fileWriteStreamHandler) { return new VolatileFile({ newFilename, filepath, originalFilename, mimetype, createFileWriteStream: this.options.fileWriteStreamHandler, hashAlgorithm: this.options.hashAlgorithm, }); } if (this.options.createDirsFromUploads) { try { await createNecessaryDirectoriesAsync(filepath); } catch (errorCreatingDir) { this._error(new FormidableError( `cannot create directory`, errors.cannotCreateDir, 409, )); } } return new PersistentFile({ newFilename, filepath, originalFilename, mimetype, hashAlgorithm: this.options.hashAlgorithm, }); } _getFileName(headerValue) { // matches either a quoted-string or a token (RFC 2616 section 19.5.1) const m = headerValue.match( /\bfilename=("(.*?)"|([^()<>{}[\]@,;:"?=\s/\t]+))($|;\s)/i, ); if (!m) return null; const match = m[2] || m[3] || ''; let originalFilename = match.substr(match.lastIndexOf('\\') + 1); originalFilename = originalFilename.replace(/%22/g, '"'); originalFilename = originalFilename.replace(/&#([\d]{4});/g, (_, code) => String.fromCharCode(code), ); return originalFilename; } // able to get composed extension with multiple dots // "a.b.c" -> ".b.c" // as opposed to path.extname -> ".c" _getExtension(str) { if (!str) { return ''; } const basename = path.basename(str); const firstDot = basename.indexOf('.'); const lastDot = basename.lastIndexOf('.'); let rawExtname = path.extname(basename); if (firstDot !== lastDot) { rawExtname = basename.slice(firstDot); } let filtered; const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar); if (firstInvalidIndex === -1) { filtered = rawExtname; } else { filtered = rawExtname.substring(0, firstInvalidIndex); } if (filtered === '.') { return ''; } return filtered; } _joinDirectoryName(name) { const resolvedDir = path.resolve(this.uploadDir); const resolvedPath = path.resolve(resolvedDir, name); // prevent directory traversal attacks // use resolvedDir + path.sep to avoid prefix collisions with sibling directories // e.g. uploadDir "/tmp/uploads" should not allow writes to "/tmp/uploads-evil/" if (resolvedPath === resolvedDir || !resolvedPath.startsWith(resolvedDir + path.sep)) { return path.join(this.uploadDir, this.options.defaultInvalidName); } return resolvedPath; } _setUpRename() { const hasRename = typeof this.options.filename === 'function'; if (hasRename) { this._getNewName = (part) => { let ext = ''; let name = this.options.defaultInvalidName; if (part.originalFilename) { // can be null ({ ext, name } = path.parse(part.originalFilename)); if (this.options.keepExtensions !== true) { ext = ''; } } return this.options.filename.call(this, name, ext, part, this); }; } else { this._getNewName = (part) => { const name = createId(); if (part && this.options.keepExtensions) { const originalFilename = typeof part === 'string' ? part : part.originalFilename; return `${name}${this._getExtension(originalFilename)}`; } return name; }; } } _setUpMaxFields() { if (this.options.maxFields !== Infinity) { let fieldsCount = 0; this.on('field', () => { fieldsCount += 1; if (fieldsCount > this.options.maxFields) { this._error( new FormidableError( `options.maxFields (${this.options.maxFields}) exceeded`, errors.maxFieldsExceeded, 413, ), ); } }); } } _setUpMaxFiles() { if (this.options.maxFiles !== Infinity) { let fileCount = 0; this.on('fileBegin', () => { fileCount += 1; if (fileCount > this.options.maxFiles) { this._error( new FormidableError( `options.maxFiles (${this.options.maxFiles}) exceeded`, errors.maxFilesExceeded, 413, ), ); } }); } } _maybeEnd() { if (!this.ended || this._flushing || this.error) { return; } this.req = null; this.emit('end'); } } export default IncomingForm; export { DEFAULT_OPTIONS }; ================================================ FILE: src/FormidableError.js ================================================ const missingPlugin = 1000; const pluginFunction = 1001; const aborted = 1002; const noParser = 1003; const uninitializedParser = 1004; const filenameNotString = 1005; const maxFieldsSizeExceeded = 1006; const maxFieldsExceeded = 1007; const smallerThanMinFileSize = 1008; const biggerThanTotalMaxFileSize = 1009; const noEmptyFiles = 1010; const missingContentType = 1011; const malformedMultipart = 1012; const missingMultipartBoundary = 1013; const unknownTransferEncoding = 1014; const maxFilesExceeded = 1015; const biggerThanMaxFileSize = 1016; const pluginFailed = 1017; const cannotCreateDir = 1018; const FormidableError = class extends Error { constructor(message, internalCode, httpCode = 500) { super(message); this.code = internalCode; this.httpCode = httpCode; } }; export { missingPlugin, pluginFunction, aborted, noParser, uninitializedParser, filenameNotString, maxFieldsSizeExceeded, maxFieldsExceeded, maxFilesExceeded, smallerThanMinFileSize, biggerThanMaxFileSize, noEmptyFiles, missingContentType, malformedMultipart, missingMultipartBoundary, unknownTransferEncoding, biggerThanTotalMaxFileSize, pluginFailed, cannotCreateDir, }; export default FormidableError; ================================================ FILE: src/PersistentFile.js ================================================ /* eslint-disable no-underscore-dangle */ import fs from 'node:fs'; import crypto from 'node:crypto'; import { EventEmitter } from 'node:events'; class PersistentFile extends EventEmitter { constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm }) { super(); this.lastModifiedDate = null; Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm }); this.size = 0; this._writeStream = null; if (typeof this.hashAlgorithm === 'string') { this.hash = crypto.createHash(this.hashAlgorithm); } else { this.hash = null; } } open() { this._writeStream = fs.createWriteStream(this.filepath); this._writeStream.on('error', (err) => { this.emit('error', err); }); } toJSON() { const json = { size: this.size, filepath: this.filepath, newFilename: this.newFilename, mimetype: this.mimetype, mtime: this.lastModifiedDate, length: this.length, originalFilename: this.originalFilename, }; if (this.hash && this.hash !== '') { json.hash = this.hash; } return json; } toString() { return `PersistentFile: ${this.newFilename}, Original: ${this.originalFilename}, Path: ${this.filepath}`; } write(buffer, cb) { if (this.hash) { this.hash.update(buffer); } if (this._writeStream.closed) { cb(); return; } this._writeStream.write(buffer, () => { this.lastModifiedDate = new Date(); this.size += buffer.length; this.emit('progress', this.size); cb(); }); } end(cb) { if (this.hash) { this.hash = this.hash.digest('hex'); } this._writeStream.end(() => { this.emit('end'); cb(); }); } destroy() { this._writeStream.destroy(); const filepath = this.filepath; setTimeout(function () { fs.unlink(filepath, () => {}); }, 1) } } export default PersistentFile; ================================================ FILE: src/VolatileFile.js ================================================ /* eslint-disable no-underscore-dangle */ import { createHash } from 'node:crypto'; import { EventEmitter } from 'node:events'; class VolatileFile extends EventEmitter { constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }) { super(); this.lastModifiedDate = null; Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }); this.size = 0; this._writeStream = null; if (typeof this.hashAlgorithm === 'string') { this.hash = createHash(this.hashAlgorithm); } else { this.hash = null; } } open() { this._writeStream = this.createFileWriteStream(this); this._writeStream.on('error', (err) => { this.emit('error', err); }); } destroy() { this._writeStream.destroy(); } toJSON() { const json = { size: this.size, newFilename: this.newFilename, length: this.length, originalFilename: this.originalFilename, mimetype: this.mimetype, }; if (this.hash && this.hash !== '') { json.hash = this.hash; } return json; } toString() { return `VolatileFile: ${this.originalFilename}`; } write(buffer, cb) { if (this.hash) { this.hash.update(buffer); } if (this._writeStream.closed || this._writeStream.destroyed) { cb(); return; } this._writeStream.write(buffer, () => { this.size += buffer.length; this.emit('progress', this.size); cb(); }); } end(cb) { if (this.hash) { this.hash = this.hash.digest('hex'); } this._writeStream.end(() => { this.emit('end'); cb(); }); } } export default VolatileFile; ================================================ FILE: src/helpers/firstValues.js ================================================ import { multipartType } from '../plugins/multipart.js'; import { querystringType } from '../plugins/querystring.js'; const firstValues = (form, fields, exceptions = []) => { if (form.type !== querystringType && form.type !== multipartType) { return fields; } return Object.fromEntries( Object.entries(fields).map(([key, value]) => { if (exceptions.includes(key)) { return [key, value]; } return [key, value[0]]; }), ); }; export { firstValues }; ================================================ FILE: src/helpers/readBooleans.js ================================================ const readBooleans = (fields, listOfBooleans) => { // html forms do not send off at all const fieldsWithBooleans = { ...fields }; listOfBooleans.forEach((key) => { fieldsWithBooleans[key] = fields[key] === `on` || fields[key] === true; }); return fieldsWithBooleans; }; export { readBooleans }; ================================================ FILE: src/index.js ================================================ import PersistentFile from './PersistentFile.js'; import VolatileFile from './VolatileFile.js'; import Formidable, { DEFAULT_OPTIONS } from './Formidable.js'; // make it available without requiring the `new` keyword // if you want it access `const formidable.IncomingForm` as v1 const formidable = (...args) => new Formidable(...args); const {enabledPlugins} = DEFAULT_OPTIONS; export default formidable; export { PersistentFile as File, PersistentFile, VolatileFile, Formidable, // alias Formidable as IncomingForm, // as named formidable, // misc DEFAULT_OPTIONS as defaultOptions, enabledPlugins, }; export * from './parsers/index.js'; export * from './plugins/index.js'; export * as errors from './FormidableError.js'; ================================================ FILE: src/parsers/Dummy.js ================================================ /* eslint-disable no-underscore-dangle */ import { Transform } from 'node:stream'; class DummyParser extends Transform { constructor(incomingForm, options = {}) { super(); this.globalOptions = { ...options }; this.incomingForm = incomingForm; } _flush(callback) { this.incomingForm.ended = true; this.incomingForm._maybeEnd(); callback(); } } export default DummyParser; ================================================ FILE: src/parsers/JSON.js ================================================ /* eslint-disable no-underscore-dangle */ import { Transform } from 'node:stream'; class JSONParser extends Transform { constructor(options = {}) { super({ readableObjectMode: true }); this.chunks = []; this.globalOptions = { ...options }; } _transform(chunk, encoding, callback) { this.chunks.push(String(chunk)); // todo consider using a string decoder callback(); } _flush(callback) { try { const fields = JSON.parse(this.chunks.join('')); this.push(fields); } catch (e) { callback(e); return; } this.chunks = null; callback(); } } export default JSONParser; ================================================ FILE: src/parsers/Multipart.js ================================================ /* eslint-disable no-fallthrough */ /* eslint-disable no-bitwise */ /* eslint-disable no-plusplus */ /* eslint-disable no-underscore-dangle */ import { Transform } from 'node:stream'; import * as errors from '../FormidableError.js'; import FormidableError from '../FormidableError.js'; let s = 0; const STATE = { PARSER_UNINITIALIZED: s++, START: s++, START_BOUNDARY: s++, HEADER_FIELD_START: s++, HEADER_FIELD: s++, HEADER_VALUE_START: s++, HEADER_VALUE: s++, HEADER_VALUE_ALMOST_DONE: s++, HEADERS_ALMOST_DONE: s++, PART_DATA_START: s++, PART_DATA: s++, PART_END: s++, END: s++, }; let f = 1; const FBOUNDARY = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) }; const LF = 10; const CR = 13; const SPACE = 32; const HYPHEN = 45; const COLON = 58; const A = 97; const Z = 122; function lower(c) { return c | 0x20; } export const STATES = {}; Object.keys(STATE).forEach((stateName) => { STATES[stateName] = STATE[stateName]; }); class MultipartParser extends Transform { constructor(options = {}) { super({ readableObjectMode: true }); this.boundary = null; this.boundaryChars = null; this.lookbehind = null; this.bufferLength = 0; this.state = STATE.PARSER_UNINITIALIZED; this.globalOptions = { ...options }; this.index = null; this.flags = 0; } _endUnexpected() { return new FormidableError( `MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`, errors.malformedMultipart, 400, ); } _flush(done) { if ( (this.state === STATE.HEADER_FIELD_START && this.index === 0) || (this.state === STATE.PART_DATA && this.index === this.boundary.length) ) { this._handleCallback('partEnd'); this._handleCallback('end'); done(); } else if (this.state !== STATE.END) { done(this._endUnexpected()); } else { done(); } } initWithBoundary(str) { this.boundary = Buffer.from(`\r\n--${str}`); this.lookbehind = Buffer.alloc(this.boundary.length + 8); this.state = STATE.START; this.boundaryChars = {}; for (let i = 0; i < this.boundary.length; i++) { this.boundaryChars[this.boundary[i]] = true; } } // eslint-disable-next-line max-params _handleCallback(name, buf, start, end) { if (start !== undefined && start === end) { return; } this.push({ name, buffer: buf, start, end }); } // eslint-disable-next-line max-statements _transform(buffer, _, done) { let i = 0; let prevIndex = this.index; let { index, state, flags } = this; const { lookbehind, boundary, boundaryChars } = this; const boundaryLength = boundary.length; const boundaryEnd = boundaryLength - 1; this.bufferLength = buffer.length; let c = null; let cl = null; const setMark = (name, idx) => { this[`${name}Mark`] = typeof idx === 'number' ? idx : i; }; const clearMarkSymbol = (name) => { delete this[`${name}Mark`]; }; const dataCallback = (name, shouldClear) => { const markSymbol = `${name}Mark`; if (!(markSymbol in this)) { return; } if (!shouldClear) { this._handleCallback(name, buffer, this[markSymbol], buffer.length); setMark(name, 0); } else { this._handleCallback(name, buffer, this[markSymbol], i); clearMarkSymbol(name); } }; for (i = 0; i < this.bufferLength; i++) { c = buffer[i]; switch (state) { case STATE.PARSER_UNINITIALIZED: done(this._endUnexpected()); return; case STATE.START: index = 0; state = STATE.START_BOUNDARY; case STATE.START_BOUNDARY: if (index === boundary.length - 2) { if (c === HYPHEN) { flags |= FBOUNDARY.LAST_BOUNDARY; } else if (c !== CR) { done(this._endUnexpected()); return; } index++; break; } else if (index - 1 === boundary.length - 2) { if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) { this._handleCallback('end'); state = STATE.END; flags = 0; } else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) { index = 0; this._handleCallback('partBegin'); state = STATE.HEADER_FIELD_START; } else { done(this._endUnexpected()); return; } break; } if (c !== boundary[index + 2]) { index = -2; } if (c === boundary[index + 2]) { index++; } break; case STATE.HEADER_FIELD_START: state = STATE.HEADER_FIELD; setMark('headerField'); index = 0; case STATE.HEADER_FIELD: if (c === CR) { clearMarkSymbol('headerField'); state = STATE.HEADERS_ALMOST_DONE; break; } index++; if (c === HYPHEN) { break; } if (c === COLON) { if (index === 1) { // empty header field done(this._endUnexpected()); return; } dataCallback('headerField', true); state = STATE.HEADER_VALUE_START; break; } cl = lower(c); if (cl < A || cl > Z) { done(this._endUnexpected()); return; } break; case STATE.HEADER_VALUE_START: if (c === SPACE) { break; } setMark('headerValue'); state = STATE.HEADER_VALUE; case STATE.HEADER_VALUE: if (c === CR) { dataCallback('headerValue', true); this._handleCallback('headerEnd'); state = STATE.HEADER_VALUE_ALMOST_DONE; } break; case STATE.HEADER_VALUE_ALMOST_DONE: if (c !== LF) { done(this._endUnexpected()); return; } state = STATE.HEADER_FIELD_START; break; case STATE.HEADERS_ALMOST_DONE: if (c !== LF) { done(this._endUnexpected()); return; } this._handleCallback('headersEnd'); state = STATE.PART_DATA_START; break; case STATE.PART_DATA_START: state = STATE.PART_DATA; setMark('partData'); case STATE.PART_DATA: prevIndex = index; if (index === 0) { // boyer-moore derived algorithm to safely skip non-boundary data i += boundaryEnd; while (i < this.bufferLength && !(buffer[i] in boundaryChars)) { i += boundaryLength; } i -= boundaryEnd; c = buffer[i]; } if (index < boundary.length) { if (boundary[index] === c) { if (index === 0) { dataCallback('partData', true); } index++; } else { index = 0; } } else if (index === boundary.length) { index++; if (c === CR) { // CR = part boundary flags |= FBOUNDARY.PART_BOUNDARY; } else if (c === HYPHEN) { // HYPHEN = end boundary flags |= FBOUNDARY.LAST_BOUNDARY; } else { index = 0; } } else if (index - 1 === boundary.length) { if (flags & FBOUNDARY.PART_BOUNDARY) { index = 0; if (c === LF) { // unset the PART_BOUNDARY flag flags &= ~FBOUNDARY.PART_BOUNDARY; this._handleCallback('partEnd'); this._handleCallback('partBegin'); state = STATE.HEADER_FIELD_START; break; } } else if (flags & FBOUNDARY.LAST_BOUNDARY) { if (c === HYPHEN) { this._handleCallback('partEnd'); this._handleCallback('end'); state = STATE.END; flags = 0; } else { index = 0; } } else { index = 0; } } if (index > 0) { // when matching a possible boundary, keep a lookbehind reference // in case it turns out to be a false lead lookbehind[index - 1] = c; } else if (prevIndex > 0) { // if our boundary turned out to be rubbish, the captured lookbehind // belongs to partData this._handleCallback('partData', lookbehind, 0, prevIndex); prevIndex = 0; setMark('partData'); // reconsider the current character even so it interrupted the sequence // it could be the beginning of a new sequence i--; } break; case STATE.END: break; default: done(this._endUnexpected()); return; } } dataCallback('headerField'); dataCallback('headerValue'); dataCallback('partData'); this.index = index; this.state = state; this.flags = flags; done(); return this.bufferLength; } explain() { return `state = ${MultipartParser.stateToString(this.state)}`; } } // eslint-disable-next-line consistent-return MultipartParser.stateToString = (stateNumber) => { // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const stateName in STATE) { const number = STATE[stateName]; if (number === stateNumber) return stateName; } }; export default Object.assign(MultipartParser, { STATES }); ================================================ FILE: src/parsers/OctetStream.js ================================================ import { PassThrough } from 'node:stream'; class OctetStreamParser extends PassThrough { constructor(options = {}) { super(); this.globalOptions = { ...options }; } } export default OctetStreamParser; ================================================ FILE: src/parsers/Querystring.js ================================================ /* eslint-disable no-underscore-dangle */ import { Transform } from 'node:stream'; // This is a buffering parser, have a look at StreamingQuerystring.js for a streaming parser class QuerystringParser extends Transform { constructor(options = {}) { super({ readableObjectMode: true }); this.globalOptions = { ...options }; this.buffer = ''; this.bufferLength = 0; } _transform(buffer, encoding, callback) { this.buffer += buffer.toString('ascii'); this.bufferLength = this.buffer.length; callback(); } _flush(callback) { const fields = new URLSearchParams(this.buffer); for (const [key, value] of fields) { this.push({ key, value, }); } this.buffer = ''; callback(); } } export default QuerystringParser; ================================================ FILE: src/parsers/StreamingQuerystring.js ================================================ // not used /* eslint-disable no-underscore-dangle */ import { Transform } from 'node:stream'; import FormidableError, { maxFieldsSizeExceeded } from '../FormidableError.js'; const AMPERSAND = 38; const EQUALS = 61; class QuerystringParser extends Transform { constructor(options = {}) { super({ readableObjectMode: true }); const { maxFieldSize } = options; this.maxFieldLength = maxFieldSize; this.buffer = Buffer.from(''); this.fieldCount = 0; this.sectionStart = 0; this.key = ''; this.readingKey = true; } _transform(buffer, encoding, callback) { let len = buffer.length; if (this.buffer && this.buffer.length) { // we have some data left over from the last write which we are in the middle of processing len += this.buffer.length; buffer = Buffer.concat([this.buffer, buffer], len); } for (let i = this.buffer.length || 0; i < len; i += 1) { const c = buffer[i]; if (this.readingKey) { // KEY, check for = if (c === EQUALS) { this.key = this.getSection(buffer, i); this.readingKey = false; this.sectionStart = i + 1; } else if (c === AMPERSAND) { // just key, no value. Prepare to read another key this.emitField(this.getSection(buffer, i)); this.sectionStart = i + 1; } // VALUE, check for & } else if (c === AMPERSAND) { this.emitField(this.key, this.getSection(buffer, i)); this.sectionStart = i + 1; } if ( this.maxFieldLength && i - this.sectionStart === this.maxFieldLength ) { callback( new FormidableError( `${ this.readingKey ? 'Key' : `Value for ${this.key}` } longer than maxFieldLength`, ), maxFieldsSizeExceeded, 413, ); } } // Prepare the remaining key or value (from sectionStart to the end) for the next write() or for end() len -= this.sectionStart; if (len) { // i.e. Unless the last character was a & or = this.buffer = Buffer.from(this.buffer, 0, this.sectionStart); } else this.buffer = null; this.sectionStart = 0; callback(); } _flush(callback) { // Emit the last field if (this.readingKey) { // we only have a key if there's something in the buffer. We definitely have no value if (this.buffer && this.buffer.length) { this.emitField(this.buffer.toString('ascii')); } } else { // We have a key, we may or may not have a value this.emitField( this.key, this.buffer && this.buffer.length && this.buffer.toString('ascii'), ); } this.buffer = ''; callback(); } getSection(buffer, i) { if (i === this.sectionStart) return ''; return buffer.toString('ascii', this.sectionStart, i); } emitField(key, val) { this.key = ''; this.readingKey = true; this.push({ key, value: val || '' }); } } export default QuerystringParser; // const q = new QuerystringParser({maxFieldSize: 100}); // (async function() { // for await (const chunk of q) { // console.log(chunk); // } // })(); // q.write("a=b&c=d") // q.end() ================================================ FILE: src/parsers/index.js ================================================ import JSONParser from './JSON.js'; import DummyParser from './Dummy.js'; import MultipartParser from './Multipart.js'; import OctetStreamParser from './OctetStream.js'; import QueryStringParser from './Querystring.js'; export { JSONParser, DummyParser, MultipartParser, OctetStreamParser, OctetStreamParser as OctetstreamParser, QueryStringParser, QueryStringParser as QuerystringParser, }; ================================================ FILE: src/plugins/index.js ================================================ import octetstream from './octetstream.js'; import querystring from './querystring.js'; import multipart from './multipart.js'; import json from './json.js'; export { octetstream, querystring, multipart, json }; ================================================ FILE: src/plugins/json.js ================================================ /* eslint-disable no-underscore-dangle */ import JSONParser from '../parsers/JSON.js'; export const jsonType = 'json'; // the `options` is also available through the `this.options` / `formidable.options` export default function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin // but this allows us to customize/test each plugin /* istanbul ignore next */ const self = this || formidable; if (/^[^;]*json/i.test(self.headers['content-type'])) { init.call(self, self, options); } return self; }; // Note that it's a good practice (but it's up to you) to use the `this.options` instead // of the passed `options` (second) param, because when you decide // to test the plugin you can pass custom `this` context to it (and so `this.options`) function init(_self, _opts) { this.type = jsonType; const parser = new JSONParser(this.options); parser.on('data', (fields) => { this.fields = fields; }); parser.once('end', () => { this.ended = true; this._maybeEnd(); }); this._parser = parser; } ================================================ FILE: src/plugins/multipart.js ================================================ /* eslint-disable no-underscore-dangle */ import { Stream } from 'node:stream'; import MultipartParser from '../parsers/Multipart.js'; import * as errors from '../FormidableError.js'; import FormidableError from '../FormidableError.js'; export const multipartType = 'multipart'; // the `options` is also available through the `options` / `formidable.options` export default function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin // but this allows us to customize/test each plugin /* istanbul ignore next */ const self = this || formidable; // NOTE: we (currently) support both multipart/form-data and multipart/related const multipart = /^[^;]*multipart/i.test(self.headers['content-type']); if (multipart) { const m = self.headers['content-type'].match( /boundary=(?:"([^"]+)"|([^;]+))/i, ); if (m) { const initMultipart = createInitMultipart(m[1] || m[2]); initMultipart.call(self, self, options); // lgtm [js/superfluous-trailing-arguments] } else { const err = new FormidableError( 'bad content-type header, no multipart boundary', errors.missingMultipartBoundary, 400, ); self._error(err); } } return self; } // Note that it's a good practice (but it's up to you) to use the `this.options` instead // of the passed `options` (second) param, because when you decide // to test the plugin you can pass custom `this` context to it (and so `this.options`) function createInitMultipart(boundary) { return function initMultipart() { this.type = multipartType; const parser = new MultipartParser(this.options); let headerField; let headerValue; let part; parser.initWithBoundary(boundary); // eslint-disable-next-line max-statements, consistent-return parser.on('data', async ({ name, buffer, start, end }) => { if (name === 'partBegin') { part = new Stream(); part.readable = true; part.headers = {}; part.name = null; part.originalFilename = null; part.mimetype = null; part.transferEncoding = this.options.encoding; part.transferBuffer = ''; headerField = ''; headerValue = ''; } else if (name === 'headerField') { headerField += buffer.toString(this.options.encoding, start, end); } else if (name === 'headerValue') { headerValue += buffer.toString(this.options.encoding, start, end); } else if (name === 'headerEnd') { headerField = headerField.toLowerCase(); part.headers[headerField] = headerValue; // matches either a quoted-string or a token (RFC 2616 section 19.5.1) const m = headerValue.match( // eslint-disable-next-line no-useless-escape /\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i, ); if (headerField === 'content-disposition') { if (m) { part.name = m[2] || m[3] || ''; } part.originalFilename = this._getFileName(headerValue); } else if (headerField === 'content-type') { part.mimetype = headerValue; } else if (headerField === 'content-transfer-encoding') { part.transferEncoding = headerValue.toLowerCase(); } headerField = ''; headerValue = ''; } else if (name === 'headersEnd') { switch (part.transferEncoding) { case 'binary': case '7bit': case '8bit': case 'utf-8': { const dataPropagation = (ctx) => { if (ctx.name === 'partData') { part.emit('data', ctx.buffer.slice(ctx.start, ctx.end)); } }; const dataStopPropagation = (ctx) => { if (ctx.name === 'partEnd') { part.emit('end'); parser.off('data', dataPropagation); parser.off('data', dataStopPropagation); } }; parser.on('data', dataPropagation); parser.on('data', dataStopPropagation); break; } case 'base64': { const dataPropagation = (ctx) => { if (ctx.name === 'partData') { part.transferBuffer += ctx.buffer .slice(ctx.start, ctx.end) .toString('ascii'); /* four bytes (chars) in base64 converts to three bytes in binary encoding. So we should always work with a number of bytes that can be divided by 4, it will result in a number of bytes that can be divided vy 3. */ const offset = parseInt(part.transferBuffer.length / 4, 10) * 4; part.emit( 'data', Buffer.from( part.transferBuffer.substring(0, offset), 'base64', ), ); part.transferBuffer = part.transferBuffer.substring(offset); } }; const dataStopPropagation = (ctx) => { if (ctx.name === 'partEnd') { part.emit('data', Buffer.from(part.transferBuffer, 'base64')); part.emit('end'); parser.off('data', dataPropagation); parser.off('data', dataStopPropagation); } }; parser.on('data', dataPropagation); parser.on('data', dataStopPropagation); break; } default: return this._error( new FormidableError( 'unknown transfer-encoding', errors.unknownTransferEncoding, 501, ), ); } this._parser.pause(); await this.onPart(part); this._parser.resume(); } else if (name === 'end') { this.ended = true; this._maybeEnd(); } }); this._parser = parser; }; } ================================================ FILE: src/plugins/octetstream.js ================================================ /* eslint-disable no-underscore-dangle */ import OctetStreamParser from '../parsers/OctetStream.js'; export const octetStreamType = 'octet-stream'; // the `options` is also available through the `options` / `formidable.options` export default async function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin // but this allows us to customize/test each plugin /* istanbul ignore next */ const self = this || formidable; if (/^[^;]*octet-stream/i.test(self.headers['content-type'])) { await init.call(self, self, options); } return self; } // Note that it's a good practice (but it's up to you) to use the `this.options` instead // of the passed `options` (second) param, because when you decide // to test the plugin you can pass custom `this` context to it (and so `this.options`) async function init(_self, _opts) { this.type = octetStreamType; const originalFilename = this.headers['x-file-name']; const mimetype = this.headers['content-type']; const thisPart = { originalFilename, mimetype, }; const newFilename = this._getNewName(thisPart); const filepath = this._joinDirectoryName(newFilename); const file = await this._newFile({ newFilename, filepath, originalFilename, mimetype, }); this.emit('fileBegin', originalFilename, file); file.open(); this.openedFiles.push(file); this._flushing += 1; this._parser = new OctetStreamParser(this.options); // Keep track of writes that haven't finished so we don't emit the file before it's done being written let outstandingWrites = 0; this._parser.on('data', (buffer) => { this.pause(); outstandingWrites += 1; file.write(buffer, () => { outstandingWrites -= 1; this.resume(); if (this.ended) { this._parser.emit('doneWritingFile'); } }); }); this._parser.on('end', () => { this._flushing -= 1; this.ended = true; const done = () => { file.end(() => { this.emit('file', 'file', file); this._maybeEnd(); }); }; if (outstandingWrites === 0) { done(); } else { this._parser.once('doneWritingFile', done); } }); return this; } ================================================ FILE: src/plugins/querystring.js ================================================ /* eslint-disable no-underscore-dangle */ import QuerystringParser from '../parsers/Querystring.js'; export const querystringType = 'urlencoded'; // the `options` is also available through the `this.options` / `formidable.options` export default function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin // but this allows us to customize/test each plugin /* istanbul ignore next */ const self = this || formidable; if (/^[^;]*urlencoded/i.test(self.headers['content-type'])) { init.call(self, self, options); } return self; }; // Note that it's a good practice (but it's up to you) to use the `this.options` instead // of the passed `options` (second) param, because when you decide // to test the plugin you can pass custom `this` context to it (and so `this.options`) function init(_self, _opts) { this.type = querystringType; const parser = new QuerystringParser(this.options); parser.on('data', ({ key, value }) => { this.emit('field', key, value); }); parser.once('end', () => { this.ended = true; this._maybeEnd(); }); this._parser = parser; return this; } ================================================ FILE: test/fixture/file/funkyfilename.txt ================================================ I am a text file with a funky name! ================================================ FILE: test/fixture/file/plain.txt ================================================ I am a simple plain text file ================================================ FILE: test/fixture/file/second-plaintext.txt ================================================ Some another plain text file, yeah! ================================================ FILE: test/fixture/http/encoding/beta-sticker-1.png.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Length: 2483 --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Disposition: form-data; name="sticker"; filename="beta-sticker-1.png" Content-Type: image/png Content-Transfer-Encoding: base64 iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABh5JREFUeNrMmHtIHEcYwGfv5SNwaovxEanEiJKqlYCCTRo1f0SvDeof1legEcE/YttQaNOiaQjYFFtpKaJILZU8SCRUWqlJGpoWepGLTXqUEnzFxCrnK9DEelbvvPOe/WacuY7r7HmGFjrwsbNzt7u//V7zfYvQ/2xI/9K1/NyvMP9PgCTuGmmL6/0ckD9UOGmbIExUsqMkAPHJjv5QwKRtgKioqDlh5+w/7IFeCuLlxCeA2zQ0IcCwh2qoaLH09fUdTElJ2e/1elU+n0/y+9fvPz4+fvfYsWN3YOoBcXPiocLghD4mBYHhQTCErqWlZU9FRcXJqKiowyqVSk/uSEH4o8fjWVlYWDB2d3e3d3R0WGB5jYqLg/NyGgsKxMNgkDB4451NTU3vxcXF1SlBKB0tFsuVxsbGjlu3bj2GJQeIk8K5RVBqBTMxrYRfuHAh9/jx4+ejo6MPS9I6f6hHPOC6rOLi4vyVlZXf7t27Z5c5/iZfkgMxxyUwFy9ezC0tLe3V6XRJ/MOCAYjWwsLCni0oKCh98uSJaWhoyMZFn0/uT2qBqYi/1NbWxjc0NJwPFUYExc/B53R5eXk5ZrN5YH5+3slFn5+D2uBDzG90IJETExOtzGdC9RelNf78wYMH3xQWFn4Ep0sgyyCr1NmJP6kEIa5tbW3dEx8fXxeKRoJpT76OR3p6enllZWUKTCOwNalFAglWDkTCvLq6+uR2YYKZSw4GQVKNfZQCafjkqhKYTBsTE3NY/uYi2Q4MP5KTkw9QGB3VEMv6G/YioqFLly5lazQavfytxobnUW+PWTGisIyNPEL3QYLB4PPIyMi4EydO7JUBbTIZ0RDYOFPkE8t/OdHczCK6Y/qdzP8BfUTW8Tj/uQndvT1F5vOzVvTLz1PwX4cQbt++fekURsNpSNLIw16v1z/HLsRRgecsSnovm8nxs5bvUe+NN1Bz47fkfBaAXj2aA2BWEsM/3hhFX1/5Fe3NTEAfvn8NXTO+tSH68IiNjU2Qw/AmCzg2XCQp+YyhJAu9c+pl9GJ+KmhiEt38bhjpoyJQRtYudA60k3dwD6o4mouKjmSiolcy0ArRqnXz3rT+knwFEShhNKLNlmmFP7Kf8XxuehHpj0QQmLdPGch/ioYyCSAe57pMaHnJgcprctDdwUkRjKi8CUTWhipvbm7uvlJo3zFNoHJDOznPeGEXqn+9EBUf+AQZXvqU+BEG/KCpHz2flYh+ALO9++ZX5L/Mj3gfevjw4ZRoP+PzD/b4HadPn844c+aMkb0F1DqIz9byzBvquXytvr6+7vr16+Ow9CfN2njjdfFAWpo9o2FnNmm12kQMw24gcvSnhbHb7Y+huHsNlhapLNHSxK3idlq287qhhrkKlSByOBzIZrPhGyCn04ncbjfRGAMV5ZlQxvDw8E+yYi1Q3qpleYjUQlNTU5aysrJqgNBhIAwGVSDCkFj48BVFULA1eCl7XV3dx1CKYK3YqKnY7u9Ti2royclJ76FDh1YhxefgsoFpCIOtra0RuGBQwYbRaLzc1dVlpjA2ZiqmKbWsDAmEYU9Pz8Tg4OCNoqKixNTU1BQostDq6iqBcrlcRBiYfEff1KBR+OnpabPBYOikWlnhtOOWm0zUffpnZ2ednZ2dJtCYMTs7+xkA2x0eHk6gsMYwFPYr/EC1Wo2LMEWzWa1WC1QRZ8FUVgpj42ohD3umWqHjRFxf5RkZGVkCNQ9CcTWQn5+flpSUtBOiMKAt7Fek/FSAmpmZMVdVVZ0dGxv7g4PhteMVlbBIofv0sh4Lbmhtb2+/Cbv1eFpaWmJCQsJODMO0hGGgUghAAay9v7//i5KSki9lmmG+4+Jg/MHaIH6f0dCkqaNFFc5VkViam5v319TUNEDdvRubEGsNYHGqsAwMDFxta2u7DdpdpA+3c+LgWiHfVkCiFnpDw0iLqwgqO6BVKoPo00K6WIDsOzE6OrpE395FzeLgxMn5jVe0dYTa26s5jfFg4VR0nAuwNtrFda1rgmToD6VzVWq3eTPyYAxOwwH5gvT2PiWY7X4fUgJTywp1fivyyL6E+Lb6XvQ0X9AkBeeXZED+p/k+9LcAAwAXm3hBLzoZPAAAAABJRU5ErkJggg== --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/-- ================================================ FILE: test/fixture/http/encoding/binaryfile.tar.gz.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Length: 676 --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Disposition: form-data; name="file"; filename="binaryfile.tar.gz" Content-Type: application/x-gzip Content-Transfer-Encoding: base64 H4sIAGiNIU8AA+3R0W6CMBQGYK59iobLZantRDG73osUOGqnFNJWM2N897UghG1ZdmWWLf93U/jP4bRAq8q92hJ/dY1J7kQEqyyLq8yXYrp2ltkqkTKXYiEykYc++ZTLVcLEvQ40dXReWcYSV1pdnL/v+6n+R11mjKVG1ZQ+s3TT2FpXqjhQ+hjzE1mnGxNLkgu+7tOKWjIVmVKTC6XL9ZaeXj4VQhwKWzL+cI4zwgQuuhkh3mhTad/Hkssh3im3027X54JnQ360R/M19OT8kC7SEN7Ooi2VvrEfznHQRWzl83gxttZKmzGehzPRW/+W8X+3fvL8sFet9sS6m3EIma02071MU3Uf9KHrmV1/+y8DAAAAAAAAAAAAAAAAAAAAAMB/9A6txIuJACgAAA== --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/-- ================================================ FILE: test/fixture/http/encoding/blank.gif.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Length: 323 --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Disposition: form-data; name="file"; filename="blank.gif" Content-Type: image/gif Content-Transfer-Encoding: base64 R0lGODlhAQABAJH/AP///wAAAMDAwAAAACH5BAEAAAIALAAAAAABAAEAAAICVAEAOw== --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/-- ================================================ FILE: test/fixture/http/encoding/menu_separator.png.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Length: 1509 --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Content-Disposition: form-data; name="image"; filename="menu_separator.png" Content-Type: image/png Content-Transfer-Encoding: base64 iVBORw0KGgoAAAANSUhEUgAAAAIAAAAYCAIAAABfmbuOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDcxODNBNzJERDcyMTFFMUFBOEVFNDQzOTA0MDJDMjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDcxODNBNzNERDcyMTFFMUFBOEVFNDQzOTA0MDJDMjQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNzE4M0E3MERENzIxMUUxQUE4RUU0NDM5MDQwMkMyNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNzE4M0E3MURENzIxMUUxQUE4RUU0NDM5MDQwMkMyNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pmvhbb8AAAAXSURBVHjaYnHk9PON8WJiAIPBSwEEGAAPrgG+VozFWgAAAABJRU5ErkJggg== --\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/-- ================================================ FILE: test/fixture/http/encoding/plain.txt.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ Content-Length: 221 ------TLV0SrKD4z1TRxRhAPUvZ Content-Disposition: form-data; name="file"; filename="plain.txt" Content-Type: text/plain Content-Transfer-Encoding: 7bit I am a plain text file ------TLV0SrKD4z1TRxRhAPUvZ-- ================================================ FILE: test/fixture/http/misc/boundary-substring-json.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=uj05Dyqd7Fd5aqAJnK1j9WeJSONmNy5vSGbM1oLf Content-Length: 211 --uj05Dyqd7Fd5aqAJnK1j9WeJSONmNy5vSGbM1oLf Content-Disposition: form-data; filename="plain.txt"; name="upload" Content-Type: text/plain I am a plain text file --uj05Dyqd7Fd5aqAJnK1j9WeJSONmNy5vSGbM1oLf-- ================================================ FILE: test/fixture/http/misc/empty-multipart.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ Content-Length: 0 ================================================ FILE: test/fixture/http/misc/empty-multipart2.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ Content-Length: 31 ------TLV0SrKD4z1TRxRhAPUvZ-- ================================================ FILE: test/fixture/http/misc/empty-urlencoded.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Length: 0 Content-Type: application/x-www-form-urlencoded ================================================ FILE: test/fixture/http/misc/empty.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Length: 0 ================================================ FILE: test/fixture/http/misc/minimal.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 ================================================ FILE: test/fixture/http/no-filename/filename-name.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG Content-Length: 1000 ------WebKitFormBoundarytyE4wkKlZ5CQJVTG Content-Disposition: form-data; filename="plain.txt"; name="upload" Content-Type: text/plain I am a plain text file ------WebKitFormBoundarytyE4wkKlZ5CQJVTG-- ================================================ FILE: test/fixture/http/no-filename/generic.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG Content-Length: 1000 ------WebKitFormBoundarytyE4wkKlZ5CQJVTG Content-Disposition: form-data; name="upload"; filename="" Content-Type: text/plain I am a plain text file ------WebKitFormBoundarytyE4wkKlZ5CQJVTG-- ================================================ FILE: test/fixture/http/preamble/crlf.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ Content-Length: 184 ------TLV0SrKD4z1TRxRhAPUvZ Content-Disposition: form-data; name="upload"; filename="plain.txt" Content-Type: text/plain I am a plain text file ------TLV0SrKD4z1TRxRhAPUvZ-- ================================================ FILE: test/fixture/http/preamble/preamble.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ Content-Length: 226 This is a preamble which should be ignored ------TLV0SrKD4z1TRxRhAPUvZ Content-Disposition: form-data; name="upload"; filename="plain.txt" Content-Type: text/plain I am a plain text file ------TLV0SrKD4z1TRxRhAPUvZ-- ================================================ FILE: test/fixture/http/special-chars-in-filename/info.md ================================================ - Opera does not allow submitting this file, it shows a warning to the user that the file could not be found instead. Tested in 9.8, 11.51 on OSX. Reported to Opera on 08.09.2011 (tracking email DSK-346009@bugs.opera.com). ================================================ FILE: test/fixture/http/special-chars-in-filename/line-separator.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Referer: http://localhost:8080/ Content-Length: 383 Origin: http://localhost:8080 User-Agent: Mozilla/5.0 Content-Type: multipart/form-data; boundary=----SEPARATOR Accept: text/html ------SEPARATOR Content-Disposition: form-data; name="title" Weird filename ------SEPARATOR Content-Disposition: form-data; name="upload"; filename="
.txt" Content-Type: text/plain I am a text file with a funky name! ------SEPARATOR-- ================================================ FILE: test/fixture/http/special-chars-in-filename/osx-chrome-13.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Connection: keep-alive Referer: http://localhost:8080/ Content-Length: 383 Cache-Control: max-age=0 Origin: http://localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: jqCookieJar_tablesorter=%7B%22showListTable%22%3A%5B%5B5%2C1%5D%2C%5B1%2C0%5D%5D%7D ------WebKitFormBoundarytyE4wkKlZ5CQJVTG Content-Disposition: form-data; name="title" Weird filename ------WebKitFormBoundarytyE4wkKlZ5CQJVTG Content-Disposition: form-data; name="upload"; filename=": \ ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: text/plain I am a text file with a funky name! ------WebKitFormBoundarytyE4wkKlZ5CQJVTG-- ================================================ FILE: test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Referer: http://localhost:8080/ Content-Type: multipart/form-data; boundary=---------------------------9849436581144108930470211272 Content-Length: 438 -----------------------------9849436581144108930470211272 Content-Disposition: form-data; name="title" Weird filename -----------------------------9849436581144108930470211272 Content-Disposition: form-data; name="upload"; filename=": \ ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: text/plain I am a text file with a funky name! -----------------------------9849436581144108930470211272-- ================================================ FILE: test/fixture/http/special-chars-in-filename/osx-safari-5.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Origin: http://localhost:8080 Content-Length: 383 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQJZ1gvhvdgfisJPJ Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Referer: http://localhost:8080/ Accept-Language: en-us Accept-Encoding: gzip, deflate Connection: keep-alive ------WebKitFormBoundaryQJZ1gvhvdgfisJPJ Content-Disposition: form-data; name="title" Weird filename ------WebKitFormBoundaryQJZ1gvhvdgfisJPJ Content-Disposition: form-data; name="upload"; filename=": \ ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: text/plain I am a text file with a funky name! ------WebKitFormBoundaryQJZ1gvhvdgfisJPJ-- ================================================ FILE: test/fixture/http/special-chars-in-filename/xp-chrome-12.http ================================================ POST /upload HTTP/1.1 Host: 192.168.56.1:8080 Connection: keep-alive Referer: http://192.168.56.1:8080/ Content-Length: 344 Cache-Control: max-age=0 Origin: http://192.168.56.1:8080 User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEvqBNplR3ByrwQPa Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 ------WebKitFormBoundaryEvqBNplR3ByrwQPa Content-Disposition: form-data; name="title" Weird filename ------WebKitFormBoundaryEvqBNplR3ByrwQPa Content-Disposition: form-data; name="upload"; filename=" ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: text/plain ------WebKitFormBoundaryEvqBNplR3ByrwQPa-- ================================================ FILE: test/fixture/http/special-chars-in-filename/xp-ie-7.http ================================================ POST /upload HTTP/1.1 Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */* Referer: http://192.168.56.1:8080/ Accept-Language: de User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) Content-Type: multipart/form-data; boundary=---------------------------7db1fe232017c Accept-Encoding: gzip, deflate Host: 192.168.56.1:8080 Content-Length: 368 Connection: Keep-Alive Cache-Control: no-cache -----------------------------7db1fe232017c Content-Disposition: form-data; name="title" Weird filename -----------------------------7db1fe232017c Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: application/octet-stream -----------------------------7db1fe232017c-- ================================================ FILE: test/fixture/http/special-chars-in-filename/xp-ie-8.http ================================================ POST /upload HTTP/1.1 Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */* Referer: http://192.168.56.1:8080/ Accept-Language: de User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0) Content-Type: multipart/form-data; boundary=---------------------------7db3a8372017c Accept-Encoding: gzip, deflate Host: 192.168.56.1:8080 Content-Length: 368 Connection: Keep-Alive Cache-Control: no-cache -----------------------------7db3a8372017c Content-Disposition: form-data; name="title" Weird filename -----------------------------7db3a8372017c Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: application/octet-stream -----------------------------7db3a8372017c-- ================================================ FILE: test/fixture/http/special-chars-in-filename/xp-safari-5.http ================================================ POST /upload HTTP/1.1 Host: 192.168.56.1:8080 Referer: http://192.168.56.1:8080/ Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-US Origin: http://192.168.56.1:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykmaWSUbu697WN9TM Content-Length: 344 Connection: keep-alive ------WebKitFormBoundarykmaWSUbu697WN9TM Content-Disposition: form-data; name="title" Weird filename ------WebKitFormBoundarykmaWSUbu697WN9TM Content-Disposition: form-data; name="upload"; filename=" ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" Content-Type: text/plain ------WebKitFormBoundarykmaWSUbu697WN9TM-- ================================================ FILE: test/fixture/http/workarounds/missing-hyphens1.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ Content-Length: 189 ------TLV0SrKD4z1TRxRhAPUvZ Content-Disposition: form-data; name="upload"; filename="plain.txt" Content-Type: text/plain I am a simple plain text file ------TLV0SrKD4z1TRxRhAPUvZ ================================================ FILE: test/fixture/http/workarounds/missing-hyphens2.http ================================================ POST /upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary=----TLVx0SrKD4z1TRxRhAPUvZx Content-Length: 209 ------TLVx0SrKD4z1TRxRhAPUvZx Content-Disposition: form-data; name="upload"; filename="second-plaintext.txt" Content-Type: text/plain Some another plain text file, yeah! ------TLVx0SrKD4z1TRxRhAPUvZx ================================================ FILE: test/fixture/js/encoding.js ================================================ const menu_separator_png_http = [ { type: 'file', name: 'image', originalFilename: 'menu_separator.png', fixture: 'menu_separator.png', sha1: 'c845ca3ea794be298f2a1b79769b71939eaf4e54', }, ]; const beta_sticker_1_png_http = [ { type: 'file', name: 'sticker', originalFilename: 'beta-sticker-1.png', fixture: 'beta-sticker-1.png', sha1: '6abbcffd12b4ada5a6a084fe9e4584f846331bc4', }, ]; const blank_gif_http = [ { type: 'file', name: 'file', originalFilename: 'blank.gif', fixture: 'blank.gif', sha1: 'a1fdee122b95748d81cee426d717c05b5174fe96', }, ]; const binaryfile_tar_gz_http = [ { type: 'file', name: 'file', originalFilename: 'binaryfile.tar.gz', fixture: 'binaryfile.tar.gz', sha1: 'cfabe13b348e5e69287d677860880c52a69d2155', }, ]; const plain_txt_http = [ { type: 'file', name: 'file', originalFilename: 'plain.txt', fixture: 'plain.txt', sha1: 'b31d07bac24ac32734de88b3687dddb10e976872', }, ]; export { menu_separator_png_http, beta_sticker_1_png_http, blank_gif_http, binaryfile_tar_gz_http, plain_txt_http, }; ================================================ FILE: test/fixture/js/misc.js ================================================ const boundary_substring_json = [ { type: 'file', name: 'upload', originalFilename: 'plain.txt', fixture: 'boundary-substring-json', }, ]; const empty_http = []; const empty_urlencoded_http = []; const empty_multipart_http = []; const empty_multipart2_http = []; const _minimal_http = []; export { boundary_substring_json, empty_http, empty_urlencoded_http, empty_multipart_http, empty_multipart2_http, _minimal_http, }; ================================================ FILE: test/fixture/js/no-filename.js ================================================ const generic_http = [ { type: 'file', name: 'upload', originalFilename: '', fixture: 'generic', sha1: 'b31d07bac24ac32734de88b3687dddb10e976872', }, ]; const filename_name_http = [ { type: 'file', name: 'upload', originalFilename: 'plain.txt', fixture: 'filename-name', sha1: 'a47f7a8a7959f36c3f151ba8b0bd28f2d6b606e2', }, ]; export { generic_http, filename_name_http }; ================================================ FILE: test/fixture/js/preamble.js ================================================ const crlf_http = [ { type: 'file', name: 'upload', originalFilename: 'plain.txt', fixture: 'crlf', sha1: 'b31d07bac24ac32734de88b3687dddb10e976872', }, ]; const preamble_http = [ { type: 'file', name: 'upload', originalFilename: 'plain.txt', fixture: 'preamble', sha1: 'a47f7a8a7959f36c3f151ba8b0bd28f2d6b606e2', }, ]; export { crlf_http, preamble_http }; ================================================ FILE: test/fixture/js/special-chars-in-filename.js ================================================ const properFilename = 'funkyfilename.txt'; function expect(originalFilename, fixtureName) { return [ { type: 'field', name: 'title', originalFilename: properFilename, fixture: fixtureName, }, { type: 'file', name: 'upload', originalFilename, fixture: fixtureName, }, ]; } const osx_chrome_13_http = expect(' ? % * | " < > . ? ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'osx-chrome-13'); const osx_firefox_3_6_http = expect(' ? % * | " < > . ☃ ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'osx-firefox-3.6'); const xp_ie_7_http = expect(' ? % * | " < > . ☃ ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'xp-ie-7'); const xp_ie_8_http = expect(' ? % * | " < > . ☃ ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'xp-ie-8'); const lineSeparator = expect(null, 'line-separator'); export { osx_chrome_13_http, osx_firefox_3_6_http, // webkit as osx_firefox_3_6_http, // webkit as osx_safari_5_http, // webkit as xp_chrome_12_http, // webkit as xp_safari_5_http, // xp_ie_7_http, // xp_ie_8_http, // lineSeparator, }; ================================================ FILE: test/fixture/js/workarounds.js ================================================ const missing_hyphens1_http = [ { type: 'file', name: 'upload', originalFilename: 'plain.txt', fixture: 'missing-hyphens1', sha1: '8c26b82ec9107e99b3486844644e92558efe0c73', }, ]; const missing_hyphens2_http = [ { type: 'file', name: 'upload', originalFilename: 'second-plaintext.txt', fixture: 'missing-hyphens2', sha1: '798e39a4a1034232ed26e0aadd67f5d1ff10b966', }, ]; export { missing_hyphens1_http, missing_hyphens2_http }; ================================================ FILE: test/fixture/multipart.js ================================================ exports.rfc1867 = { boundary: 'AaB03x', raw: '--AaB03x\r\n' + 'content-disposition: form-data; name="field1"\r\n' + '\r\n' + 'Joe Blow\r\nalmost tricked you!\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + 'Content-Type: text/plain\r\n' + '\r\n' + '... contents of file1.txt ...\r\r\n' + '--AaB03x--\r\n', parts: [ { headers: { 'content-disposition': 'form-data; name="field1"', }, data: 'Joe Blow\r\nalmost tricked you!', }, { headers: { 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', 'Content-Type': 'text/plain', }, data: '... contents of file1.txt ...\r', }, ], }; exports['noTrailing\r\n'] = { boundary: 'AaB03x', raw: '--AaB03x\r\n' + 'content-disposition: form-data; name="field1"\r\n' + '\r\n' + 'Joe Blow\r\nalmost tricked you!\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + 'Content-Type: text/plain\r\n' + '\r\n' + '... contents of file1.txt ...\r\r\n' + '--AaB03x--', parts: [ { headers: { 'content-disposition': 'form-data; name="field1"', }, data: 'Joe Blow\r\nalmost tricked you!', }, { headers: { 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', 'Content-Type': 'text/plain', }, data: '... contents of file1.txt ...\r', }, ], }; exports.emptyHeader = { boundary: 'AaB03x', raw: '--AaB03x\r\n' + 'content-disposition: form-data; name="field1"\r\n' + ': foo\r\n' + '\r\n' + 'Joe Blow\r\nalmost tricked you!\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + 'Content-Type: text/plain\r\n' + '\r\n' + '... contents of file1.txt ...\r\r\n' + '--AaB03x--\r\n', expectError: true, }; ================================================ FILE: test/integration/file-write-stream-handler-option.test.js ================================================ import { existsSync, mkdirSync, createWriteStream, readdirSync, statSync, unlinkSync, createReadStream } from 'node:fs'; import { tmpdir } from 'node:os'; import { createServer, request as _request } from 'node:http'; import path, { join, dirname } from 'node:path'; import url from 'node:url'; import assert, { strictEqual, ok } from 'node:assert'; import formidable from '../../src/index.js'; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = 13533; const DEFAULT_UPLOAD_DIR = join( tmpdir(), 'test-store-files-option-default', ); const CUSTOM_UPLOAD_DIR = join( tmpdir(), 'test-store-files-option-custom', ); const CUSTOM_UPLOAD_FILE_PATH = join(CUSTOM_UPLOAD_DIR, 'test-file'); const testFilePath = join( dirname(__dirname), 'fixture', 'file', 'binaryfile.tar.gz', ); const createDirs = (dirs) => { dirs.forEach((dir) => { if (!existsSync(dir)) { mkdirSync(dir); } }); }; test('file write stream handler', (done) => { const server = createServer((req, res) => { createDirs([DEFAULT_UPLOAD_DIR, CUSTOM_UPLOAD_DIR]); const form = formidable({ uploadDir: DEFAULT_UPLOAD_DIR, fileWriteStreamHandler: () => createWriteStream(CUSTOM_UPLOAD_FILE_PATH), }); form.parse(req, (err, fields, files) => { strictEqual(Object.keys(files).length, 1); const file = files.file[0]; strictEqual(file.size, 301); strictEqual(typeof file.filepath, 'string'); const dirFiles = readdirSync(DEFAULT_UPLOAD_DIR); ok(dirFiles.length === 0); const uploadedFileStats = statSync(CUSTOM_UPLOAD_FILE_PATH); ok(uploadedFileStats.size === file.size); unlinkSync(CUSTOM_UPLOAD_FILE_PATH); res.end(); server.close(); done(); }); }); server.listen(PORT, (err) => { assert(!err, 'should not have error, but be falsey'); const request = _request({ port: PORT, method: 'POST', headers: { 'Content-Type': 'application/octet-stream', }, }); createReadStream(testFilePath).pipe(request); }); }); ================================================ FILE: test/integration/fixtures.test.js ================================================ /* eslint-disable global-require */ /* eslint-disable import/no-dynamic-require */ import { createReadStream } from 'node:fs'; import { createConnection } from 'node:net'; import { join } from 'node:path'; import { createServer } from 'node:http'; import { strictEqual } from 'node:assert'; import formidable from '../../src/index.js'; const PORT = 13534; const CWD = process.cwd(); const FIXTURES_HTTP = join(CWD, 'test', 'fixture', 'http'); const UPLOAD_DIR = join(CWD, 'test', 'tmp'); import * as encoding from "../fixture/js/encoding.js"; import * as misc from "../fixture/js/misc.js"; import * as noFilename from "../fixture/js/no-filename.js"; import * as preamble from "../fixture/js/preamble.js"; import * as workarounds from "../fixture/js/workarounds.js"; import * as specialCharsInFilename from "../fixture/js/special-chars-in-filename.js"; const fixtures= { encoding, misc, [`no-filename`]: noFilename, preamble, [`special-chars-in-filename`]: specialCharsInFilename, // workarounds, // todo uncomment this and make it work }; test('fixtures', (done) => { const server = createServer(); server.listen(PORT, findFixtures); function findFixtures() { const results = Object.entries(fixtures).map(([fixtureGroup, fixture]) => { return Object.entries(fixture).map(([k, v]) => { return v.map(details => { return { fixture: v, name: `${fixtureGroup}/${details.fixture}.http` }; }); }); }).flat(Infinity); testNext(results); } function testNext(results) { const fixtureWithName = results.shift(); if (!fixtureWithName) { server.close(); done(); return; } const fixtureName = fixtureWithName.name; const fixture = fixtureWithName.fixture; uploadFixture(fixtureName, (err, parts) => { try { if (err) { err.fixtureName = fixtureName; throw err; } fixture.forEach((expectedPart, i) => { const parsedPart = parts[i]; strictEqual(parsedPart.type, expectedPart.type); strictEqual(parsedPart.name, expectedPart.name); if (parsedPart.type === 'file') { const file = parsedPart.value; strictEqual(file.originalFilename, expectedPart.originalFilename, `${JSON.stringify([expectedPart, file])}`); if (expectedPart.sha1) { strictEqual( file.hash, expectedPart.sha1, `SHA1 error ${file.originalFilename} on ${file.filepath} ${JSON.stringify([expectedPart, file])}`, ); } } }); } catch (e) { server.close(); done(e); throw e; } testNext(results); }); } function uploadFixture(fixtureName, cb) { server.once('request', (req, res) => { const form = formidable({ uploadDir: UPLOAD_DIR, hashAlgorithm: 'sha1', }); function callback(...args) { const realCallback = cb; // eslint-disable-next-line no-param-reassign cb = function callbackFn() {}; realCallback(...args); } const parts = []; form .on('error', callback) .on('fileBegin', (name, value) => { parts.push({ type: 'file', name, value }); }) .on('field', (name, value) => { parts.push({ type: 'field', name, value }); }) .on('end', () => { res.end(); callback(null, parts); }); form.parse(req); }); const socket = createConnection(PORT); const fixturePath = join(FIXTURES_HTTP, fixtureName); const file = createReadStream(fixturePath); file.pipe(socket, { end: false }); socket.on('data', () => { socket.end(); }); } }); ================================================ FILE: test/integration/json.test.js ================================================ import { createServer, request as _request } from 'node:http'; import assert, { deepStrictEqual } from 'node:assert'; import formidable from '../../src/index.js'; const testData = { numbers: [1, 2, 3, 4, 5], nested: { key: 'val' }, }; const PORT = 13535; test('json', (done) => { const server = createServer((req, res) => { const form = formidable({ }); form.parse(req, (err, fields) => { deepStrictEqual(fields, { numbers: [1, 2, 3, 4, 5], nested: { key: 'val' }, }); res.end(); server.close(); done(); }); }); server.listen(PORT, (err) => { assert(!err, 'should not have error, but be falsey'); const request = _request({ port: PORT, method: 'POST', headers: { 'Content-Type': 'application/json', }, }); request.write(JSON.stringify(testData)); request.end(); }); }); ================================================ FILE: test/integration/octet-stream.test.js ================================================ import { readFileSync, createReadStream } from 'node:fs'; import { createServer, request as _request } from 'node:http'; import path, { join, dirname } from 'node:path'; import url from 'node:url'; import assert, { strictEqual, deepStrictEqual } from 'node:assert'; import formidable from '../../src/index.js'; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = 13536; const testFilePath = join( dirname(__dirname), 'fixture', 'file', 'binaryfile.tar.gz', ); test('octet stream', (done) => { const server = createServer((req, res) => { const form = formidable(); form.parse(req, (err, fields, files) => { strictEqual(Object.keys(files).length, 1); const file = files.file[0]; strictEqual(file.size, 301); const uploaded = readFileSync(file.filepath); const original = readFileSync(testFilePath); deepStrictEqual(uploaded, original); res.end(); server.close(); done(); }); }); server.listen(PORT, (err) => { assert(!err, 'should not have error, but be falsey'); const request = _request({ port: PORT, method: 'POST', headers: { 'Content-Type': 'application/octet-stream', }, }); createReadStream(testFilePath).pipe(request); }); }); ================================================ FILE: test/integration/store-files-option.test.js ================================================ import { existsSync, mkdirSync, WriteStream, statSync, unlinkSync, createReadStream } from 'node:fs'; import { tmpdir } from 'node:os'; import { createServer, request as _request } from 'http'; import assert, { strictEqual, ok } from 'node:assert'; import path, { join, dirname } from 'node:path'; import url from 'node:url'; import formidable from '../../src/index.js'; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = 13537; const DEFAULT_UPLOAD_DIR = join( tmpdir(), 'test-store-files-option-default', ); const CUSTOM_UPLOAD_FILE_PATH = join(DEFAULT_UPLOAD_DIR, 'test-file'); const testFilePath = join( dirname(__dirname), 'fixture', 'file', 'binaryfile.tar.gz', ); test('store files option', (done) => { const server = createServer((req, res) => { if (!existsSync(DEFAULT_UPLOAD_DIR)) { mkdirSync(DEFAULT_UPLOAD_DIR); } const form = formidable({ uploadDir: DEFAULT_UPLOAD_DIR, fileWriteStreamHandler: () => new WriteStream(CUSTOM_UPLOAD_FILE_PATH), }); form.parse(req, (err, fields, files) => { strictEqual(Object.keys(files).length, 1); const file = files.file[0]; strictEqual(file.size, 301); strictEqual(typeof file.filepath, 'string'); const uploadedFileStats = statSync(CUSTOM_UPLOAD_FILE_PATH); ok(uploadedFileStats.size === file.size); unlinkSync(CUSTOM_UPLOAD_FILE_PATH); res.end(); server.close(); done(); }); }); server.listen(PORT, (err) => { assert(!err, 'should not have error, but be falsey'); const request = _request({ port: PORT, method: 'POST', headers: { 'Content-Type': 'application/octet-stream', }, }); createReadStream(testFilePath).pipe(request); }); }); ================================================ FILE: test/standalone/connection-aborted.test.js ================================================ import assert from 'node:assert'; import { createServer } from 'node:http'; import { connect } from 'node:net'; import formidable from '../../src/index.js'; let server; let port = 13540; beforeEach(() => { server = createServer(); port += 1; }); afterEach(() => { return new Promise((resolve) => { if (server.listening) { server.close(() => resolve()); } else { resolve(); } }); }); test('connection aborted', (done) => { server.on('request', (req) => { const form = formidable(); let abortedReceived = false; form.on('aborted', () => { abortedReceived = true; }); form.on('error', () => { assert(abortedReceived, 'Error event should follow aborted'); }); form.on('end', () => { throw new Error('Unexpected "end" event'); }); form.parse(req, () => { assert( abortedReceived, 'from .parse() callback: Error event should follow aborted', ); done(); }); }); server.listen(port, 'localhost', () => { const client = connect(port); client.write( 'POST / HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Content-Length: 70\r\n' + 'Content-Type: multipart/form-data; boundary=foo\r\n\r\n', ); client.end(); }); }); ================================================ FILE: test/standalone/content-transfer-encoding.test.js ================================================ import { join } from 'node:path'; import { createServer, request } from 'node:http'; import { strictEqual } from 'node:assert'; import formidable from '../../src/index.js'; const UPLOAD_DIR = join(process.cwd(), 'test', 'tmp'); // OS choosing port const PORT = 13530; test('content transfer encoding', (done) => { const server = createServer(async (req, res) => { const form = formidable({ uploadDir: UPLOAD_DIR }); form.on('end', () => { throw new Error('Unexpected "end" event'); }); form.on('error', (e) => { res.writeHead(500); res.end(e.message); }); try { await form.parse(req); } catch (formidableError) { } }); server.listen(PORT, () => { const chosenPort = server.address().port; const body = '--foo\r\n' + 'Content-Disposition: form-data; name="file1"; filename="file1"\r\n' + 'Content-Type: application/octet-stream\r\n' + '\r\nThis is the first file\r\n' + '--foo\r\n' + 'Content-Type: application/octet-stream\r\n' + 'Content-Disposition: form-data; name="file2"; filename="file2"\r\n' + 'Content-Transfer-Encoding: unknown\r\n' + '\r\nThis is the second file\r\n' + '--foo--\r\n'; const req = request({ method: 'POST', port: chosenPort, headers: { 'Content-Length': body.length, 'Content-Type': 'multipart/form-data; boundary=foo', }, }); req.on('response', (res) => { strictEqual(res.statusCode, 500); res.on('data', () => {}); res.on('end', () => { server.close(); done(); }); }); req.end(body); }); }); ================================================ FILE: test/standalone/issue-46.test.js ================================================ import { createServer, request } from "node:http"; import { ok, strictEqual } from "node:assert"; import { Buffer } from 'node:buffer'; import formidable from "../../src/index.js"; // OS choosing port const PORT = 13531; const type = "multipart/related; boundary=a7a65b99-8a61-4e2c-b149-f73a3b35f923" const body = "LS1hN2E2NWI5OS04YTYxLTRlMmMtYjE0OS1mNzNhM2IzNWY5MjMNCmNvbnRlbnQtZGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iZm9vIg0KDQpiYXJyeQ0KLS1hN2E2NWI5OS04YTYxLTRlMmMtYjE0OS1mNzNhM2IzNWY5MjMtLQ"; const buffer = Buffer.from(body, 'base64url'); test("issue 46", (done) => { const server = createServer(async (req, res) => { // Parse form and write results to response. const form = formidable(); const [fields] = await form.parse(req); ok(fields.foo, 'should have fields.foo === barry'); strictEqual(fields.foo[0], 'barry'); res.end(); server.close(() => { done(); }); }); server.listen(PORT, () => { const chosenPort = server.address().port; const url = `http://localhost:${chosenPort}`; const req = request(url, { method: "POST", headers: { "Content-Type": type, "Content-Length": buffer.byteLength } }); req.write(buffer); req.end(); }); }); ================================================ FILE: test/standalone/keep-alive-error.test.js ================================================ /* eslint-disable max-nested-callbacks */ import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { createConnection } from 'node:net'; import formidable from '../../src/index.js'; let server; let port = 13539; let ok = 0; let errors = 0; beforeEach(() => { server = createServer(); ok = 0; errors = 0; port += 1; }); afterEach(() => { return new Promise((resolve) => { if (server.listening) { server.close(() => resolve()); } else { resolve(); } }); }); test('keep alive error', (done) => { server.on('request', async (req, res) => { const form = formidable(); form.on('error', () => { errors += 1; res.writeHead(500); res.end(); }); form.on('end', () => { ok += 1; res.writeHead(200); res.end(); }); try { await form.parse(req); // for client two assert.strictEqual(ok, 1, `should "ok" count === 1, has: ${ok}`); done(); } catch (formidableError) { assert.strictEqual(errors, 1, `should "errors" === 1, has: ${errors}`); const clientTwo = createConnection(port); // correct post upload (with hyphens) clientTwo.write( 'POST /upload-test HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Connection: keep-alive\r\n' + 'Content-Type: multipart/form-data; boundary=----aaa\r\n' + 'Content-Length: 13\r\n\r\n' + '------aaa--\r\n', ); clientTwo.end(); } }); server.listen(port, () => { const client = createConnection(port); // first send malformed (boundary / hyphens) post upload client.write( 'POST /upload-test HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Connection: keep-alive\r\n' + 'Content-Type: multipart/form-data; boundary=----aaa\r\n' + 'Content-Length: 10011\r\n\r\n' + '------XXX\n\r', ); setTimeout(() => { const buf = Buffer.alloc(10000); buf.fill('a'); client.write(buf); client.end(); }, 150); }); }); ================================================ FILE: test/tools/base64.html ================================================ Convert a file to a base64 request




Don't forget to save the output with windows (CRLF) line endings!

================================================ FILE: test/unit/custom-plugins.test.js ================================================ /* eslint-disable no-underscore-dangle */ import { join } from 'node:path'; import Koa from 'koa'; import request from 'supertest'; import { formidable, json, octetstream, multipart, errors } from '../../src/index.js'; function createServer(options, handler) { const app = new Koa(); app.use(async (ctx, next) => { const form = formidable(options); await handler(ctx, form); await next(); }); return app; } function fromFixtures(...args) { return join(process.cwd(), 'test', 'fixture', ...args); } // function makeRequest(server, options) { // server.listen(0, () => { // const chosenPort = server.address().port; // const url = `http://localhost:${chosenPort}`; // const method = 'POST'; // const opts = { // ...options, // port: chosenPort, // url, // method, // }; // return http.request(opts); // }); // } // function onDone({ server, form, req, res }) { // form.parse(req, (err, fields) => { // assert.strictEqual(fields.qux, 'zaz'); // setTimeout(() => { // res.end(); // server.close(); // }, 200); // }); // } // ! tests test('should call 3 custom and 1 builtin plugins, when .parse() is called', async () => { const server = createServer({ enabledPlugins: [json] }, (ctx, form) => { form.on('plugin', () => { ctx.__pluginsCount = ctx.__pluginsCount || 0; ctx.__pluginsCount += 1; }); form.on('end', () => { ctx.__ends = 1; expect(ctx.__customPlugin1).toBe(111); expect(ctx.__customPlugin2).toBe(222); expect(ctx.__customPlugin3).toBe(333); ctx.__ends = 2; const len = form._plugins.length; expect(len).toBe(4); }); form.use(() => { ctx.__customPlugin1 = 111; }); form.use(() => { ctx.__customPlugin2 = 222; }); form.use(() => { ctx.__customPlugin3 = 333; }); form.parse(ctx.req, (err, fields) => { expect(fields.qux).toBe('zaz'); expect(fields.a).toBe('bbb'); expect(ctx.__pluginsCount).toBe(4); }); }); await new Promise((resolve, reject) => { request(server.callback()) .post('/') .type('application/json') .send({ qux: 'zaz', a: 'bbb' }) .end((err) => (err ? reject(err) : resolve())); }); }); test('.parse throw error when some plugin fail', async () => { const server = createServer( { enabledPlugins: [octetstream, json] }, async (ctx, form) => { // const failedIsOkay = false; // ! not emitted? // form.on('file', () => { // ctx.__onFileCalled = ctx.__onFileCalled || 0; // ctx.__onFileCalled += 1; // }); form.on('plugin', () => { ctx.__pluginsCount = ctx.__pluginsCount || 0; ctx.__pluginsCount += 1; }); form.once('error', () => { throw new Error('error event should not be fired when plugin throw'); }); form.use(() => { throw new Error('custom plugin err'); }); let res = null; try { await form.parse(ctx.req); } catch (err) { expect(err.code).toBe(errors.pluginFailed); expect(err.httpCode).toBe(500); expect(form._plugins.length).toBe(3); expect(ctx.__pluginsCount).toBe(2); expect(ctx.__pluginsResults).toBe(undefined); res = err; } if (!res) { throw new Error( '^ .parse should throw & be caught with the try/catch ^', ); } }, ); return new Promise((resolve, reject) => { request(server.callback()) .post('/') .type('application/octet-stream') .attach('bin', fromFixtures('file', 'binaryfile.tar.gz')) .end((err) => (err ? reject(err) : resolve())); }); }); test('multipart plugin fire `error` event when malformed boundary', async () => { const server = createServer( { enabledPlugins: [json, multipart] }, (ctx, form) => { let failedIsOkay = false; form.once('error', (err) => { expect(form._plugins.length).toBe(2); expect(err).toBeTruthy(); expect(err.message).toMatch(/bad content-type header/); expect(err.message).toMatch(/no multipart boundary/); failedIsOkay = true; }); // Should never be called when `error` form.on('end', () => { throw new Error('should not fire `end` event when error'); }); form.parse(ctx.req, (err) => { expect(err).toBeTruthy(); expect(failedIsOkay).toBe(true); }); }, ); // 'Content-Length': 1111111, // 'content-Disposition': 'form-data; bouZndary=', // 'Content-Type': 'multipart/form-data; bouZndary=', await new Promise((resolve, reject) => { request(server.callback()) .post('/') .type('multipart/form-data') .set('Content-Length', 11111111) .set('Content-Disposition', 'form-data; bouZndary=') .set('Content-Type', 'multipart/form-data; bouZndary=') .end((err) => (err ? reject(err) : resolve())); }); }); test('formidable() throw if not at least 1 built-in plugin in options.enabledPlugins', () => { try { formidable({ enabledPlugins: [] }); } catch (err) { expect(err).toBeTruthy(); expect(err.message).toMatch(/expect at least 1 enabled builtin/); } }); ================================================ FILE: test/unit/formidable.test.js ================================================ /* eslint-disable max-statements */ /* eslint-disable no-underscore-dangle */ import {jest} from '@jest/globals'; import Stream from 'node:stream'; import http from 'node:http'; import path from 'node:path'; import formidable from '../../src/index.js'; import * as mod from '../../src/index.js'; function requestStub() { return Object.assign(new Stream (), { pause() {}, resume() {}, }); } function getForm(name, opts) { return name === 'formidable' ? formidable(opts) : new mod[name](opts); } function makeHeader(originalFilename) { return `Content-Disposition: form-data; name="upload"; filename="${originalFilename}"`; } ['IncomingForm', 'Formidable', 'formidable'].forEach((name) => { test(`${name}#_getFileName with regular characters`, () => { const originalFilename = 'foo.txt'; const form = getForm(name); expect(form._getFileName(makeHeader(originalFilename))).toBe('foo.txt'); }); test(`${name}#_getFileName with unescaped quote`, () => { const originalFilename = 'my".txt'; const form = getForm(name); expect(form._getFileName(makeHeader(originalFilename))).toBe('my".txt'); }); test(`${name}#_getFileName with escaped quote`, () => { const originalFilename = 'my%22.txt'; const form = getForm(name); expect(form._getFileName(makeHeader(originalFilename))).toBe('my".txt'); }); test(`${name}#_getFileName with bad quote and additional sub-header`, () => { const originalFilename = 'my".txt'; const form = getForm(name); const header = `${makeHeader(originalFilename)}; foo="bar"`; expect(form._getFileName(header)).toBe(originalFilename); }); test(`${name}#_getFileName with semicolon`, () => { const originalFilename = 'my;.txt'; const form = getForm(name); expect(form._getFileName(makeHeader(originalFilename))).toBe('my;.txt'); }); test(`${name}#_getFileName with utf8 character`, () => { const originalFilename = 'my☃.txt'; const form = getForm(name); expect(form._getFileName(makeHeader(originalFilename))).toBe('my☃.txt'); }); test(`${name}#_getNewName strips harmful characters from extension when keepExtensions`, () => { const form = getForm(name, { keepExtensions: true }); const getBasename = (part) => path.basename(form._getNewName(part)); // tests below assume baseline hexoid 25 chars + a few more for the extension let basename = getBasename('fine.jpg?foo=bar'); expect(basename).toHaveLength(29); let ext = path.extname(basename); expect(ext).toBe('.jpg'); basename = getBasename('fine-no-ext?foo=qux'); expect(basename).toHaveLength(25); ext = path.extname(basename); expect(ext).toBe(''); basename = getBasename({ originalFilename: 'super.cr2+dsad' }); expect(basename).toHaveLength(29); ext = path.extname(basename); expect(ext).toBe('.cr2'); basename = getBasename({ originalFilename: 'super.gz' }); expect(basename).toHaveLength(28); ext = path.extname(basename); expect(ext).toBe('.gz'); basename = getBasename('file.aAa'); expect(basename).toHaveLength(29); ext = path.extname(basename); expect(ext).toBe('.aAa'); basename = getBasename('file#!@#koh.QxZs?sa=1'); expect(basename).toHaveLength(30); ext = path.extname(basename); expect(ext).toBe('.QxZs'); basename = getBasename('test.pdf.jqlnn.png'); expect(basename).toHaveLength(35); ext = path.extname(basename); expect(ext).toBe('.jqlnn'); basename = getBasename('test. { const form = getForm(name, { }); const req = new http.ClientRequest(); req.headers = { 'content-length': '8', 'content-type': 'multipart/form-data; boundary=----TLVx', }; form.parse(req, (error, fields) => { expect(Array.isArray(fields.a)).toBe(true); expect(fields.a[0]).toBe('1'); expect(fields.a[1]).toBe('2'); }); form.emit('field', 'a', '1'); form.emit('field', 'a', '2'); form.emit('end'); }); test(`${name}#_Nested array parameters support`, () => { const form = getForm(name, { }); const req = new http.ClientRequest(); req.headers = { 'content-length': '8', 'content-type': 'multipart/form-data; boundary=----TLVx', }; form.parse(req, (error, fields) => { expect(Array.isArray(fields[`a[0]`])).toBe(true); expect(fields[`a[0]`][0]).toBe('a'); expect(fields[`a[0]`][1]).toBe('b'); expect(fields[`a[1]`][0]).toBe('c'); }); form.emit('field', 'a[0]', 'a'); form.emit('field', 'a[0]', 'b'); form.emit('field', 'a[1]', 'c'); form.emit('end'); }); test(`${name}#_Object parameters support`, () => { const form = getForm(name, { }); const req = new http.ClientRequest(); req.headers = { 'content-length': '8', 'content-type': 'multipart/form-data; boundary=----TLVx', }; form.parse(req, (error, fields) => { expect(fields[`a[x]`][0]).toBe('1'); expect(fields[`a[y]`][0]).toBe('2'); }); form.emit('field', 'a[x]', '1'); form.emit('field', 'a[y]', '2'); form.emit('end'); }); xtest(`${name}#_Nested object parameters support`, () => { const form = getForm(name, { }); const req = new http.ClientRequest(); req.headers = { 'content-length': '8', 'content-type': 'multipart/form-data; boundary=----TLVx', }; form.parse(req, (error, fields) => { expect(fields.a.l1.k1).toBe('2'); expect(fields.a.l1.k2).toBe('3'); expect(fields.a.l2.k3).toBe('5'); }); form.emit('field', 'a[l1][k1]', '2'); form.emit('field', 'a[l1][k2]', '3'); form.emit('field', 'a[l2][k3]', '5'); form.emit('end'); }); describe(`${name}#_onPart`, () => { describe('when not allow empty files', () => { describe('when file is empty', () => { test('emits error when part is received', (done) => { const form = getForm(name, { allowEmptyFiles: false, }); form.req = requestStub(); const part = new Stream(); part.mimetype = 'text/plain'; // eslint-disable-next-line max-nested-callbacks form.on('error', (error) => { expect(error.message).toBe( 'options.allowEmptyFiles is false, file size should be greater than 0', ); done(); }); form.onPart(part).then (function () { part.emit('end'); form.emit('end'); }); }); }); describe('when file is not empty', () => { test('not emits error when part is received', () => { const form = getForm(name, { allowEmptyFiles: false, }); const formEmitSpy = jest.spyOn(form, 'emit'); const part = new Stream(); part.mimetype = 'text/plain'; form.onPart(part); part.emit('data', Buffer.alloc(1)); part.emit('end'); form.emit('end'); expect(formEmitSpy).not.toBeCalledWith('error'); }); }); }); describe('when allow empty files', () => { test('not emits error when part is received', () => { const form = getForm(name, { }); const formEmitSpy = jest.spyOn(form, 'emit'); const part = new Stream(); part.mimetype = 'text/plain'; form.onPart(part); part.emit('end'); form.emit('end'); expect(formEmitSpy).not.toBeCalledWith('error'); }); }); describe('when file uploaded size is inferior than minFileSize option', () => { test('emits error when part is received', (done) => { const form = getForm(name, { minFileSize: 5 }); const part = new Stream(); const req = requestStub(); part.mimetype = 'text/plain'; form.on('error', (error) => { expect(error.message).toBe( 'options.minFileSize (5 bytes) inferior, received 4 bytes of file data', ); done(); }); form.req = req; form.onPart(part).then(function () { part.emit('data', Buffer.alloc(4)); part.emit('end'); form.emit('end'); }); }); }); describe('when file uploaded size is superior than minFileSize option', () => { test('not emits error when part is received', () => { const form = getForm(name, { minFileSize: 10 }); const formEmitSpy = jest.spyOn(form, 'emit'); const part = new Stream(); part.mimetype = 'text/plain'; form.onPart(part); part.emit('data', Buffer.alloc(11)); part.emit('end'); form.emit('end'); expect(formEmitSpy).not.toBeCalledWith('error'); }); }); describe('when there are more fields than maxFields', () => { test('emits error', (done) => { const form = getForm(name, { maxFields: 1, }); form.on('error', (error) => { expect(error.message.includes('maxFields')).toBe(true); done(); }); form.emit('field', 'a', '1'); form.emit('field', 'b', '2'); form.emit('end'); }); }); }); test(`${name}: maxFiles exceeded emits error`, (done) => { const form = getForm(name, { maxFiles: 1 }); form.req = requestStub(); form.on('error', (error) => { expect(error.message.includes('maxFiles')).toBe(true); done(); }); const part1 = new Stream(); part1.mimetype = 'text/plain'; const part2 = new Stream(); part2.mimetype = 'text/plain'; form.onPart(part1).then(() => { part1.emit('data', Buffer.alloc(1)); part1.emit('end'); form.onPart(part2); }); }); test(`${name}: maxFiles defaults to 1000`, () => { const form = getForm(name); expect(form.options.maxFiles).toBe(1000); }); // test(`${name}: use custom options.originalFilename instead of form._uploadPath`, () => { // const form = getForm(name, { // originalFilename: (_) => path.join(__dirname, 'sasa'), // }); // }); test(`${name}#_joinDirectoryName blocks standard directory traversal`, () => { const form = getForm(name, { uploadDir: '/tmp/uploads' }); const result = form._joinDirectoryName('../../../etc/passwd'); expect(result).toBe(path.join('/tmp/uploads', 'invalid-name')); }); test(`${name}#_joinDirectoryName blocks sibling directory prefix collision`, () => { const form = getForm(name, { uploadDir: '/tmp/uploads' }); const result = form._joinDirectoryName('../uploads-evil/payload.sh'); expect(result).toBe(path.join('/tmp/uploads', 'invalid-name')); }); test(`${name}#_joinDirectoryName allows subdirectories within uploadDir`, () => { const form = getForm(name, { uploadDir: '/tmp/uploads' }); const result = form._joinDirectoryName('images/photo.jpg'); expect(result).toBe(path.resolve('/tmp/uploads', 'images/photo.jpg')); }); test(`${name}#_joinDirectoryName blocks name resolving to uploadDir itself`, () => { const form = getForm(name, { uploadDir: '/tmp/uploads' }); const result = form._joinDirectoryName('.'); expect(result).toBe(path.join('/tmp/uploads', 'invalid-name')); }); }); ================================================ FILE: test/unit/multipart-parser.test.js ================================================ import { MultipartParser } from '../../src/index.js'; test('on constructor', () => { const parser = new MultipartParser(); expect(parser.boundary).toBeNull(); expect(parser.state).toBe(0); expect(parser.flags).toBe(0); expect(parser.boundaryChars).toBeNull(); expect(parser.index).toBeNull(); expect(parser.lookbehind).toBeNull(); expect(parser.constructor.name).toBe('MultipartParser'); }); test('initWithBoundary', () => { const boundary = 'abc'; const parser = new MultipartParser(); parser.initWithBoundary(boundary); expect(Array.prototype.slice.call(parser.boundary)).toMatchObject([ 13, 10, 45, 45, 97, 98, 99, ]); expect(parser.state).toBe(MultipartParser.STATES.START); expect(parser.boundaryChars).toMatchObject({ 10: true, 13: true, 45: true, 97: true, 98: true, 99: true, }); }); test('initWithBoundary failing', () => { const parser = new MultipartParser(); const boundary = 'abc'; const buffer = Buffer.alloc(5); parser.initWithBoundary(boundary); buffer.write('--ad', 0); expect(parser.bufferLength).toBe(0); parser.write(buffer); expect(parser.bufferLength).toBe(5); }); test('on .end() throwing', () => { const parser = new MultipartParser(); parser.once('error', () => {}); const res = parser.end(); expect(res.state).toBe(0); // expect(() => { parser.end() }).toThrow(/MultipartParser/); // expect(() => { parser.end() }).toThrow(/stream ended unexpectedly/); // expect(() => { parser.end() }).toThrow(parser.explain()); }); test('on .end() successful', () => { const parser = new MultipartParser(); parser.state = MultipartParser.STATES.END; const res = parser.end(); expect(res.state).toBe(12); }); ================================================ FILE: test/unit/persistent-file.disabled-test.js ================================================ import {jest} from '@jest/globals'; import fs from 'node:fs'; import PersistentFile from '../../src/PersistentFile.js'; const mockFs = fs; const now = new Date(); const file = new PersistentFile({ size: 1024, filepath: '/tmp/cat.png', name: 'cat.png', type: 'image/png', lastModifiedDate: now, originalFilename: 'cat.png', newFilename: 'dff1d2eaab9752165764dcd00', mimetype: 'image/png', }); const mockFn = jest.fn(); jest.mock('fs', () => { return { ...mockFs, unlink: mockFn, }; }); describe('PersistentFile', () => { test('toJSON()', () => { const obj = file.toJSON(); const len = Object.keys(obj).length; expect(obj.filepath).toBe('/tmp/cat.png'); expect(obj.mimetype).toBe('image/png'); expect(obj.originalFilename).toBe('cat.png'); }); test('toString()', () => { const result = file.toString(); expect(result).toBe('PersistentFile: dff1d2eaab9752165764dcd00, Original: cat.png, Path: /tmp/cat.png') }); test('destroy()', () => { file.open(); file.destroy(); // eslint-disable-next-line global-require expect(mockFn).toBeCalled(); }); }); ================================================ FILE: test/unit/querystring-parser.test.js ================================================ import { QuerystringParser } from '../../src/index.js'; test('on constructor', () => { const parser = new QuerystringParser(); expect(parser.constructor.name).toBe('QuerystringParser'); }); // ! skip // test(function end => // const FIELDS = { a: ['b', { c: 'd' }], e: 'f' }; // gently.expect(GENTLY.hijacked.querystring, 'parse', (str) => { // assert.equal(str, parser.buffer); // return FIELDS; // }); // gently.expect(parser, 'onField', Object.keys(FIELDS).length, (key, val) => { // assert.deepEqual(FIELDS[key], val); // }); // gently.expect(parser, 'onEnd'); // parser.buffer = 'my buffer'; // parser.end(); // assert.equal(parser.buffer, ''); // }); ================================================ FILE: test/unit/volatile-file.test.js ================================================ import {jest} from '@jest/globals'; import VolatileFile from '../../src/VolatileFile.js'; describe('VolatileFile', () => { let file; let writeStreamInstanceMock; let writeStreamMock; beforeEach(() => { writeStreamInstanceMock = { on: jest.fn(), destroy: jest.fn(), end: jest.fn(), write: jest.fn(), }; writeStreamMock = jest.fn(() => writeStreamInstanceMock); file = new VolatileFile({ xname: 'cat.png', originalFilename: 'cat.png', mimetype: 'image/png', createFileWriteStream: writeStreamMock, }); file.open(); }); test('open()', (done) => { const error = new Error('test'); file.on('error', (err) => { expect(err).toBe(error); done(); }); file.emit('error', error); expect(writeStreamMock).toBeCalled(); expect(writeStreamInstanceMock.on).toBeCalledWith( 'error', expect.any(Function), ); }); test('toJSON()', () => { const obj = file.toJSON(); expect(obj.mimetype).toBe('image/png'); expect(obj.originalFilename).toBe('cat.png'); }); test('toString()', () => { expect(file.toString()).toBe('VolatileFile: cat.png'); }); test('write()', (done) => { const buffer = Buffer.alloc(5); writeStreamInstanceMock.write.mockImplementationOnce((writeBuffer, cb) => { expect(buffer).toBe(writeBuffer); cb(); }); file.write(buffer, () => { done(); }); }); test('end()', (done) => { writeStreamInstanceMock.end.mockImplementationOnce((cb) => { cb(); }); const fileEmitSpy = jest.spyOn(file, 'emit'); file.end(() => done()); expect(fileEmitSpy).toBeCalledWith('end'); }); test('destroy()', () => { file.destroy(); expect(writeStreamInstanceMock.destroy).toBeCalled(); }); }); ================================================ FILE: test-legacy/README.md ================================================ These tests were deleted due to failures when removing the gently lib. previously in `test/legacy` ================================================ FILE: test-legacy/common.js ================================================ const path = require('path'); const fs = require('fs'); exports.lib = path.join(__dirname, '../../lib'); global.assert = require('assert'); global.TEST_PORT = 13532; global.TEST_FIXTURES = path.join(__dirname, '../fixture'); global.TEST_TMP = path.join(__dirname, '../tmp'); // Stupid new feature in node that complains about gently attaching too many // listeners to process 'exit'. This is a workaround until I can think of a // better way to deal with this. if (process.setMaxListeners) { process.setMaxListeners(10000); } ================================================ FILE: test-legacy/integration/test-multipart-parser.js ================================================ const common = require('../common'); const CHUNK_LENGTH = 10; const multipartParser = require(`${common.lib}/multipart_parser`); const { MultipartParser } = multipartParser; const parser = new MultipartParser(); const fixtures = require(`${TEST_FIXTURES}/multipart`); Object.keys(fixtures).forEach(function(name) { const fixture = fixtures[name]; const buffer = Buffer.alloc(Buffer.byteLength(fixture.raw, 'binary')); let offset = 0; let chunk; let nparsed; const parts = []; let part = null; let headerField; let headerValue; let endCalled = ''; parser.initWithBoundary(fixture.boundary); parser.onPartBegin = function() { part = { headers: {}, data: '' }; parts.push(part); headerField = ''; headerValue = ''; }; parser.onHeaderField = function(b, start, end) { headerField += b.toString('ascii', start, end); }; parser.onHeaderValue = function(b, start, end) { headerValue += b.toString('ascii', start, end); }; parser.onHeaderEnd = function() { part.headers[headerField] = headerValue; headerField = ''; headerValue = ''; }; parser.onPartData = function(b, start, end) { const str = b.toString('ascii', start, end); part.data += b.slice(start, end); }; parser.onEnd = function() { endCalled = true; }; buffer.write(fixture.raw, 0, undefined, 'binary'); while (offset < buffer.length) { if (offset + CHUNK_LENGTH < buffer.length) { chunk = buffer.slice(offset, offset + CHUNK_LENGTH); } else { chunk = buffer.slice(offset, buffer.length); } offset += CHUNK_LENGTH; nparsed = parser.write(chunk); if (nparsed != chunk.length) { if (fixture.expectError) { return; } puts('-- ERROR --'); p(chunk.toString('ascii')); throw new Error( `${chunk.length} bytes written, but only ${nparsed} bytes parsed!`, ); } } if (fixture.expectError) { throw new Error('expected parse error did not happen'); } assert.ok(endCalled); assert.deepEqual(parts, fixture.parts); }); ================================================ FILE: test-legacy/simple/test-file.js ================================================ const common = require('../common'); const WriteStreamStub = GENTLY.stub('fs', 'WriteStream'); const File = require(`${common.lib}/file`); const { EventEmitter } = require('events'); let file; let gently; function test(test) { gently = new Gently(); file = new File(); test(); gently.verify(test.name); } test(function constructor() { assert.ok(file instanceof EventEmitter); assert.strictEqual(file.size, 0); assert.strictEqual(file.path, null); assert.strictEqual(file.name, null); assert.strictEqual(file.type, null); assert.strictEqual(file.lastModifiedDate, null); assert.strictEqual(file._writeStream, null); (function testSetProperties() { const file2 = new File({ foo: 'bar' }); assert.equal(file2.foo, 'bar'); })(); }); test(function open() { let WRITE_STREAM; file.path = '/foo'; gently.expect(WriteStreamStub, 'new', function(path) { WRITE_STREAM = this; assert.strictEqual(path, file.path); }); file.open(); assert.strictEqual(file._writeStream, WRITE_STREAM); }); test(function write() { const BUFFER = { length: 10 }; let CB_STUB; const CB = function() { CB_STUB.apply(this, arguments); }; file._writeStream = {}; gently.expect(file._writeStream, 'write', function(buffer, cb) { assert.strictEqual(buffer, BUFFER); gently.expect(file, 'emit', function(event, bytesWritten) { assert.ok(file.lastModifiedDate instanceof Date); assert.equal(event, 'progress'); assert.equal(bytesWritten, file.size); }); CB_STUB = gently.expect(function writeCb() { assert.equal(file.size, 10); }); cb(); gently.expect(file, 'emit', function(event, bytesWritten) { assert.equal(event, 'progress'); assert.equal(bytesWritten, file.size); }); CB_STUB = gently.expect(function writeCb() { assert.equal(file.size, 20); }); cb(); }); file.write(BUFFER, CB); }); test(function end() { let CB_STUB; const CB = function() { CB_STUB.apply(this, arguments); }; file._writeStream = {}; gently.expect(file._writeStream, 'end', function(cb) { gently.expect(file, 'emit', function(event) { assert.equal(event, 'end'); }); CB_STUB = gently.expect(function endCb() {}); cb(); }); file.end(CB); }); ================================================ FILE: test-legacy/simple/test-incoming-form.js ================================================ const common = require('../common'); const MultipartParserStub = GENTLY.stub( './multipart_parser', 'MultipartParser', ); const QuerystringParserStub = GENTLY.stub( './querystring_parser', 'QuerystringParser', ); const EventEmitterStub = GENTLY.stub('events', 'EventEmitter'); const StreamStub = GENTLY.stub('stream', 'Stream'); const FileStub = GENTLY.stub('./file'); const formidable = require(`${common.lib}/index`); const { IncomingForm } = formidable; const events = require('events'); const fs = require('fs'); const path = require('path'); const fixtures = require(`${TEST_FIXTURES}/multipart`); let form; let gently; function test(test) { gently = new Gently(); gently.expect(EventEmitterStub, 'call'); form = new IncomingForm(); test(); gently.verify(test.name); } test(function constructor() { assert.strictEqual(form.error, null); assert.strictEqual(form.ended, false); assert.strictEqual(form.type, null); assert.strictEqual(form.headers, null); assert.strictEqual(form.keepExtensions, false); // Can't assume dir === '/tmp' for portability // assert.strictEqual(form.uploadDir, '/tmp'); // Make sure it is a directory instead assert.doesNotThrow(function() { assert(fs.statSync(form.uploadDir).isDirectory()); }); assert.strictEqual(form.encoding, 'utf-8'); assert.strictEqual(form.bytesReceived, null); assert.strictEqual(form.bytesExpected, null); assert.strictEqual(form.maxFieldsSize, 20 * 1024 * 1024); assert.strictEqual(form._parser, null); assert.strictEqual(form._flushing, 0); assert.strictEqual(form._fieldsSize, 0); assert.ok(form instanceof EventEmitterStub); assert.equal(form.constructor.name, 'IncomingForm'); (function testSimpleConstructor() { gently.expect(EventEmitterStub, 'call'); const form = IncomingForm(); assert.ok(form instanceof IncomingForm); })(); (function testSimpleConstructorShortcut() { gently.expect(EventEmitterStub, 'call'); const form = formidable(); assert.ok(form instanceof IncomingForm); })(); }); test(function parse() { const REQ = { headers: {} }; const emit = {}; gently.expect(form, 'writeHeaders', function(headers) { assert.strictEqual(headers, REQ.headers); }); const EVENTS = ['error', 'aborted', 'data', 'end']; gently.expect(REQ, 'on', EVENTS.length, function(event, fn) { assert.equal(event, EVENTS.shift()); emit[event] = fn; return this; }); form.parse(REQ); (function testPause() { gently.expect(REQ, 'pause'); assert.strictEqual(form.pause(), true); })(); (function testPauseCriticalException() { form.ended = false; const ERR = new Error('dasdsa'); gently.expect(REQ, 'pause', function() { throw ERR; }); gently.expect(form, '_error', function(err) { assert.strictEqual(err, ERR); }); assert.strictEqual(form.pause(), false); })(); (function testPauseHarmlessException() { form.ended = true; const ERR = new Error('dasdsa'); gently.expect(REQ, 'pause', function() { throw ERR; }); assert.strictEqual(form.pause(), false); })(); (function testResume() { gently.expect(REQ, 'resume'); assert.strictEqual(form.resume(), true); })(); (function testResumeCriticalException() { form.ended = false; const ERR = new Error('dasdsa'); gently.expect(REQ, 'resume', function() { throw ERR; }); gently.expect(form, '_error', function(err) { assert.strictEqual(err, ERR); }); assert.strictEqual(form.resume(), false); })(); (function testResumeHarmlessException() { form.ended = true; const ERR = new Error('dasdsa'); gently.expect(REQ, 'resume', function() { throw ERR; }); assert.strictEqual(form.resume(), false); })(); (function testEmitError() { const ERR = new Error('something bad happened'); gently.expect(form, '_error', function(err) { assert.strictEqual(err, ERR); }); emit.error(ERR); })(); (function testEmitAborted() { gently.expect(form, 'emit', function(event) { assert.equal(event, 'aborted'); }); gently.expect(form, '_error'); emit.aborted(); })(); (function testEmitData() { const BUFFER = [1, 2, 3]; gently.expect(form, 'write', function(buffer) { assert.strictEqual(buffer, BUFFER); }); emit.data(BUFFER); })(); (function testEmitEnd() { form._parser = {}; (function testWithError() { const ERR = new Error('haha'); gently.expect(form._parser, 'end', function() { return ERR; }); gently.expect(form, '_error', function(err) { assert.strictEqual(err, ERR); }); emit.end(); })(); (function testWithoutError() { gently.expect(form._parser, 'end'); emit.end(); })(); (function testAfterError() { form.error = true; emit.end(); })(); })(); (function testWithCallback() { gently.expect(EventEmitterStub, 'call'); const form = new IncomingForm(); const REQ = { headers: {} }; const parseCalled = 0; gently.expect(form, 'on', 4, function(event, fn) { if (event == 'field') { fn('field1', 'foo'); fn('field1', 'bar'); fn('field2', 'nice'); } if (event == 'file') { fn('file1', '1'); fn('file1', '2'); fn('file2', '3'); } if (event == 'end') { fn(); } return this; }); gently.expect(form, 'writeHeaders'); gently.expect(REQ, 'on', 4, function() { return this; }); const parseCbOk = function(err, fields, files) { // assert.deepEqual(fields, {field1: ['foo', 'bar'], field2: 'nice'}); assert.deepEqual(files, { file1: '2', file2: '3' }); }; form.parse(REQ, parseCbOk); const ERR = new Error('test'); gently.expect(form, 'on', 3, function(event, fn) { if (event == 'field') { fn('foo', 'bar'); } if (event == 'error') { fn(ERR); gently.expect(form, 'on'); gently.expect(form, 'writeHeaders'); gently.expect(REQ, 'on', 4, function() { return this; }); } return this; }); form.parse(REQ, function parseCbErr(err, fields, files) { assert.strictEqual(err, ERR); assert.deepEqual(fields, { foo: 'bar' }); }); })(); (function testWriteOrder() { gently.expect(EventEmitterStub, 'call'); const form = new IncomingForm(); const REQ = new events.EventEmitter(); const BUF = {}; const DATACB = null; REQ.on('newListener', function(event, fn) { if (event === 'data') fn(BUF); }); gently.expect(form, 'writeHeaders'); gently.expect(form, 'write', function(buf) { assert.strictEqual(buf, BUF); }); form.parse(REQ); })(); }); test(function pause() { assert.strictEqual(form.pause(), false); }); test(function resume() { assert.strictEqual(form.resume(), false); }); test(function writeHeaders() { const HEADERS = {}; gently.expect(form, '_parseContentLength'); gently.expect(form, '_parseContentType'); form.writeHeaders(HEADERS); assert.strictEqual(form.headers, HEADERS); }); test(function write() { const parser = {}; const BUFFER = [1, 2, 3]; form._parser = parser; form.bytesExpected = 523423; (function testBasic() { gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { assert.equal(event, 'progress'); assert.equal(bytesReceived, BUFFER.length); assert.equal(bytesExpected, form.bytesExpected); }); gently.expect(parser, 'write', function(buffer) { assert.strictEqual(buffer, BUFFER); return buffer.length; }); assert.equal(form.write(BUFFER), BUFFER.length); assert.equal(form.bytesReceived, BUFFER.length); })(); (function testParserError() { gently.expect(form, 'emit'); gently.expect(parser, 'write', function(buffer) { assert.strictEqual(buffer, BUFFER); return buffer.length - 1; }); gently.expect(form, '_error', function(err) { assert.ok(err.message.match(/parser error/i)); }); assert.equal(form.write(BUFFER), BUFFER.length - 1); assert.equal(form.bytesReceived, BUFFER.length + BUFFER.length); })(); (function testUninitialized() { delete form._parser; gently.expect(form, '_error', function(err) { assert.ok(err.message.match(/uninitialized parser/i)); }); form.write(BUFFER); })(); }); test(function parseContentType() { const HEADERS = {}; form.headers = { 'content-type': 'application/x-www-form-urlencoded' }; gently.expect(form, '_initUrlencoded'); form._parseContentType(); // accept anything that has 'urlencoded' in it form.headers = { 'content-type': 'broken-client/urlencoded-stupid' }; gently.expect(form, '_initUrlencoded'); form._parseContentType(); const BOUNDARY = '---------------------------57814261102167618332366269'; form.headers = { 'content-type': `multipart/form-data; boundary=${BOUNDARY}`, }; gently.expect(form, '_initMultipart', function(boundary) { assert.equal(boundary, BOUNDARY); }); form._parseContentType(); (function testQuotedBoundary() { form.headers = { 'content-type': `multipart/form-data; boundary="${BOUNDARY}"`, }; gently.expect(form, '_initMultipart', function(boundary) { assert.equal(boundary, BOUNDARY); }); form._parseContentType(); })(); (function testNoBoundary() { form.headers = { 'content-type': 'multipart/form-data' }; gently.expect(form, '_error', function(err) { assert.ok(err.message.match(/no multipart boundary/i)); }); form._parseContentType(); })(); (function testNoContentType() { form.headers = {}; gently.expect(form, '_error', function(err) { assert.ok(err.message.match(/no content-type/i)); }); form._parseContentType(); })(); (function testUnknownContentType() { form.headers = { 'content-type': 'invalid' }; gently.expect(form, '_error', function(err) { assert.ok(err.message.match(/unknown content-type/i)); }); form._parseContentType(); })(); }); test(function parseContentLength() { const HEADERS = {}; form.headers = {}; gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { assert.equal(event, 'progress'); assert.equal(bytesReceived, 0); assert.equal(bytesExpected, 0); }); form._parseContentLength(); form.headers['content-length'] = '8'; gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { assert.equal(event, 'progress'); assert.equal(bytesReceived, 0); assert.equal(bytesExpected, 8); }); form._parseContentLength(); assert.strictEqual(form.bytesReceived, 0); assert.strictEqual(form.bytesExpected, 8); // JS can be evil, lets make sure we are not form.headers['content-length'] = '08'; gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { assert.equal(event, 'progress'); assert.equal(bytesReceived, 0); assert.equal(bytesExpected, 8); }); form._parseContentLength(); assert.strictEqual(form.bytesExpected, 8); }); test(function _initMultipart() { const BOUNDARY = '123'; let PARSER; gently.expect(MultipartParserStub, 'new', function() { PARSER = this; }); gently.expect(MultipartParserStub.prototype, 'initWithBoundary', function( boundary, ) { assert.equal(boundary, BOUNDARY); }); form._initMultipart(BOUNDARY); assert.equal(form.type, 'multipart'); assert.strictEqual(form._parser, PARSER); (function testRegularField() { let PART; gently.expect(StreamStub, 'new', function() { PART = this; }); gently.expect(form, 'onPart', function(part) { assert.strictEqual(part, PART); assert.deepEqual(part.headers, { 'content-disposition': 'form-data; name="field1"', foo: 'bar', }); assert.equal(part.name, 'field1'); const strings = ['hello', ' world']; gently.expect(part, 'emit', 2, function(event, b) { assert.equal(event, 'data'); assert.equal(b.toString(), strings.shift()); }); gently.expect(part, 'emit', function(event, b) { assert.equal(event, 'end'); }); }); PARSER.onPartBegin(); PARSER.onHeaderField(Buffer.from('content-disposition'), 0, 10); PARSER.onHeaderField(Buffer.from('content-disposition'), 10, 19); PARSER.onHeaderValue(Buffer.from('form-data; name="field1"'), 0, 14); PARSER.onHeaderValue(Buffer.from('form-data; name="field1"'), 14, 24); PARSER.onHeaderEnd(); PARSER.onHeaderField(Buffer.from('foo'), 0, 3); PARSER.onHeaderValue(Buffer.from('bar'), 0, 3); PARSER.onHeaderEnd(); PARSER.onHeadersEnd(); PARSER.onPartData(Buffer.from('hello world'), 0, 5); PARSER.onPartData(Buffer.from('hello world'), 5, 11); PARSER.onPartEnd(); })(); (function testFileField() { let PART; gently.expect(StreamStub, 'new', function() { PART = this; }); gently.expect(form, 'onPart', function(part) { assert.deepEqual(part.headers, { 'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"', 'content-type': 'text/plain', }); assert.equal(part.name, 'field2'); assert.equal(part.filename, 'Sun"et.jpg'); assert.equal(part.mime, 'text/plain'); gently.expect(part, 'emit', function(event, b) { assert.equal(event, 'data'); assert.equal(b.toString(), '... contents of file1.txt ...'); }); gently.expect(part, 'emit', function(event, b) { assert.equal(event, 'end'); }); }); PARSER.onPartBegin(); PARSER.onHeaderField(Buffer.from('content-disposition'), 0, 19); PARSER.onHeaderValue( Buffer.from( 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"', ), 0, 85, ); PARSER.onHeaderEnd(); PARSER.onHeaderField(Buffer.from('Content-Type'), 0, 12); PARSER.onHeaderValue(Buffer.from('text/plain'), 0, 10); PARSER.onHeaderEnd(); PARSER.onHeadersEnd(); PARSER.onPartData(Buffer.from('... contents of file1.txt ...'), 0, 29); PARSER.onPartEnd(); })(); (function testEnd() { gently.expect(form, '_maybeEnd'); PARSER.onEnd(); assert.ok(form.ended); })(); }); test(function _fileName() { // TODO }); test(function _initUrlencoded() { let PARSER; gently.expect(QuerystringParserStub, 'new', function() { PARSER = this; }); form._initUrlencoded(); assert.equal(form.type, 'urlencoded'); assert.strictEqual(form._parser, PARSER); (function testOnField() { const KEY = 'KEY'; const VAL = 'VAL'; gently.expect(form, 'emit', function(field, key, val) { assert.equal(field, 'field'); assert.equal(key, KEY); assert.equal(val, VAL); }); PARSER.onField(KEY, VAL); })(); (function testOnEnd() { gently.expect(form, '_maybeEnd'); PARSER.onEnd(); assert.equal(form.ended, true); })(); }); test(function _error() { const ERR = new Error('bla'); gently.expect(form, 'emit', function(event, err) { assert.equal(event, 'error'); assert.strictEqual(err, ERR); }); form._error(ERR); assert.strictEqual(form.error, ERR); // make sure _error only does its thing once form._error(ERR); }); test(function onPart() { const PART = {}; gently.expect(form, 'handlePart', function(part) { assert.strictEqual(part, PART); }); form.onPart(PART); }); test(function handlePart() { (function testUtf8Field() { const PART = new events.EventEmitter(); PART.name = 'my_field'; gently.expect(form, 'emit', function(event, field, value) { assert.equal(event, 'field'); assert.equal(field, 'my_field'); assert.equal(value, 'hello world: €'); }); form.handlePart(PART); PART.emit('data', Buffer.from('hello')); PART.emit('data', Buffer.from(' world: ')); PART.emit('data', Buffer.from([0xe2])); PART.emit('data', Buffer.from([0x82, 0xac])); PART.emit('end'); })(); (function testBinaryField() { const PART = new events.EventEmitter(); PART.name = 'my_field2'; gently.expect(form, 'emit', function(event, field, value) { assert.equal(event, 'field'); assert.equal(field, 'my_field2'); assert.equal( value, `hello world: ${Buffer.from([0xe2, 0x82, 0xac]).toString('binary')}`, ); }); form.encoding = 'binary'; form.handlePart(PART); PART.emit('data', Buffer.from('hello')); PART.emit('data', Buffer.from(' world: ')); PART.emit('data', Buffer.from([0xe2])); PART.emit('data', Buffer.from([0x82, 0xac])); PART.emit('end'); })(); (function testFieldSize() { form.maxFieldsSize = 8; const PART = new events.EventEmitter(); PART.name = 'my_field'; gently.expect(form, '_error', function(err) { assert.equal( err.message, 'maxFieldsSize exceeded, received 9 bytes of field data', ); }); form.handlePart(PART); form._fieldsSize = 1; PART.emit('data', Buffer.alloc(7)); PART.emit('data', Buffer.alloc(1)); })(); (function testFilePart() { const PART = new events.EventEmitter(); let FILE = new events.EventEmitter(); const PATH = '/foo/bar'; PART.name = 'my_file'; PART.filename = 'sweet.txt'; PART.mime = 'sweet.txt'; gently.expect(form, '_uploadPath', function(filename) { assert.equal(filename, PART.filename); return PATH; }); gently.expect(FileStub, 'new', function(properties) { assert.equal(properties.path, PATH); assert.equal(properties.name, PART.filename); assert.equal(properties.type, PART.mime); FILE = this; gently.expect(form, 'emit', function(event, field, file) { assert.equal(event, 'fileBegin'); assert.strictEqual(field, PART.name); assert.strictEqual(file, FILE); }); gently.expect(FILE, 'open'); }); form.handlePart(PART); assert.equal(form._flushing, 1); let BUFFER; gently.expect(form, 'pause'); gently.expect(FILE, 'write', function(buffer, cb) { assert.strictEqual(buffer, BUFFER); gently.expect(form, 'resume'); // @todo handle cb(new Err) cb(); }); PART.emit('data', (BUFFER = Buffer.from('test'))); gently.expect(FILE, 'end', function(cb) { gently.expect(form, 'emit', function(event, field, file) { assert.equal(event, 'file'); assert.strictEqual(file, FILE); }); gently.expect(form, '_maybeEnd'); cb(); assert.equal(form._flushing, 0); }); PART.emit('end'); })(); }); test(function _uploadPath() { (function testUniqueId() { let UUID_A; let UUID_B; gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) { assert.equal(uploadDir, form.uploadDir); UUID_A = uuid; }); form._uploadPath(); gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) { UUID_B = uuid; }); form._uploadPath(); assert.notEqual(UUID_A, UUID_B); })(); (function testFileExtension() { form.keepExtensions = true; const FILENAME = 'foo.jpg'; const EXT = '.bar'; gently.expect(GENTLY.hijacked.path, 'extname', function(filename) { assert.equal(filename, FILENAME); gently.restore(path, 'extname'); return EXT; }); gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, name) { assert.equal(path.extname(name), EXT); }); form._uploadPath(FILENAME); })(); }); test(function _maybeEnd() { gently.expect(form, 'emit', 0); form._maybeEnd(); form.ended = true; form._flushing = 1; form._maybeEnd(); gently.expect(form, 'emit', function(event) { assert.equal(event, 'end'); }); form.ended = true; form._flushing = 0; form._maybeEnd(); }); ================================================ FILE: test-legacy/system/test-multi-video-upload.js ================================================ const common = require('../common'); const BOUNDARY = '---------------------------10102754414578508781458777923'; const FIXTURE = `${TEST_FIXTURES}/multi_video.upload`; const fs = require('fs'); const http = require('http'); const formidable = require(`${common.lib}/index`); const server = http.createServer(); server.on('request', function(req, res) { const form = new formidable.IncomingForm(); const uploads = {}; form.uploadDir = TEST_TMP; form.hash = 'sha1'; form.parse(req); form .on('fileBegin', function(field, file) { assert.equal(field, 'upload'); const tracker = { file, progress: [], ended: false }; uploads[file.name] = tracker; file .on('progress', function(bytesReceived) { tracker.progress.push(bytesReceived); assert.equal(bytesReceived, file.size); }) .on('end', function() { tracker.ended = true; }); }) .on('field', function(field, value) { assert.equal(field, 'title'); assert.equal(value, ''); }) .on('file', function(field, file) { assert.equal(field, 'upload'); assert.strictEqual(uploads[file.name].file, file); }) .on('end', function() { assert.ok(uploads['shortest_video.flv']); assert.ok(uploads['shortest_video.flv'].ended); assert.ok(uploads['shortest_video.flv'].progress.length > 3); assert.equal( uploads['shortest_video.flv'].file.hash, 'd6a17616c7143d1b1438ceeef6836d1a09186b3a', ); assert.equal( uploads['shortest_video.flv'].progress.slice(-1), uploads['shortest_video.flv'].file.size, ); assert.ok(uploads['shortest_video.mp4']); assert.ok(uploads['shortest_video.mp4'].ended); assert.ok(uploads['shortest_video.mp4'].progress.length > 3); assert.equal( uploads['shortest_video.mp4'].file.hash, '937dfd4db263f4887ceae19341dcc8d63bcd557f', ); server.close(); res.writeHead(200); res.end('good'); }); }); server.listen(TEST_PORT, function() { let stat; let headers; let request; let fixture; stat = fs.statSync(FIXTURE); request = http.request({ port: TEST_PORT, path: '/', method: 'POST', headers: { 'content-type': `multipart/form-data; boundary=${BOUNDARY}`, 'content-length': stat.size, }, }); fs.createReadStream(FIXTURE).pipe(request); }); ================================================ FILE: test-node/standalone/createDirsFromUploads.test.js ================================================ import {strictEqual, deepEqual} from 'node:assert'; import { createServer, request } from 'node:http'; import formidable from '../../src/index.js'; import test from 'node:test'; import fs from 'node:fs'; const PORT = 13539; const uploads = './uploads'; test('folder created', (t,done) => { const server = createServer((req, res) => { const form = formidable({ createDirsFromUploads: true, uploadDir: uploads, filename: (x) => { return 'x/y/z.txt' } }); form.parse(req, () => { res.writeHead(200); res.end("ok") }); }); server.listen(PORT, () => { const chosenPort = server.address().port; const body = `----13068458571765726332503797717\r Content-Disposition: form-data; name="title"\r \r a\r ----13068458571765726332503797717\r Content-Disposition: form-data; name="multipleFiles"; filename="x.txt"\r Content-Type: application/x-javascript\r \r \r \r a\r b\r c\r d\r \r ----13068458571765726332503797717--\r `; fetch(String(new URL(`http:localhost:${chosenPort}/`)), { method: 'POST', headers: { 'Content-Length': body.length, Host: `localhost:${chosenPort}`, 'Content-Type': 'multipart/form-data; boundary=--13068458571765726332503797717', }, body }).then(res => { //may also contain tests from other tests deepEqual(fs.readdirSync(uploads).includes('x'), true); deepEqual(fs.readdirSync(`${uploads}/x`), ['y']); deepEqual(fs.readdirSync(`${uploads}/x/y`), ['z.txt']); strictEqual(res.status, 200); server.close(); done(); }); }); }); ================================================ FILE: test-node/standalone/end-event-emitted-twice.test.js ================================================ import {strictEqual} from 'node:assert'; import { createServer, request } from 'node:http'; import formidable from '../../src/index.js'; import test from 'node:test'; const PORT = 13540; test('end event emitted twice', (t,done) => { const server = createServer((req, res) => { const form = formidable(); let i = 0; form.on('end', () => { i += 1; strictEqual(i, 1, 'end should be emitted once (on end)'); }); form.parse(req, () => { try { strictEqual(i, 1, 'end should be emitted once (callback)'); } catch (e) { done(e); } res.writeHead(200); res.end("ok") }); }); server.listen(PORT, () => { const chosenPort = server.address().port; const body = `----13068458571765726332503797717\r Content-Disposition: form-data; name="title"\r \r a\r ----13068458571765726332503797717\r Content-Disposition: form-data; name="multipleFiles"; filename="x.txt"\r Content-Type: application/x-javascript\r \r \r \r a\r b\r c\r d\r \r ----13068458571765726332503797717--\r `; fetch(String(new URL(`http:localhost:${chosenPort}/`)), { method: 'POST', headers: { 'Content-Length': body.length, Host: `localhost:${chosenPort}`, 'Content-Type': 'multipart/form-data; boundary=--13068458571765726332503797717', }, body }).then(res => { strictEqual(res.status, 200); server.close(); done(); }); }); }); ================================================ FILE: test-node/standalone/multipart_parser.test.js ================================================ import {Readable} from 'node:stream'; import MultipartParser from '../../src/parsers/Multipart.js'; import {malformedMultipart} from '../../src/FormidableError.js'; import test from 'node:test'; import assert, { deepEqual } from 'node:assert'; test('MultipartParser does not hang', async (t) => { const mime = `--_\r\n--_--\r\n`; const parser = new MultipartParser(); parser.initWithBoundary('_'); try { for await (const {name, buffer, start, end} of Readable.from(mime).pipe(parser)) { console.log(name, buffer ? buffer.subarray(start, end).toString() : ''); } } catch (e) { deepEqual(e.code, malformedMultipart) return; // console.error('error'); // console.error(e); } assert(false, 'should catch error'); }); ================================================ FILE: test-node/standalone/promise.test.js ================================================ import { ok, strictEqual } from 'node:assert'; import { createServer } from 'node:http'; import test from 'node:test'; import formidable, { errors } from '../../src/index.js'; const isPromise = (x) => { return x && typeof x === `object` && typeof x.then === `function`; }; let server; let port = 13540; test.beforeEach(() => { // Increment port to avoid conflicts between tests port += 1; server = createServer(); }); test.afterEach(() => { return new Promise((resolve) => { if (server.listening) { server.close(() => resolve()); } else { resolve(); } }); }); test('parse returns promise if no callback is provided', async (t) => { server.on('request', (req, res) => { const form = formidable(); const promise = form.parse(req); strictEqual(isPromise(promise), true); promise.then(([fields, files]) => { ok(typeof fields === 'object'); ok(typeof files === 'object'); res.writeHead(200); res.end("ok"); }).catch(e => { res.writeHead(500); res.end(String(e)); }); }); await new Promise(resolve => server.listen(port, resolve)); const body = `----13068458571765726332503797717\r Content-Disposition: form-data; name="title"\r \r a\r ----13068458571765726332503797717\r Content-Disposition: form-data; name="multipleFiles"; filename="x.txt"\r Content-Type: application/x-javascript\r \r \r \r a\r b\r c\r d\r \r ----13068458571765726332503797717--\r `; const res = await fetch(String(new URL(`http:localhost:${port}/`)), { method: 'POST', headers: { 'Content-Length': body.length, Host: `localhost:${port}`, 'Content-Type': 'multipart/form-data; boundary=--13068458571765726332503797717', }, body }); strictEqual(res.status, 200); }); test('parse rejects with promise if it fails', async (t) => { server.on('request', (req, res) => { const form = formidable({minFileSize: 10 ** 6}); // create condition to fail const promise = form.parse(req); strictEqual(isPromise(promise), true); promise.then(() => { res.writeHead(500); res.end('should have failed'); }).catch(e => { res.writeHead(e.httpCode); strictEqual(e.code, errors.smallerThanMinFileSize); res.end(String(e)); }); }); await new Promise(resolve => server.listen(port, resolve)); const body = `----13068458571765726332503797717\r Content-Disposition: form-data; name="title"\r \r a\r ----13068458571765726332503797717\r Content-Disposition: form-data; name="multipleFiles"; filename="x.txt"\r Content-Type: application/x-javascript\r \r \r \r a\r b\r c\r d\r \r ----13068458571765726332503797717--\r `; const res = await fetch(String(new URL(`http:localhost:${port}/`)), { method: 'POST', headers: { 'Content-Length': body.length, Host: `localhost:${port}`, 'Content-Type': 'multipart/form-data; boundary=--13068458571765726332503797717', }, body }); strictEqual(res.status, 400); }); ================================================ FILE: tool/record.js ================================================ /* eslint-disable no-param-reassign */ const http = require('http'); const fs = require('fs'); let connections = 0; const server = http.createServer((req, res) => { const { socket } = req; console.log('Request: %s %s -> %s', req.method, req.url, socket.filename); req.on('end', () => { if (req.url !== '/') { res.end( JSON.stringify({ method: req.method, url: req.url, filename: socket.filename, }), ); return; } res.writeHead(200, { 'Content-Type': 'text/html' }); res.end( '
' + '
' + '
' + '' + '
', ); }); }); server.on('connection', (socket) => { connections += 1; socket.id = connections; socket.filename = `connection-${socket.id}.http`; socket.file = fs.createWriteStream(socket.filename); socket.pipe(socket.file); console.log('--> %s', socket.filename); socket.on('close', () => { console.log('<-- %s', socket.filename); }); }); const port = process.env.PORT || 8080; server.listen(port, () => { console.log('Recording connections on port %s', port); }); ================================================ FILE: tool/rollup.config.js ================================================ /* eslint-disable */ import cjs from '@rollup/plugin-commonjs'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import packageJson from '../package.json' with { type: "json" }; const {dependencies} = packageJson; const plugins = [nodeResolve(), cjs()]; const cjsOptions = { format: `cjs`, exports: `named`, } const external = [...Object.keys(dependencies)]; export default [ { input: `src/index.js`, output: [ { file: `dist/index.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/helpers/firstValues.js`, output: [ { file: `dist/helpers/firstValues.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/helpers/readBooleans.js`, output: [ { file: `dist/helpers/readBooleans.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/parsers/JSON.js`, output: [ { file: `dist/parsers/JSON.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/parsers/Multipart.js`, output: [ { file: `dist/parsers/Multipart.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/parsers/Querystring.js`, output: [ { file: `dist/parsers/Querystring.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/parsers/OctetStream.js`, output: [ { file: `dist/parsers/OctetStream.cjs`, ...cjsOptions, }, ], external, plugins, }, { input: `src/parsers/StreamingQuerystring.js`, output: [ { file: `dist/parsers/StreamingQuerystring.cjs`, ...cjsOptions, }, ], external, plugins, }, ];