commit ec8f13539226b818168d491edd32e795401ed148 Author: Xi Xu Date: Sun Mar 29 21:39:05 2026 +0800 Initial commit diff --git a/.github/workflows/.keep b/.github/workflows/.keep new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..f9a6ee2 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,89 @@ +run-name: Build push and deploy (CD) +on: + push: + branches: + - main + - ci-debug + - dev + tags: + - '*' + +jobs: + build-and-push-to-gcr: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.ref_type == 'branch' && github.ref }} + cancel-in-progress: true + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + lfs: true + submodules: true + token: ${{ secrets.THINAPPS_SHARED_READ_TOKEN }} + - uses: 'google-github-actions/auth@v2' + with: + credentials_json: '${{ secrets.GCLOUD_SERVICE_ACCOUNT_SECRET_JSON }}' + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v2' + with: + install_components: beta + - name: "Docker auth" + run: |- + gcloud auth configure-docker us-docker.pkg.dev --quiet + - name: Set controller release version + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.12.0 + cache: npm + + - name: npm install + run: npm ci + - name: get maxmind mmdb + run: mkdir -p licensed && curl -o licensed/GeoLite2-City.mmdb https://raw.githubusercontent.com/P3TERX/GeoLite.mmdb/download/GeoLite2-City.mmdb + - name: get source han sans font + run: curl -o licensed/SourceHanSansSC-Regular.otf https://raw.githubusercontent.com/adobe-fonts/source-han-sans/refs/heads/release/OTF/SimplifiedChinese/SourceHanSansSC-Regular.otf + - name: build application + run: npm run build + - name: Set package version + run: npm version --no-git-tag-version ${{ env.RELEASE_VERSION }} + if: github.ref_type == 'tag' + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + us-docker.pkg.dev/reader-6b7dc/jina-reader/reader + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push + id: container + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + - name: Deploy CRAWL with Tag + run: | + gcloud beta run deploy crawl --image us-docker.pkg.dev/reader-6b7dc/jina-reader/reader@${{steps.container.outputs.imageid}} --tag ${{ env.RELEASE_VERSION }} --command '' --args build/stand-alone/crawl.js --region us-central1 --async --min-instances 0 --deploy-health-check --use-http2 + - name: Deploy SEARCH with Tag + run: | + gcloud beta run deploy search --image us-docker.pkg.dev/reader-6b7dc/jina-reader/reader@${{steps.container.outputs.imageid}} --tag ${{ env.RELEASE_VERSION }} --command '' --args build/stand-alone/search.js --region us-central1 --async --min-instances 0 --deploy-health-check --use-http2 + - name: Deploy SERP with Tag + run: | + gcloud beta run deploy serp --image us-docker.pkg.dev/reader-6b7dc/jina-reader/reader@${{steps.container.outputs.imageid}} --tag ${{ env.RELEASE_VERSION }} --command '' --args build/stand-alone/serp.js --region us-central1 --async --min-instances 0 --deploy-health-check --use-http2 + - name: Deploy CRAWL-EU with Tag + run: | + gcloud beta run deploy crawl-eu --image us-docker.pkg.dev/reader-6b7dc/jina-reader/reader@${{steps.container.outputs.imageid}} --tag ${{ env.RELEASE_VERSION }} --command '' --args build/stand-alone/crawl.js --region europe-west1 --async --min-instances 0 --deploy-health-check --use-http2 + - name: Deploy SEARCH-EU with Tag + run: | + gcloud beta run deploy search-eu --image us-docker.pkg.dev/reader-6b7dc/jina-reader/reader@${{steps.container.outputs.imageid}} --tag ${{ env.RELEASE_VERSION }} --command '' --args build/stand-alone/search.js --region europe-west1 --async --min-instances 0 --deploy-health-check --use-http2 + - name: Deploy SERP-HK with Tag + run: | + gcloud beta run deploy serp-hk --image us-docker.pkg.dev/reader-6b7dc/jina-reader/reader@${{steps.container.outputs.imageid}} --tag ${{ env.RELEASE_VERSION }} --command '' --args build/stand-alone/serp.js --region asia-east2 --async --min-instances 0 --deploy-health-check --use-http2 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1b850b --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# 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 (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# 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 +.secret.local + +toy*.ts + +.DS_Store +build/ +.firebase-emu/ +*.log +.DS_Store + +*.local +.secret.* +licensed/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8fddc71 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thinapps-shared"] + path = thinapps-shared + url = git@github.com:jina-ai/thinapps-shared.git diff --git a/.vscode/exensions.json b/.vscode/exensions.json new file mode 100644 index 0000000..37bdb37 --- /dev/null +++ b/.vscode/exensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "editorconfig.editorconfig", + "octref.vetur", + "redhat.vscode-yaml", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "streetsidesoftware.code-spell-checker" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c8a4a5c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,128 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach", + "port": 9229, + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + { + "name": "Attach by Process ID", + "processId": "${command:PickProcess}", + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + { + "name": "Debug Stand Alone Crawl", + "request": "launch", + "runtimeArgs": [ + "--env-file=.secret.local", + ], + "env": { + "GCLOUD_PROJECT": "reader-6b7dc", + "LD_PRELOAD": "/usr/local/lib/libcurl-impersonate-chrome.dylib" + }, + "cwd": "${workspaceFolder}", + "program": "build/stand-alone/crawl.js", + "skipFiles": [ + "/**" + ], + "type": "node", + "outputCapture": "std", + "preLaunchTask": "Backend:build:watch", + "killBehavior": "forceful" + }, + { + "name": "Debug Stand Alone Crawl + Browser", + "request": "launch", + "runtimeArgs": [ + "--env-file=.secret.local", + ], + "env": { + "GCLOUD_PROJECT": "reader-6b7dc", + "DEBUG_BROWSER": "true", + "LD_PRELOAD": "/usr/local/lib/libcurl-impersonate-chrome.dylib" + }, + "cwd": "${workspaceFolder}", + "program": "build/stand-alone/crawl.js", + "skipFiles": [ + "/**" + ], + "type": "node", + "outputCapture": "std", + "preLaunchTask": "Backend:build:watch", + "killBehavior": "forceful" + }, + { + "name": "Debug Stand Alone Crawl - EU", + "request": "launch", + "runtimeArgs": [ + "--env-file=.secret.local", + ], + "env": { + "GCLOUD_PROJECT": "reader-6b7dc", + "FIRESTORE_DATABASE": "reader-eu", + "GCP_STORAGE_BUCKET": "reader-eu", + "LD_PRELOAD": "/usr/local/lib/libcurl-impersonate-chrome.dylib" + }, + "cwd": "${workspaceFolder}", + "program": "build/stand-alone/crawl.js", + "skipFiles": [ + "/**" + ], + "type": "node", + "outputCapture": "std", + "preLaunchTask": "Backend:build:watch", + "killBehavior": "forceful" + }, + { + "name": "Debug Stand Alone Search", + "request": "launch", + "runtimeArgs": [ + "--env-file=.secret.local", + ], + "env": { + "GCLOUD_PROJECT": "reader-6b7dc", + "LD_PRELOAD": "/usr/local/lib/libcurl-impersonate-chrome.dylib" + }, + "cwd": "${workspaceFolder}", + "program": "build/stand-alone/search.js", + "skipFiles": [ + "/**" + ], + "type": "node", + "outputCapture": "std", + "preLaunchTask": "Backend:build:watch", + "killBehavior": "forceful" + }, + { + "name": "Debug Stand Alone SERP", + "request": "launch", + "runtimeArgs": [ + "--env-file=.secret.local", + ], + "env": { + "GCLOUD_PROJECT": "reader-6b7dc", + "PREFERRED_PROXY_COUNTRY": "hk", + "OVERRIDE_GOOGLE_DOMAIN": "www.google.com.hk", + "LD_PRELOAD": "/usr/local/lib/libcurl-impersonate-chrome.dylib" + }, + "cwd": "${workspaceFolder}", + "program": "build/stand-alone/serp.js", + "skipFiles": [ + "/**" + ], + "type": "node", + "outputCapture": "std", + "preLaunchTask": "Backend:build:watch", + "killBehavior": "forceful" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..87ecca7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,32 @@ +{ + "editor.wordWrap": "on", + "editor.wordWrapColumn": 120, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "[javascript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[yaml]": { + "editor.defaultFormatter": "redhat.vscode-yaml" + }, + "[markdown]": { + "files.trimTrailingWhitespace": false + }, + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.preferences.quoteStyle": "single", + "typescript.format.semicolons": "insert", + "typescript.preferences.importModuleSpecifier": "project-relative", + "typescript.locale": "en", + "cSpell.enabled": true, + "cSpell.words": [ + ], +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5cba448 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "group": "build", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [], + "label": "Backend:rebuild", + "detail": "Backend:rebuild" + }, + { + "type": "typescript", + "options": { + "cwd": "${workspaceFolder}" + }, + "tsconfig": "tsconfig.json", + "option": "watch", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch" + ], + "group": "build", + "label": "Backend:build:watch" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..56040e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# syntax=docker/dockerfile:1 +FROM lwthiker/curl-impersonate:0.6-chrome-slim-bullseye + +FROM node:22 + +RUN apt-get update \ + && apt-get install -y wget gnupg \ + && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ + && apt-get update \ + && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 zstd \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=0 /usr/local/lib/libcurl-impersonate.so /usr/local/lib/libcurl-impersonate.so + +RUN groupadd -r jina +RUN useradd -g jina -G audio,video -m jina +USER jina + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY build ./build +COPY public ./public +COPY licensed ./licensed + +RUN rm -rf ~/.config/chromium && mkdir -p ~/.config/chromium + +RUN NODE_COMPILE_CACHE=node_modules npm run dry-run + +ENV OVERRIDE_CHROME_EXECUTABLE_PATH=/usr/bin/google-chrome-stable +ENV LD_PRELOAD=/usr/local/lib/libcurl-impersonate.so CURL_IMPERSONATE=chrome116 CURL_IMPERSONATE_HEADERS=no +ENV NODE_COMPILE_CACHE=node_modules +ENV PORT=8080 + +EXPOSE 3000 3001 8080 8081 +ENTRYPOINT ["node"] +CMD [ "build/stand-alone/crawl.js" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4875d6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,193 @@ +Copyright 2020-2024 Jina AI Limited. All rights reserved. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2020-2021 Jina AI Limited + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2806226 --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# Reader + +Your LLMs deserve better input. + +Reader does two things: +- **Read**: It converts any URL to an **LLM-friendly** input with `https://r.jina.ai/https://your.url`. Get improved output for your agent and RAG systems at no cost. +- **Search**: It searches the web for a given query with `https://s.jina.ai/your+query`. This allows your LLMs to access the latest world knowledge from the web. + +Check out [the live demo](https://jina.ai/reader#demo) + +Or just visit these URLs (**Read**) https://r.jina.ai/https://github.com/jina-ai/reader, (**Search**) https://s.jina.ai/Who%20will%20win%202024%20US%20presidential%20election%3F and see yourself. + +> Feel free to use Reader API in production. It is free, stable and scalable. We are maintaining it actively as one of the core products of Jina AI. [Check out rate limit](https://jina.ai/reader#pricing) + +image +image + + +## Updates + +- **2024-07-15**: To restrict the results of `s.jina.ai` to certain domain/website, you can set e.g. `site=jina.ai` in the query parameters, which enables in-site search. For more options, [try our updated live-demo](https://jina.ai/reader/#apiform). +- **2024-05-30**: Reader can now read abitrary PDF from any URL! Check out [this PDF result from NASA.gov](https://r.jina.ai/https://www.nasa.gov/wp-content/uploads/2023/01/55583main_vision_space_exploration2.pdf) vs [the original](https://www.nasa.gov/wp-content/uploads/2023/01/55583main_vision_space_exploration2.pdf). +- **2024-05-15**: We introduced a new endpoint `s.jina.ai` that searches on the web and return top-5 results, each in a LLM-friendly format. [Read more about this new feature here](https://jina.ai/news/jina-reader-for-search-grounding-to-improve-factuality-of-llms). +- **2024-05-08**: Image caption is off by default for better latency. To turn it on, set `x-with-generated-alt: true` in the request header. +- **2024-04-24**: You now have more fine-grained control over Reader API [using headers](#using-request-headers), e.g. forwarding cookies, using HTTP proxy. +- **2024-04-15**: Reader now supports image reading! It captions all images at the specified URL and adds `Image [idx]: [caption]` as an alt tag (if they initially lack one). This enables downstream LLMs to interact with the images in reasoning, summarizing etc. [See example here](https://x.com/JinaAI_/status/1780094402071023926). + +## Usage + +### Using `r.jina.ai` for single URL fetching +Simply prepend `https://r.jina.ai/` to any URL. For example, to convert the URL `https://en.wikipedia.org/wiki/Artificial_intelligence` to an LLM-friendly input, use the following URL: + +[https://r.jina.ai/https://en.wikipedia.org/wiki/Artificial_intelligence](https://r.jina.ai/https://en.wikipedia.org/wiki/Artificial_intelligence) + +### [Using `r.jina.ai` for a full website fetching (Google Colab)](https://colab.research.google.com/drive/1uoBy6_7BhxqpFQ45vuhgDDDGwstaCt4P#scrollTo=5LQjzJiT9ewT) + +### Using `s.jina.ai` for web search +Simply prepend `https://s.jina.ai/` to your search query. Note that if you are using this in the code, make sure to encode your search query first, e.g. if your query is `Who will win 2024 US presidential election?` then your url should look like: + +[https://s.jina.ai/Who%20will%20win%202024%20US%20presidential%20election%3F](https://s.jina.ai/Who%20will%20win%202024%20US%20presidential%20election%3F) + +Behind the scenes, Reader searches the web, fetches the top 5 results, visits each URL, and applies `r.jina.ai` to it. This is different from many `web search function-calling` in agent/RAG frameworks, which often return only the title, URL, and description provided by the search engine API. If you want to read one result more deeply, you have to fetch the content yourself from that URL. With Reader, `http://s.jina.ai` automatically fetches the content from the top 5 search result URLs for you (reusing the tech stack behind `http://r.jina.ai`). This means you don't have to handle browser rendering, blocking, or any issues related to JavaScript and CSS yourself. + +### Using `s.jina.ai` for in-site search +Simply specify `site` in the query parameters such as: + +```bash +curl 'https://s.jina.ai/When%20was%20Jina%20AI%20founded%3F?site=jina.ai&site=github.com' +``` + +### [Interactive Code Snippet Builder](https://jina.ai/reader#apiform) + +We highly recommend using the code builder to explore different parameter combinations of the Reader API. + +image + + +### Using request headers + +As you have already seen above, one can control the behavior of the Reader API using request headers. Here is a complete list of supported headers. + +- You can enable the image caption feature via the `x-with-generated-alt: true` header. +- You can ask the Reader API to forward cookies settings via the `x-set-cookie` header. + - Note that requests with cookies will not be cached. +- You can bypass `readability` filtering via the `x-respond-with` header, specifically: + - `x-respond-with: markdown` returns markdown *without* going through `reability` + - `x-respond-with: html` returns `documentElement.outerHTML` + - `x-respond-with: text` returns `document.body.innerText` + - `x-respond-with: screenshot` returns the URL of the webpage's screenshot +- You can specify a proxy server via the `x-proxy-url` header. +- You can customize cache tolerance via the `x-cache-tolerance` header (integer in seconds). +- You can bypass the cached page (lifetime 3600s) via the `x-no-cache: true` header (equivalent of `x-cache-tolerance: 0`). +- If you already know the HTML structure of your target page, you may specify `x-target-selector` or `x-wait-for-selector` to direct the Reader API to focus on a specific part of the page. + - By setting `x-target-selector` header to a CSS selector, the Reader API return the content within the matched element, instead of the full HTML. Setting this header is useful when the automatic content extraction fails to capture the desired content and you can manually select the correct target. + - By setting `x-wait-for-selector` header to a CSS selector, the Reader API will wait until the matched element is rendered before returning the content. If you already specified `x-wait-for-selector`, this header can be omitted if you plan to wait for the same element. + +### Using `r.jina.ai` for single page application (SPA) fetching +Many websites nowadays rely on JavaScript frameworks and client-side rendering. Usually known as Single Page Application (SPA). Thanks to [Puppeteer](https://github.com/puppeteer/puppeteer) and headless Chrome browser, Reader natively supports fetching these websites. However, due to specific approach some SPA are developed, there may be some extra precautions to take. + +#### SPAs with hash-based routing +By definition of the web standards, content come after `#` in a URL is not sent to the server. To mitigate this issue, use `POST` method with `url` parameter in body. + +```bash +curl -X POST 'https://r.jina.ai/' -d 'url=https://example.com/#/route' +``` + +#### SPAs with preloading contents +Some SPAs, or even some websites that are not strictly SPAs, may show preload contents before later loading the main content dynamically. In this case, Reader may be capturing the preload content instead of the main content. To mitigate this issue, here are some possible solutions: + +##### Specifying `x-timeout` +When timeout is explicitly specified, Reader will not attempt to return early and will wait for network idle until the timeout is reached. This is useful when the target website will eventually come to a network idle. + +```bash +curl 'https://example.com/' -H 'x-timeout: 30' +``` + +##### Specifying `x-wait-for-selector` +When wait-for-selector is explicitly specified, Reader will wait for the appearance of the specified CSS selector until timeout is reached. This is useful when you know exactly what element to wait for. + +```bash +curl 'https://example.com/' -H 'x-wait-for-selector: #content' +``` + +### Streaming mode + +Streaming mode is useful when you find that the standard mode provides an incomplete result. This is because the Reader will wait a bit longer until the page is *stablely* rendered. Use the accept-header to toggle the streaming mode: + +```bash +curl -H "Accept: text/event-stream" https://r.jina.ai/https://en.m.wikipedia.org/wiki/Main_Page +``` + +The data comes in a stream; each subsequent chunk contains more complete information. **The last chunk should provide the most complete and final result.** If you come from LLMs, please note that it is a different behavior than the LLMs' text-generation streaming. + +For example, compare these two curl commands below. You can see streaming one gives you complete information at last, whereas standard mode does not. This is because the content loading on this particular site is triggered by some js *after* the page is fully loaded, and standard mode returns the page "too soon". +```bash +curl -H 'x-no-cache: true' https://access.redhat.com/security/cve/CVE-2023-45853 +curl -H "Accept: text/event-stream" -H 'x-no-cache: true' https://r.jina.ai/https://access.redhat.com/security/cve/CVE-2023-45853 +``` + +> Note: `-H 'x-no-cache: true'` is used only for demonstration purposes to bypass the cache. + +Streaming mode is also useful if your downstream LLM/agent system requires immediate content delivery or needs to process data in chunks to interleave I/O and LLM processing times. This allows for quicker access and more efficient data handling: + +```text +Reader API: streamContent1 ----> streamContent2 ----> streamContent3 ---> ... + | | | + v | | +Your LLM: LLM(streamContent1) | | + v | + LLM(streamContent2) | + v + LLM(streamContent3) +``` + +Note that in terms of completeness: `... > streamContent3 > streamContent2 > streamContent1`, each subsequent chunk contains more complete information. + +### JSON mode + +This is still very early and the result is not really a "useful" JSON. It contains three fields `url`, `title` and `content` only. Nonetheless, you can use accept-header to control the output format: +```bash +curl -H "Accept: application/json" https://r.jina.ai/https://en.m.wikipedia.org/wiki/Main_Page +``` + +JSON mode is probably more useful in `s.jina.ai` than `r.jina.ai`. For `s.jina.ai` with JSON mode, it returns 5 results in a list, each in the structure of `{'title', 'content', 'url'}`. + +### Generated alt + +All images in that page that lack `alt` tag can be auto-captioned by a VLM (vision langauge model) and formatted as `!(Image [idx]: [VLM_caption])[img_URL]`. This should give your downstream text-only LLM *just enough* hints to include those images into reasoning, selecting, and summarization. Use the x-with-generated-alt header to toggle the streaming mode: + +```bash +curl -H "X-With-Generated-Alt: true" https://r.jina.ai/https://en.m.wikipedia.org/wiki/Main_Page +``` + +## How it works +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/jina-ai/reader) + +## What is `thinapps-shared` submodule? + +You might notice a reference to `thinapps-shared` submodule, an internal package we use to share code across our products. While it’s not open-sourced and isn't integral to the Reader's functions, it mainly helps with decorators, logging, secrets management, etc. Feel free to ignore it for now. + +That said, this is *the single codebase* behind `https://r.jina.ai`, so everytime we commit here, we will deploy the new version to the `https://r.jina.ai`. + +## Having trouble on some websites? +Please raise an issue with the URL you are having trouble with. We will look into it and try to fix it. + +## License +Reader is backed by [Jina AI](https://jina.ai) and licensed under [Apache-2.0](./LICENSE). diff --git a/integrity-check.cjs b/integrity-check.cjs new file mode 100644 index 0000000..8f7494e --- /dev/null +++ b/integrity-check.cjs @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const file = path.resolve(__dirname, 'licensed/GeoLite2-City.mmdb'); + +if (!fs.existsSync(file)) { + console.error(`Integrity check failed: ${file} does not exist.`); + process.exit(1); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9aa825d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12990 @@ +{ + "name": "reader", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reader", + "dependencies": { + "@esm2cjs/normalize-url": "^8.0.0", + "@google-cloud/translate": "^8.2.0", + "@koa/bodyparser": "^5.1.1", + "@mozilla/readability": "^0.6.0", + "@napi-rs/canvas": "^0.1.68", + "@types/turndown": "^5.0.4", + "@xmldom/xmldom": "^0.9.3", + "archiver": "^6.0.1", + "axios": "^1.3.3", + "bcrypt": "^5.1.0", + "busboy": "^1.6.0", + "civkit": "^0.9.0-2570394", + "cors": "^2.8.5", + "dayjs": "^1.11.9", + "express": "^4.19.2", + "firebase-admin": "^12.1.0", + "firebase-functions": "^6.1.1", + "htmlparser2": "^9.0.0", + "jose": "^5.1.0", + "koa": "^2.16.0", + "koa-compress": "^5.1.1", + "langdetect": "^0.2.1", + "linkedom": "^0.18.4", + "lru-cache": "^11.0.2", + "maxmind": "^4.3.18", + "minio": "^7.1.3", + "node-libcurl": "^4.1.0", + "openai": "^4.20.0", + "pdfjs-dist": "^4.10.38", + "puppeteer": "^23.3.0", + "puppeteer-extra": "^3.3.6", + "puppeteer-extra-plugin-block-resources": "^2.4.3", + "robots-parser": "^3.0.1", + "set-cookie-parser": "^2.6.0", + "simple-zstd": "^1.4.2", + "stripe": "^11.11.0", + "svg2png-wasm": "^1.4.1", + "tiktoken": "^1.0.16", + "tld-extract": "^2.1.0", + "turndown": "^7.1.3", + "turndown-plugin-gfm": "^1.0.2", + "undici": "^7.8.0" + }, + "devDependencies": { + "@types/archiver": "^5.3.4", + "@types/bcrypt": "^5.0.0", + "@types/busboy": "^1.5.4", + "@types/cors": "^2.8.17", + "@types/koa": "^2.15.0", + "@types/koa-compress": "^4.0.6", + "@types/node": "^20.14.13", + "@types/set-cookie-parser": "^2.4.7", + "@types/xmldom": "^0.1.34", + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-import": "^2.25.4", + "firebase-functions-test": "^3.0.0", + "pino-pretty": "^13.0.0", + "replicate": "^0.16.1", + "typescript": "^5.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "dev": true, + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "peer": true + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@esm2cjs/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@esm2cjs/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-9lrM8Q0ANSfM7ziVpNXEDKrVYcU4M3xIAUZWcvH9bY0fINBCkew1YBU1C1otfNoFDCERvCH0AkO70wkePdQwQw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/AlCalzone" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.1.tgz", + "integrity": "sha512-NILZbe6RH3X1pZmJnfOfY2gLIrlKmrkUMMrrK6VSXHcSE0eQv28xFEcw16D198i9JYZpy5Kwq394My62qCMaIw==" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.1.tgz", + "integrity": "sha512-nFGqTYsnDFn1oXf1tCwPAc+hQPxyvBT/QB7qDjwK+IDYThOn63nGhzdUTXxVD9Ca8gUY/e5PQMngeo0ZW/E3uQ==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.2.tgz", + "integrity": "sha512-k3NA28Jfoo0+o391bFjoV9X5QLnUL1WbLhZZRbTQhZdmdGYJfX8ixtNNlHsYQ94bwG0QRbsmvkzDnzuhHrV11w==" + }, + "node_modules/@firebase/component": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.6.tgz", + "integrity": "sha512-pp7sWqHmAAlA3os6ERgoM3k5Cxff510M9RLXZ9Mc8KFKMBc2ct3RkZTWUF7ixJNvMiK/iNgRLPDrLR2gtRJ9iQ==", + "dependencies": { + "@firebase/util": "1.9.5", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.4.tgz", + "integrity": "sha512-k84cXh+dtpzvY6yOhfyr1B+I1vjvSMtmlqotE0lTNVylc8m5nmOohjzpTLEQDrBWvwACX/VP5fEyajAdmnOKqA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.1", + "@firebase/auth-interop-types": "0.2.2", + "@firebase/component": "0.6.6", + "@firebase/logger": "0.4.1", + "@firebase/util": "1.9.5", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.4.tgz", + "integrity": "sha512-GEEDAvsSMAkqy0BIFSVtFzoOIIcKHFfDM4aXHtWL/JCaNn4OOjH7td73jDfN3ALvpIN4hQki0FcxQ89XjqaTjQ==", + "dependencies": { + "@firebase/component": "0.6.6", + "@firebase/database": "1.0.4", + "@firebase/database-types": "1.0.2", + "@firebase/logger": "0.4.1", + "@firebase/util": "1.9.5", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.2.tgz", + "integrity": "sha512-JRigr5JNLEHqOkI99tAGHDZF47469/cJz1tRAgGs8Feh+3ZmQy/vVChSqwMp2DuVUGp9PlmGsNSlpINJ/hDuIA==", + "dependencies": { + "@firebase/app-types": "0.9.1", + "@firebase/util": "1.9.5" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.1.tgz", + "integrity": "sha512-tTIixB5UJbG9ZHSGZSZdX7THr3KWOLrejZ9B7jYsm6fpwgRNngKznQKA2wgYVyvBc1ta7dGFh9NtJ8n7qfiYIw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.5.tgz", + "integrity": "sha512-PP4pAFISDxsf70l3pEy34Mf3GkkUcVQ3MdKp6aSVb7tcpfUQxnsdV7twDd8EkfB6zZylH6wpUAoangQDmCUMqw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/common": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.1.tgz", + "integrity": "sha512-7NBC5vD0au75nkctVs2vEGpdUPFs1BaHTMpeI+RVEgQSMe5/wEU6dx9p0fmZA0bj4HgdpobMKeegOcLUiEoxng==", + "dependencies": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.6.0.tgz", + "integrity": "sha512-WUDbaLY8UnPxgwsyIaxj6uxCtSDAaUyvzWJykNH5rZ9i92/SZCsPNNMN0ajrVpAR81hPIL4amXTaMJ40y5L+Yg==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.1", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.0.tgz", + "integrity": "sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.10.1.tgz", + "integrity": "sha512-sZW14pfxEQZSIbBPs6doFYtcbK31Bs3E4jH5Ly3jJnBkYfkMPX8sXG3ZQXCJa88MKtUNPlgBdMN2OJUzmFe5/g==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "ent": "^2.2.0", + "fast-xml-parser": "^4.3.0", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@google-cloud/translate": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/translate/-/translate-8.2.0.tgz", + "integrity": "sha512-PDF5FoFXzCEIKtj5zB5nQRYN6Yr0YqnVU1trozFoomvNlMq8iM5GImeCHKjr883ue397j7oc/J1q9eoduzjKRg==", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.0", + "extend": "^3.0.2", + "google-gax": "^4.0.3", + "is-html": "^2.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.11.tgz", + "integrity": "sha512-3RaoxOqkHHN2c05bwtBNVJmOf/UwMam0rZYtdl7dsRpsvDwcNpv6LkGgzltQ7xVf822LzBoKEPRvf4D7+xeIDw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "peer": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "peer": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@koa/bodyparser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@koa/bodyparser/-/bodyparser-5.1.1.tgz", + "integrity": "sha512-ZBF49xqNVxnmJ+8iXegq+fXPQm9RSX8giNl/aXS5rW1VpNct92wnFbGR/47vfoRJVLARGQ4HVL4WaQ0u8IJVoA==", + "license": "MIT", + "dependencies": { + "co-body": "^6.1.0", + "lodash.merge": "^4.6.2", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "koa": "^2.14.1" + } + }, + "node_modules/@koa/router": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz", + "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==", + "optional": true, + "dependencies": { + "debug": "^4.3.4", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.2.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "peer": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@mozilla/readability": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", + "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.68.tgz", + "integrity": "sha512-LQESrePLEBLvhuFkXx9jjBXRC2ClYsO5mqQ1m/puth5z9SOuM3N/B3vDuqnC3RJFktDktyK9khGvo7dTkqO9uQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.68", + "@napi-rs/canvas-darwin-arm64": "0.1.68", + "@napi-rs/canvas-darwin-x64": "0.1.68", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.68", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.68", + "@napi-rs/canvas-linux-arm64-musl": "0.1.68", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.68", + "@napi-rs/canvas-linux-x64-gnu": "0.1.68", + "@napi-rs/canvas-linux-x64-musl": "0.1.68", + "@napi-rs/canvas-win32-x64-msvc": "0.1.68" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.68.tgz", + "integrity": "sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.68.tgz", + "integrity": "sha512-/VURlrAD4gDoxW1GT/b0nP3fRz/fhxmHI/xznTq2FTwkQLPOlLkDLCvTmQ7v6LtGKdc2Ed6rvYpRan+JXThInQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.68.tgz", + "integrity": "sha512-tEpvGR6vCLTo1Tx9wmDnoOKROpw57wiCWwCpDOuVlj/7rqEJOUYr9ixW4aRJgmeGBrZHgevI0EURys2ER6whmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.68.tgz", + "integrity": "sha512-U9xbJsumPOiAYeAFZMlHf62b9dGs2HJ6Q5xt7xTB0uEyPeurwhgYBWGgabdsEidyj38YuzI/c3LGBbSQB3vagw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.68.tgz", + "integrity": "sha512-KFkn8wEm3mPnWD4l8+OUUkxylSJuN5q9PnJRZJgv15RtCA1bgxIwTkBhI/+xuyVMcHqON9sXq7cDkEJtHm35dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.68.tgz", + "integrity": "sha512-IQzts91rCdOALXBWQxLZRCEDrfFTGDtNRJMNu+2SKZ1uT8cmPQkPwVk5rycvFpvgAcmiFiOSCp1aRrlfU8KPpQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.68.tgz", + "integrity": "sha512-e9AS5UttoIKqXSmBzKZdd3NErSVyOEYzJfNOCGtafGk1//gibTwQXGlSXmAKuErqMp09pyk9aqQRSYzm1AQfBw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.68.tgz", + "integrity": "sha512-Pa/I36VE3j57I3Obhrr+J48KGFfkZk2cJN/2NmW/vCgmoF7kCP6aTVq5n+cGdGWLd/cN9CJ9JvNwEoMRDghu0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.68.tgz", + "integrity": "sha512-9c6rkc5195wNxuUHJdf4/mmnq433OQey9TNvQ9LspJazvHbfSkTij8wtKjASVQsJyPDva4fkWOeV/OQ7cLw0GQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.68.tgz", + "integrity": "sha512-Fc5Dez23u0FoSATurT6/w1oMytiRnKWEinHivdMvXpge6nG4YvhrASrtqMk8dGJMVQpHr8QJYF45rOrx2YU2Aw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "devOptional": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "devOptional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "devOptional": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.3.8.tgz", + "integrity": "sha512-Wtk9R7yQxGaIaawHorWKP2OOOm/RZzamOmSWwaqGphIuU6TcKYih0slL6asZlSSZtVoYTrBfrddSOD/jTu9vuQ==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "@peculiar/asn1-x509-attr": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.3.8.tgz", + "integrity": "sha512-ZmAaP2hfzgIGdMLcot8gHTykzoI+X/S53x1xoGbTmratETIaAbSWMiPGvZmXRA0SNEIydpMkzYtq4fQBxN1u1w==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.8.tgz", + "integrity": "sha512-Ah/Q15y3A/CtxbPibiLM/LKcMbnLTdUdLHUgdpB5f60sSvGkXzxJCu5ezGTFHogZXWNX3KSmYqilCrfdmBc6pQ==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.3.8.tgz", + "integrity": "sha512-XhdnCVznMmSmgy68B9pVxiZ1XkKoE1BjO4Hv+eUGiY1pM14msLsFZ3N7K46SoITIVZLq92kKkXpGiTfRjlNLyg==", + "optional": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.3.8", + "@peculiar/asn1-pkcs8": "^2.3.8", + "@peculiar/asn1-rsa": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.3.8.tgz", + "integrity": "sha512-rL8k2x59v8lZiwLRqdMMmOJ30GHt6yuHISFIuuWivWjAJjnxzZBVzMTQ72sknX5MeTSSvGwPmEFk2/N8+UztFQ==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.3.8.tgz", + "integrity": "sha512-+nONq5tcK7vm3qdY7ZKoSQGQjhJYMJbwJGbXLFOhmqsFIxEWyQPHyV99+wshOjpOjg0wUSSkEEzX2hx5P6EKeQ==", + "optional": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.3.8", + "@peculiar/asn1-pfx": "^2.3.8", + "@peculiar/asn1-pkcs8": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "@peculiar/asn1-x509-attr": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.8.tgz", + "integrity": "sha512-ES/RVEHu8VMYXgrg3gjb1m/XG0KJWnV4qyZZ7mAg7rrF3VTmRbLxO8mk+uy0Hme7geSMebp+Wvi2U6RLLEs12Q==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", + "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", + "optional": true, + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.8.tgz", + "integrity": "sha512-voKxGfDU1c6r9mKiN5ZUsZWh3Dy1BABvTM3cimf0tztNwyMJPhiXY94eRTgsMQe6ViLfT6EoXxkWVzcm3mFAFw==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "asn1js": "^3.0.5", + "ipaddr.js": "^2.1.0", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.3.8.tgz", + "integrity": "sha512-4Z8mSN95MOuX04Aku9BUyMdsMKtVQUqWnr627IheiWnwFoheUhX3R4Y2zh23M7m80r4/WG8MOAckRKc77IRv6g==", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.9.7.tgz", + "integrity": "sha512-O+fR1ge6U8upO52q5b3d4tF4SxUdK4IQ0y++Z/Wlqq+ySZUf+deHnbMlDB1YZsIQ/DXU0i5M7Y1DyF5kwpXouQ==", + "optional": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.3.8", + "@peculiar/asn1-csr": "^2.3.8", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-pkcs9": "^2.3.8", + "@peculiar/asn1-rsa": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "pvtsutils": "^1.3.5", + "reflect-metadata": "^0.2.1", + "tslib": "^2.6.2", + "tsyringe": "^4.8.0" + } + }, + "node_modules/@peculiar/x509/node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "optional": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@puppeteer/browsers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/archiver": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.4.tgz", + "integrity": "sha512-Lj7fLBIMwYFgViVVZHEdExZC3lVYsl+QL0VmdNdIzGZH544jHveYWij6qdnBgJQDnR7pMKliN9z2cPZFEbhyPw==", + "dev": true, + "dependencies": { + "@types/readdir-glob": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/busboy": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookies": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", + "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-compress": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/koa-compress/-/koa-compress-4.0.6.tgz", + "integrity": "sha512-jJzlsQTeIXRfQzY7y2Dl/syqOxvEaNJzFTJ8ZGaDFWvqgMmCXplfCGjPfHQgU7xYNKq/mCdN3YWus1YOldfIwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*", + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/node": { + "version": "20.14.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz", + "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.7.tgz", + "integrity": "sha512-+ge/loa0oTozxip6zmhRIk8Z/boU51wl9Q6QdLZcokIGMzY5lFXYy/x7Htj2HTC6/KZP1hUbZ1ekx8DYXICvWg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "peer": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + }, + "node_modules/@types/turndown": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.4.tgz", + "integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "peer": true + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "peer": true, + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@types/xmldom": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/@types/xmldom/-/xmldom-0.1.34.tgz", + "integrity": "sha512-7eZFfxI9XHYjJJuugddV6N5YNeXgQE1lArWOcd1eCOKWb/FGs5SIjacSYuEJuwhsGS3gy4RuZ5EUIcqYscuPDA==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.3.tgz", + "integrity": "sha512-W7fOe0N+t2eyL9sjDE+7bBNo/NZg6U6aU0Rp8wwQV8TRkzLnX13SvROoyJMAH0Kcm9G1DX9b1XI4LxwKxowsXw==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/archiver": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.2.tgz", + "integrity": "sha512-UQ/2nW7NMl1G+1UnrLypQw1VdT9XZg/ECcKPq7l+STzStrSivFIXIp34D8M5zeNGW5NoOupdYCHv6VySCPNNlw==", + "dependencies": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "dependencies": { + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "optional": true, + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "devOptional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.3.tgz", + "integrity": "sha512-7RYKL+vZVCyAsMLi5SPu7QGauGGT8avnP/HO571ndEuV4MYdGXvLhtW67FuLPeEI8EiIY7zbbRR9x7x7HU0kgw==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.2.tgz", + "integrity": "sha512-HZoJwzC+rZ9lqEemTMiO0luOePoGYNBgsLLgegKR/cljiJvcDNhDZQkzC+NC5Oh0aHbdBNSOHpghwMuB5tqhjg==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.2.1.tgz", + "integrity": "sha512-YTB47kHwBW9zSG8LD77MIBAAQXjU2WjAkMHeeb7hUplVs6+IoM5I7uEVQNPMB7lj9r8I76UMdoMkGnCodHOLqg==", + "optional": true, + "dependencies": { + "b4a": "^1.6.6", + "streamx": "^2.18.0" + } + }, + "node_modules/base32.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "peer": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "peer": true, + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001608", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", + "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.5.tgz", + "integrity": "sha512-RuLrmzYrxSb0s9SgpB+QN5jJucPduZQ/9SIe76MDxYJuecPW5mxMdacJ1f4EtgiV+R0p3sCkznTMvH0MPGFqjA==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/civkit": { + "version": "0.9.0-2570394", + "resolved": "https://registry.npmjs.org/civkit/-/civkit-0.9.0-2570394.tgz", + "integrity": "sha512-w77agnElTEP6g+l66KhX1Ib9z7JXbR3FaR5/2yTUPIPjm32qsWkmKRvv0mZ83IcMSSmTjF9LxboYAliyTx7cIA==", + "license": "AGPL", + "dependencies": { + "lodash": "^4.17.21", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@koa/router": "^12.0.0", + "@nodelib/fs.walk": "^1.2.8", + "@peculiar/x509": "^1.9.3", + "busboy": "^1.6.0", + "express": "^4.18.2", + "iconv-lite": "^0.6.3", + "js-yaml": "^4.1.0", + "jschardet": "^3.0.0", + "koa": "^2.15.4", + "koa-bodyparser": "^4.4.0", + "koa-compose": "^4.1.0", + "libmagic-ffi": "^0.1.4", + "mime": "^3.0.0", + "minimatch": "^10.0.1", + "minio": "^7.0.33", + "node-object-hash": "^3.0.0", + "node-schedule": "^2.1.1", + "nodemailer": "^6.9.1", + "pino": "^8.11.0", + "reflect-metadata": "^0.1.13", + "smtp-server": "^3.11.0", + "tld-extract": "^2.1.0", + "zod": "*", + "zod-openai": "*" + }, + "peerDependencies": { + "mongodb": "^6", + "tsyringe": "^4" + } + }, + "node_modules/civkit/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/civkit/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true, + "peer": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "peer": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compress-commons": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.3.tgz", + "integrity": "sha512-/UIcLWvwAQyVibgpQDPtfNM3SvqN7G9elAPAV7GM0L53EbNWwWiCsWtK8Fwed/APEbptPHXs5PuW+y8Bq8lFTA==", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "peer": true + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==", + "optional": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.1.tgz", + "integrity": "sha512-lO1dFui+CEUh/ztYIpgpKItKW9Bb4NWakCRJrnqAbFIYD+OZAwb2VfD5T5eXMw2FNcsDHkQcNl/Wh3iVXYwU6g==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "optional": true, + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==" + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1330662", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1330662.tgz", + "integrity": "sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw==" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domino": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", + "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/duplex-maker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/duplex-maker/-/duplex-maker-1.0.0.tgz", + "integrity": "sha512-KoHuzggxg7f+vvjqOHfXxaQYI1POzBm+ah0eec7YDssZmbt6QFBI8d1nl5GQwAgR2f+VQCPvyvZtmWWqWuFtlA==", + "license": "MIT" + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.735", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.735.tgz", + "integrity": "sha512-pkYpvwg8VyOTQAeBqZ7jsmpCjko1Qc6We1ZtZCjRyYbT5v4AIUKDy5cQTRotQlSSZmMr8jqpEt6JtOj5k7lR7A==", + "dev": true, + "peer": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "optional": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "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.10", + "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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/farmhash": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/farmhash/-/farmhash-3.3.1.tgz", + "integrity": "sha512-XUizHanzlr/v7suBr/o85HSakOoWh6HKXZjFYl5C2+Gj0f0rkw+XTUZzrd9odDsgI9G5tRUcF4wSbKaX04T0DQ==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^5.1.0", + "prebuild-install": "^7.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "devOptional": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "peer": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase-admin": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.1.0.tgz", + "integrity": "sha512-bU7uPKMmIXAihWxntpY/Ma9zucn5y3ec+HQPqFQ/zcEfP9Avk9E/6D8u+yT/VwKHNZyg7yDVWOoJi73TIdR4Ww==", + "dependencies": { + "@fastify/busboy": "^2.1.0", + "@firebase/database-compat": "^1.0.2", + "@firebase/database-types": "^1.0.0", + "@types/node": "^20.10.3", + "farmhash": "^3.3.0", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "long": "^5.2.3", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.1.0", + "@google-cloud/storage": "^7.7.0" + } + }, + "node_modules/firebase-functions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.1.1.tgz", + "integrity": "sha512-q+4zsQhX04YJUz6hqaiH/j5kixljPj0PMxkm8KN3juYp3I4NC6CZ4qfy5JRfwvV8VfXM2KkJrZuyJtLyZr97aw==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.17.21", + "cors": "^2.8.5", + "express": "^4.21.0", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.2.0.tgz", + "integrity": "sha512-UkOPIJH4I4qUGGSr4vaBcbAqn+YblVtMqRI2KQMW2nhMw5So91Iw1klu5Epk8vhEOhn1LPG5/tMaBI1MAtOt6Q==", + "dev": true, + "dependencies": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5", + "ts-deepmerge": "^2.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "firebase-functions": ">=4.9.0", + "jest": ">=28.0.0" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gaxios": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.5.0.tgz", + "integrity": "sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.7.0.tgz", + "integrity": "sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.2.tgz", + "integrity": "sha512-2mw7qgei2LPdtGrmd1zvxQviOcduTnsvAWYzCxhOWXK4IQKmQztHnDQwD0ApB690fBQJemFKSU7DnceAy3RLzw==", + "dependencies": { + "@grpc/grpc-js": "~1.10.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.0", + "protobufjs": "7.2.6", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "peer": true + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "peer": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/ipv6-normalize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz", + "integrity": "sha512-Bm6H79i01DjgGTCWjUuCjJ6QDo1HB96PT/xCYuyJUP9WFbVDrLSbG4EZCvOCun2rNswZb0c3e4Jt/ws795esHA==", + "optional": true + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-html": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-html/-/is-html-2.0.0.tgz", + "integrity": "sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==", + "dependencies": { + "html-tags": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-zst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-zst/-/is-zst-1.0.0.tgz", + "integrity": "sha512-ZA5lvshKAl8z30dX7saXLpVhpsq3d2EHK9uf7qtUjnOtdw4XBpAoWb2RvZ5kyoaebdoidnGI0g2hn9Z7ObPbww==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "peer": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "peer": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "peer": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jose": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.4.tgz", + "integrity": "sha512-6ScbIk2WWCeXkmzF6bRPmEuaqy1m8SbsRFMa/FLrSCkGIhj8OLVG/IH+XHVmNMx/KUo8cVWEE6oKR4dJ+S0Rkg==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jschardet": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.1.2.tgz", + "integrity": "sha512-mw3CBZGzW8nUBPYhFU2ztZ/kJ6NClQUQVpyzvFMfznZsoC///ZQ30J2RCUanNsr5yF22LqhgYr/lj807/ZleWA==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-stream/-/json-stream-1.0.0.tgz", + "integrity": "sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/jose": { + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", + "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/koa": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.0.tgz", + "integrity": "sha512-Afhqq0Vq3W7C+/rW6IqHVBDLzqObwZ07JaUNUEF8yCQ6afiyFE3RAy+i7V0E46XOWlH7vPWn/x0vsZwNy6PWxw==", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-bodyparser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.4.1.tgz", + "integrity": "sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==", + "optional": true, + "dependencies": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, + "node_modules/koa-compress": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.1.1.tgz", + "integrity": "sha512-UgMIN7ZoEP2DuoSQmD6CYvFSLt0NReGlc2qSY4bO4Oq0L56OiD9pDG41Kj/zFmVY/A3Wvmn4BqKcfq5H30LGIg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "compressible": "^2.0.18", + "http-errors": "^1.8.1", + "koa-is-json": "^1.0.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/koa-compress/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-compress/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-compress/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw==", + "license": "MIT" + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koffi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.9.0.tgz", + "integrity": "sha512-KCsuJ2gM58n6bNdR2Z7gqsh/3TchxxQFbVgax2/UvAjRTgwNSYAJDx9E3jrkBP4jEDHWRCfE47Y2OG+/fiSvEw==", + "hasInstallScript": true, + "optional": true + }, + "node_modules/langdetect": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/langdetect/-/langdetect-0.2.1.tgz", + "integrity": "sha512-vWK2xz8Urp6J0RFwnrR8+d0DQ2yIGjCOqAxBoXNMVFv4ZDmFsbCcNEtwuVnaQ5+ltwZ4Z5rTtuTwHSaEWFdw+A==", + "dependencies": { + "unicode-9.0.0": "0.7.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libmagic-ffi": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/libmagic-ffi/-/libmagic-ffi-0.1.4.tgz", + "integrity": "sha512-IVa12E6kLILyjqGX0h0FpGao2gFqls8yvyDx8EMMtXQB/8rhc7ERze+ip8f/WECakWQ7s2c/vRRe6lK4Nzhz9g==", + "optional": true, + "dependencies": { + "generic-pool": "^3.9.0", + "koffi": "^2.9.0", + "tslib": "^2.4.0" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/linkedom": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.4.tgz", + "integrity": "sha512-JhLErxMIEOKByMi3fURXgI1fYOzR87L1Cn0+MI9GlMckFrqFZpV1SUGox1jcKtsKN3y6JgclcQf0FzZT//BuGw==", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^9.1.0", + "uhyphen": "^0.2.0" + } + }, + "node_modules/linkedom/node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/maxmind": { + "version": "4.3.18", + "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.18.tgz", + "integrity": "sha512-5b9utU7ZxcGYTBaO7hCF0FXyfw3IpankLn+FnLW4RZS1zi97RBeSdfXJFJlk5UxNsMiFZlsdMT3lzvD+bD8MLQ==", + "dependencies": { + "mmdb-lib": "2.1.0", + "tiny-lru": "11.2.5" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "peer": true + }, + "node_modules/merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "dependencies": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minio": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minio/-/minio-7.1.3.tgz", + "integrity": "sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^0.2.13", + "fast-xml-parser": "^4.2.2", + "ipaddr.js": "^2.0.1", + "json-stream": "^1.0.0", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml": "^1.0.1", + "xml2js": "^0.5.0" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mmdb-lib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.1.0.tgz", + "integrity": "sha512-tdDTZmnI5G4UoSctv2KxM/3VQt2XRj4CmR5R4VsAWsOUcS3LysHR34wtixWm/pXxXdkBDuN92auxkC0T2+qd1Q==", + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/mongodb": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", + "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==", + "peer": true, + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "peer": true, + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "git+ssh://git@github.com/JCMais/nan.git#0ec2eca8b2fd7518affb3945d087e393ad839b7e" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-abi": { + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "peer": true + }, + "node_modules/node-libcurl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/node-libcurl/-/node-libcurl-4.1.0.tgz", + "integrity": "sha512-cwJ4pEqFmzUivMl0CtS2yYjBmZJ3/63Fl1WJCGzw45jXTCL04Ygbqvl+I5blMZm4ZOQChbATmQ6H4lxAnlIU+g==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "1.0.11", + "env-paths": "2.2.0", + "nan": "github:JCMais/nan#fix/electron-failures", + "node-gyp": "10.2.0", + "npmlog": "7.0.1", + "rimraf": "5.0.5", + "tslib": "2.6.2" + }, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/node-libcurl/node_modules/are-we-there-yet": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.2.tgz", + "integrity": "sha512-ncSWAawFhKMJDTdoAeOV+jyW1VCMj5QIAwULIBV0SSR7B/RLPPEQiknKcg/RIIZlUQrxELpsxMiTUoAQ4sIUyg==", + "deprecated": "This package is no longer supported.", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-libcurl/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-libcurl/node_modules/env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-libcurl/node_modules/gauge": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.2.tgz", + "integrity": "sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-libcurl/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-libcurl/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-libcurl/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/node-libcurl/node_modules/npmlog": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz", + "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-libcurl/node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-libcurl/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-object-hash/-/node-object-hash-3.0.0.tgz", + "integrity": "sha512-jLF6tlyletktvSAawuPmH1SReP0YfZQ+tBrDiTCK+Ai7eXPMS9odi5xW/iKC7ZhrWJJ0Z5xYcW/x+1fVMn1Qvw==", + "optional": true, + "engines": { + "node": ">=16", + "pnpm": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true, + "peer": true + }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "optional": true, + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "devOptional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" + }, + "node_modules/openai": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.33.0.tgz", + "integrity": "sha512-Sh4KvplkvkAREuhb8yZpohqsOo08cBBu6LNWLD8YyMxe8yCxbE+ouJYUs1X2oDPrzQGANj0rFNQYiwW9gWLBOg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", + "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT", + "optional": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfjs-dist": { + "version": "4.10.38", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz", + "integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.65" + } + }, + "node_modules/peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, + "node_modules/peek-stream/node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/peek-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/peek-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/peek-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/peek-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/peek-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz", + "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==", + "optional": true, + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "optional": true, + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz", + "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "optional": true + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-streams": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/process-streams/-/process-streams-1.0.3.tgz", + "integrity": "sha512-xkIaM5vYnyekB88WyET78YEqXiaJRy0xcvIdE22n+myhvBT7LlLmX6iAtq7jDvVH8CUx2rqQsd32JdRyJMV3NA==", + "funding": [ + "https://www.paypal.com/donate/?hosted_button_id=GB656ZSAEQEXN", + "https://de.liberapay.com/nils.knappmeier/" + ], + "license": "MIT", + "dependencies": { + "duplex-maker": "^1.0.0" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "optional": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.1.tgz", + "integrity": "sha512-8awBvjO+FwkMd6gNoGFZyqkHZXCFd54CIYTb6De7dPaufGJ2XNW+QUNqbMr8MaAocMdb+KpsD4rxEOaTBDCffA==", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.3.0.tgz", + "integrity": "sha512-e2jY8cdWSUGsrLxqGm3hIbJq/UIk1uOY8XY7SM51leXkH7shrIyE91lK90Q9byX6tte+cyL3HKqlWBEd6TjWTA==", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.6.5", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1330662", + "puppeteer-core": "23.3.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.3.0.tgz", + "integrity": "sha512-sB2SsVMFs4gKad5OCdv6w5vocvtEUrRl0zQqSyRPbo/cj1Ktbarmhxy02Zyb9R9HrssBcJDZbkrvBnbaesPyYg==", + "dependencies": { + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.6.5", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1330662", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-extra": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz", + "integrity": "sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==", + "dependencies": { + "@types/debug": "^4.1.0", + "debug": "^4.1.1", + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "@types/puppeteer": "*", + "puppeteer": "*", + "puppeteer-core": "*" + }, + "peerDependenciesMeta": { + "@types/puppeteer": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "puppeteer-core": { + "optional": true + } + } + }, + "node_modules/puppeteer-extra-plugin": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", + "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", + "dependencies": { + "@types/debug": "^4.1.0", + "debug": "^4.1.1", + "merge-deep": "^3.0.1" + }, + "engines": { + "node": ">=9.11.2" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } + } + }, + "node_modules/puppeteer-extra-plugin-block-resources": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-block-resources/-/puppeteer-extra-plugin-block-resources-2.4.3.tgz", + "integrity": "sha512-WLxkytm3Nn6D+3N8lFMIi9lL9lLA1mTRHZAflR+zDs9/d1oOYVy52pWPaiQaetOObgfHDpPlHBmzHE2Yhr3ZnQ==", + "dependencies": { + "debug": "^4.1.1", + "puppeteer-extra-plugin": "^3.2.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "puppeteer-extra": { + "optional": true + } + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "peer": true + }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "optional": true, + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "optional": true + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true, + "peer": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "optional": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "optional": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/replicate": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/replicate/-/replicate-0.16.1.tgz", + "integrity": "sha512-7GlY2IRJd4CwXLSO+eTNCRjYFlT10pgHQjYnwCBczr4+qdErIZp4sAEuRFfhzXNU3Bi+sYJJLpcddnUOFxStNg==", + "dev": true, + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "peer": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "devOptional": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robots-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", + "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "dependencies": { + "is-buffer": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-zstd": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/simple-zstd/-/simple-zstd-1.4.2.tgz", + "integrity": "sha512-kGYEvT33M5XfyQvvW4wxl3eKcWbdbCc1V7OZzuElnaXft0qbVzoIIXHXiCm3JCUki+MZKKmvjl8p2VGLJc5Y/A==", + "license": "MIT", + "dependencies": { + "is-zst": "^1.0.0", + "peek-stream": "^1.1.3", + "process-streams": "^1.0.1", + "through2": "^4.0.2" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smtp-server": { + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.13.3.tgz", + "integrity": "sha512-a5lGUfh0GkXGKHlDK/F6ErqIDOOGUGhcVnAiBB3a5iGQXPO95vY7LIceKsPYy96N8Aqsl4JZFANil46qh5m2mg==", + "optional": true, + "dependencies": { + "base32.js": "0.1.0", + "ipv6-normalize": "1.0.1", + "nodemailer": "6.9.11", + "punycode": "2.3.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/smtp-server/node_modules/nodemailer": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.11.tgz", + "integrity": "sha512-UiAkgiERuG94kl/3bKfE8o10epvDnl0vokNEtZDPTq9BWzIl6EFT9336SbIT4oaTBD8NmmUTLsQyXHV82eXSWg==", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "optional": true, + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "optional": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "peer": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "devOptional": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "peer": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/streamx": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz", + "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "peer": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stripe": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.18.0.tgz", + "integrity": "sha512-OUA32uhNoSoM6wOodyFbV+3IBCoO140uzdXmBArQ0S88D4EbH91xl2v+Ml1sKalcFKUBadHLeHfU/p9AbsOfGw==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg2png-wasm": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/svg2png-wasm/-/svg2png-wasm-1.4.1.tgz", + "integrity": "sha512-ZFy1NtwZVAsslaTQoI+/QqX2sg0vjmgJ/jGAuLZZvYcRlndI54hLPiwLC9JzXlFBerfxN5JiS7kpEUG0mrXS3Q==", + "license": "MIT" + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "peer": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "optional": true, + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/tiktoken": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.16.tgz", + "integrity": "sha512-hRcORIGF2YlAgWx3nzrGJOrKSJwLoc81HpXmMQk89632XAgURc7IeV2FgQ2iXo9z/J96fCvpsHg2kWoHcbj9fg==" + }, + "node_modules/tiny-lru": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.5.tgz", + "integrity": "sha512-JpqM0K33lG6iQGKiigcwuURAKZlq6rHXfrgeL4/I8/REoyJTGU+tEMszvT/oTRVHG2OiylhGDjqPp1jWMlr3bw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/tld-extract": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tld-extract/-/tld-extract-2.1.0.tgz", + "integrity": "sha512-Y9QHWIoDQPJJVm3/pOC7kOfOj7vsNSVZl4JGoEHb605FiwZgIfzSMyU0HC0wYw5Cx8435vaG1yGZtIm1yiQGOw==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "peer": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "peer": true, + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/ts-deepmerge": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz", + "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/turndown": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.1.3.tgz", + "integrity": "sha512-Z3/iJ6IWh8VBiACWQJaA5ulPQE5E1QwvBHj00uGzdQxdRnd8fh1DPqNOJqzQDu6DkOstORrtXzf/9adB+vMtEA==", + "dependencies": { + "domino": "^2.1.6" + } + }, + "node_modules/turndown-plugin-gfm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz", + "integrity": "sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==" + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.8.0.tgz", + "integrity": "sha512-vFv1GA99b7eKO1HG/4RPu2Is3FBTWBrmzqzO0mz+rLxN3yXkE4mqRcb8g8fHxzX4blEysrNZLqg5RbJLqX5buA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicode-9.0.0": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/unicode-9.0.0/-/unicode-9.0.0-0.7.0.tgz", + "integrity": "sha512-aqL0GQ3LB2nC3ZEKBZ93hfn7QY80sVGk+OT9Sc6lTxl4wRiwzUBRlAjJB9Fe4+5XydXOV+tb8udp5oRBoED5Tw==", + "deprecated": "Use @unicode/unicode-9.0.0 instead." + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "peer": true, + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz", + "integrity": "sha512-LfOdrUvPB8ZoXtvOBz6DlNClfvi//b5d56mSWyJi7XbH/HfhOHfUhOqxhT/rUiR7yiktlunqRo+jY6y/cWC/5g==", + "dependencies": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..09bfa7c --- /dev/null +++ b/package.json @@ -0,0 +1,85 @@ +{ + "name": "reader", + "scripts": { + "lint": "eslint --ext .js,.ts .", + "build": "node ./integrity-check.cjs && tsc -p .", + "build:watch": "tsc --watch", + "build:clean": "rm -rf ./build", + "serve": "npm run build && npm run start", + "debug": "npm run build && npm run dev", + "start": "node ./build/stand-alone/crawl.js", + "dry-run": "NODE_ENV=dry-run node ./build/stand-alone/search.js" + }, + "engines": { + "node": ">=18" + }, + "main": "build/index.js", + "dependencies": { + "@esm2cjs/normalize-url": "^8.0.0", + "@google-cloud/translate": "^8.2.0", + "@koa/bodyparser": "^5.1.1", + "@mozilla/readability": "^0.6.0", + "@napi-rs/canvas": "^0.1.68", + "@types/turndown": "^5.0.4", + "@xmldom/xmldom": "^0.9.3", + "archiver": "^6.0.1", + "axios": "^1.3.3", + "bcrypt": "^5.1.0", + "busboy": "^1.6.0", + "civkit": "^0.9.0-2570394", + "cors": "^2.8.5", + "dayjs": "^1.11.9", + "express": "^4.19.2", + "firebase-admin": "^12.1.0", + "firebase-functions": "^6.1.1", + "htmlparser2": "^9.0.0", + "jose": "^5.1.0", + "koa": "^2.16.0", + "koa-compress": "^5.1.1", + "langdetect": "^0.2.1", + "linkedom": "^0.18.4", + "lru-cache": "^11.0.2", + "maxmind": "^4.3.18", + "minio": "^7.1.3", + "node-libcurl": "^4.1.0", + "openai": "^4.20.0", + "pdfjs-dist": "^4.10.38", + "puppeteer": "^23.3.0", + "puppeteer-extra": "^3.3.6", + "puppeteer-extra-plugin-block-resources": "^2.4.3", + "robots-parser": "^3.0.1", + "set-cookie-parser": "^2.6.0", + "simple-zstd": "^1.4.2", + "stripe": "^11.11.0", + "svg2png-wasm": "^1.4.1", + "tiktoken": "^1.0.16", + "tld-extract": "^2.1.0", + "turndown": "^7.1.3", + "turndown-plugin-gfm": "^1.0.2", + "undici": "^7.8.0" + }, + "devDependencies": { + "@types/archiver": "^5.3.4", + "@types/bcrypt": "^5.0.0", + "@types/busboy": "^1.5.4", + "@types/cors": "^2.8.17", + "@types/koa": "^2.15.0", + "@types/koa-compress": "^4.0.6", + "@types/node": "^20.14.13", + "@types/set-cookie-parser": "^2.4.7", + "@types/xmldom": "^0.1.34", + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-import": "^2.25.4", + "firebase-functions-test": "^3.0.0", + "pino-pretty": "^13.0.0", + "replicate": "^0.16.1", + "typescript": "^5.5.4" + }, + "private": true, + "exports": { + ".": "./build/index.js" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..1f9ee24 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..c6742d8 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-Agent: * +Disallow: / diff --git a/src/api/crawler.ts b/src/api/crawler.ts new file mode 100644 index 0000000..c5679bc --- /dev/null +++ b/src/api/crawler.ts @@ -0,0 +1,1374 @@ +import { singleton } from 'tsyringe'; +import { pathToFileURL } from 'url'; +import { randomUUID } from 'crypto'; +import _ from 'lodash'; + +import { + assignTransferProtocolMeta, RPCHost, RPCReflection, + AssertionFailureError, ParamValidationError, + RawString, + ApplicationError, + DataStreamBrokenError, + assignMeta, +} from 'civkit/civ-rpc'; +import { marshalErrorLike } from 'civkit/lang'; +import { Defer } from 'civkit/defer'; +import { retryWith } from 'civkit/decorators'; +import { FancyFile } from 'civkit/fancy-file'; + +import { CONTENT_FORMAT, CrawlerOptions, CrawlerOptionsHeaderOnly, ENGINE_TYPE, RESPOND_TIMING } from '../dto/crawler-options'; + +import { Crawled } from '../db/crawled'; +import { DomainBlockade } from '../db/domain-blockade'; +import { OutputServerEventStream } from '../lib/transform-server-event-stream'; + +import { PageSnapshot, PuppeteerControl, ScrappingOptions } from '../services/puppeteer'; +import { JSDomControl } from '../services/jsdom'; +import { FormattedPage, md5Hasher, SnapshotFormatter } from '../services/snapshot-formatter'; +import { CurlControl } from '../services/curl'; +import { LmControl } from '../services/lm'; +import { tryDecodeURIComponent } from '../utils/misc'; +import { CFBrowserRendering } from '../services/cf-browser-rendering'; + +import { GlobalLogger } from '../services/logger'; +import { RateLimitControl, RateLimitDesc } from '../shared/services/rate-limit'; +import { AsyncLocalContext } from '../services/async-context'; +import { Context, Ctx, Method, Param, RPCReflect } from '../services/registry'; +import { + BudgetExceededError, InsufficientBalanceError, + SecurityCompromiseError, ServiceBadApproachError, ServiceBadAttemptError, + ServiceNodeResourceDrainError +} from '../services/errors'; + +import { countGPTToken as estimateToken } from '../shared/utils/openai'; +import { ProxyProviderService } from '../shared/services/proxy-provider'; +import { FirebaseStorageBucketControl } from '../shared/services/firebase-storage-bucket'; +import { JinaEmbeddingsAuthDTO } from '../dto/jina-embeddings-auth'; +import { RobotsTxtService } from '../services/robots-text'; +import { TempFileManager } from '../services/temp-file'; +import { MiscService } from '../services/misc'; +import { HTTPServiceError } from 'civkit/http'; +import { GeoIPService } from '../services/geoip'; + +export interface ExtraScrappingOptions extends ScrappingOptions { + withIframe?: boolean | 'quoted'; + withShadowDom?: boolean; + targetSelector?: string | string[]; + removeSelector?: string | string[]; + keepImgDataUrl?: boolean; + engine?: string; + allocProxy?: string; + private?: boolean; + countryHint?: string; +} + +const indexProto = { + toString: function (): string { + return _(this) + .toPairs() + .map(([k, v]) => k ? `[${_.upperFirst(_.lowerCase(k))}] ${v}` : '') + .value() + .join('\n') + '\n'; + } +}; + +@singleton() +export class CrawlerHost extends RPCHost { + logger = this.globalLogger.child({ service: this.constructor.name }); + + cacheRetentionMs = 1000 * 3600 * 24 * 7; + cacheValidMs = 1000 * 3600; + urlValidMs = 1000 * 3600 * 4; + abuseBlockMs = 1000 * 3600; + domainProfileRetentionMs = 1000 * 3600 * 24 * 30; + + batchedCaches: Crawled[] = []; + + constructor( + protected globalLogger: GlobalLogger, + protected puppeteerControl: PuppeteerControl, + protected curlControl: CurlControl, + protected cfBrowserRendering: CFBrowserRendering, + protected proxyProvider: ProxyProviderService, + protected lmControl: LmControl, + protected jsdomControl: JSDomControl, + protected snapshotFormatter: SnapshotFormatter, + protected firebaseObjectStorage: FirebaseStorageBucketControl, + protected rateLimitControl: RateLimitControl, + protected threadLocal: AsyncLocalContext, + protected robotsTxtService: RobotsTxtService, + protected tempFileManager: TempFileManager, + protected geoIpService: GeoIPService, + protected miscService: MiscService, + ) { + super(...arguments); + + puppeteerControl.on('crawled', async (snapshot: PageSnapshot, options: ExtraScrappingOptions & { url: URL; }) => { + if (!snapshot.title?.trim() && !snapshot.pdfs?.length) { + return; + } + if (options.cookies?.length || options.private) { + // Potential privacy issue, dont cache if cookies are used + return; + } + if (options.injectFrameScripts?.length || options.injectPageScripts?.length || options.viewport) { + // Potentially mangeled content, dont cache if scripts are injected + return; + } + if (snapshot.isIntermediate) { + return; + } + if (!snapshot.lastMutationIdle) { + // Never reached mutationIdle, presumably too short timeout + return; + } + if (options.locale) { + Reflect.set(snapshot, 'locale', options.locale); + } + + const analyzed = await this.jsdomControl.analyzeHTMLTextLite(snapshot.html); + if (analyzed.tokens < 200) { + // Does not contain enough content + if (snapshot.status !== 200) { + return; + } + if (snapshot.html.includes('captcha') || snapshot.html.includes('cf-turnstile')) { + return; + } + } + + await this.setToCache(options.url, snapshot); + }); + + puppeteerControl.on('abuse', async (abuseEvent: { url: URL; reason: string, sn: number; }) => { + this.logger.warn(`Abuse detected on ${abuseEvent.url}, blocking ${abuseEvent.url.hostname}`, { reason: abuseEvent.reason, sn: abuseEvent.sn }); + + await DomainBlockade.save(DomainBlockade.from({ + domain: abuseEvent.url.hostname.toLowerCase(), + triggerReason: `${abuseEvent.reason}`, + triggerUrl: abuseEvent.url.toString(), + createdAt: new Date(), + expireAt: new Date(Date.now() + this.abuseBlockMs), + })).catch((err) => { + this.logger.warn(`Failed to save domain blockade for ${abuseEvent.url.hostname}`, { err: marshalErrorLike(err) }); + }); + + }); + + setInterval(() => { + const thisBatch = this.batchedCaches; + this.batchedCaches = []; + if (!thisBatch.length) { + return; + } + const batch = Crawled.DB.batch(); + + for (const x of thisBatch) { + batch.set(Crawled.COLLECTION.doc(x._id), x.degradeForFireStore(), { merge: true }); + } + + batch.commit() + .then(() => { + this.logger.debug(`Saved ${thisBatch.length} caches by batch`); + }) + .catch((err) => { + this.logger.warn(`Failed to save cache in batch`, { err }); + }); + }, 1000 * 10 + Math.round(1000 * Math.random())).unref(); + } + + override async init() { + await this.dependencyReady(); + + if (this.puppeteerControl.effectiveUA) { + this.curlControl.impersonateChrome(this.puppeteerControl.effectiveUA); + } + + this.emit('ready'); + } + + async getIndex(auth?: JinaEmbeddingsAuthDTO) { + const indexObject: Record = Object.create(indexProto); + Object.assign(indexObject, { + usage1: 'https://r.jina.ai/YOUR_URL', + usage2: 'https://s.jina.ai/YOUR_SEARCH_QUERY', + homepage: 'https://jina.ai/reader', + }); + + await auth?.solveUID(); + if (auth && auth.user) { + indexObject[''] = undefined; + indexObject.authenticatedAs = `${auth.user.user_id} (${auth.user.full_name})`; + indexObject.balanceLeft = auth.user.wallet.total_balance; + } + + return indexObject; + } + + @Method({ + name: 'getIndex', + description: 'Index of the service', + proto: { + http: { + action: 'get', + path: '/', + } + }, + tags: ['misc', 'crawl'], + returnType: [String, Object], + }) + async getIndexCtrl(@Ctx() ctx: Context, @Param({ required: false }) auth?: JinaEmbeddingsAuthDTO) { + const indexObject = await this.getIndex(auth); + + if (!ctx.accepts('text/plain') && (ctx.accepts('text/json') || ctx.accepts('application/json'))) { + return indexObject; + } + + return assignTransferProtocolMeta(`${indexObject}`, + { contentType: 'text/plain; charset=utf-8', envelope: null } + ); + } + + + @Method({ + name: 'crawlByPostingToIndex', + description: 'Crawl any url into markdown', + proto: { + http: { + action: 'POST', + path: '/', + } + }, + tags: ['crawl'], + returnType: [String, OutputServerEventStream], + }) + @Method({ + description: 'Crawl any url into markdown', + proto: { + http: { + action: ['GET', 'POST'], + path: '::url', + } + }, + tags: ['crawl'], + returnType: [String, OutputServerEventStream, RawString], + }) + async crawl( + @RPCReflect() rpcReflect: RPCReflection, + @Ctx() ctx: Context, + auth: JinaEmbeddingsAuthDTO, + crawlerOptionsHeaderOnly: CrawlerOptionsHeaderOnly, + crawlerOptionsParamsAllowed: CrawlerOptions, + ) { + const uid = await auth.solveUID(); + let chargeAmount = 0; + const crawlerOptions = ctx.method === 'GET' ? crawlerOptionsHeaderOnly : crawlerOptionsParamsAllowed; + const tierPolicy = await this.saasAssertTierPolicy(crawlerOptions, auth); + + // Use koa ctx.URL, a standard URL object to avoid node.js framework prop naming confusion + const targetUrl = await this.getTargetUrl(tryDecodeURIComponent(`${ctx.URL.pathname}${ctx.URL.search}`), crawlerOptions); + if (!targetUrl) { + return await this.getIndex(auth); + } + + // Prevent circular crawling + this.puppeteerControl.circuitBreakerHosts.add( + ctx.hostname.toLowerCase() + ); + + if (uid) { + const user = await auth.assertUser(); + if (!(user.wallet.total_balance > 0)) { + throw new InsufficientBalanceError(`Account balance not enough to run this query, please recharge.`); + } + + const rateLimitPolicy = auth.getRateLimits('CRAWL') || [ + parseInt(user.metadata?.speed_level) >= 2 ? + RateLimitDesc.from({ + occurrence: 5000, + periodSeconds: 60 + }) : + RateLimitDesc.from({ + occurrence: 500, + periodSeconds: 60 + }) + ]; + + const apiRoll = await this.rateLimitControl.simpleRPCUidBasedLimit( + rpcReflect, uid, ['CRAWL'], + ...rateLimitPolicy + ); + + rpcReflect.finally(() => { + if (crawlerOptions.tokenBudget && chargeAmount > crawlerOptions.tokenBudget) { + return; + } + if (chargeAmount) { + auth.reportUsage(chargeAmount, `reader-crawl`).catch((err) => { + this.logger.warn(`Unable to report usage for ${uid}`, { err: marshalErrorLike(err) }); + }); + apiRoll.chargeAmount = chargeAmount; + } + }); + } else if (ctx.ip) { + const apiRoll = await this.rateLimitControl.simpleRpcIPBasedLimit(rpcReflect, ctx.ip, ['CRAWL'], + [ + // 20 requests per minute + new Date(Date.now() - 60 * 1000), 20 + ] + ); + + rpcReflect.finally(() => { + if (crawlerOptions.tokenBudget && chargeAmount > crawlerOptions.tokenBudget) { + return; + } + apiRoll.chargeAmount = chargeAmount; + }); + } + + if (!uid) { + // Enforce no proxy is allocated for anonymous users due to abuse. + crawlerOptions.proxy = 'none'; + const blockade = (await DomainBlockade.fromFirestoreQuery( + DomainBlockade.COLLECTION + .where('domain', '==', targetUrl.hostname.toLowerCase()) + .where('expireAt', '>=', new Date()) + .limit(1) + ))[0]; + if (blockade) { + throw new SecurityCompromiseError(`Domain ${targetUrl.hostname} blocked until ${blockade.expireAt || 'Eternally'} due to previous abuse found on ${blockade.triggerUrl || 'site'}: ${blockade.triggerReason}`); + } + } + const crawlOpts = await this.configure(crawlerOptions); + this.logger.info(`Accepting request from ${uid || ctx.ip}`, { opts: crawlerOptions }); + if (crawlerOptions.robotsTxt) { + await this.robotsTxtService.assertAccessAllowed(targetUrl, crawlerOptions.robotsTxt); + } + if (rpcReflect.signal.aborted) { + return; + } + if (!ctx.accepts('text/plain') && ctx.accepts('text/event-stream')) { + const sseStream = new OutputServerEventStream(); + rpcReflect.return(sseStream); + + try { + for await (const scrapped of this.iterSnapshots(targetUrl, crawlOpts, crawlerOptions)) { + if (!scrapped) { + continue; + } + if (rpcReflect.signal.aborted) { + break; + } + + const formatted = await this.formatSnapshot(crawlerOptions, scrapped, targetUrl, this.urlValidMs, crawlOpts); + chargeAmount = this.assignChargeAmount(formatted, tierPolicy); + sseStream.write({ + event: 'data', + data: formatted, + }); + if (chargeAmount && scrapped.pdfs?.length) { + break; + } + } + } catch (err: any) { + this.logger.error(`Failed to crawl ${targetUrl}`, { err: marshalErrorLike(err) }); + sseStream.write({ + event: 'error', + data: marshalErrorLike(err), + }); + } + + sseStream.end(); + + return sseStream; + } + + let lastScrapped; + if (!ctx.accepts('text/plain') && (ctx.accepts('text/json') || ctx.accepts('application/json'))) { + try { + for await (const scrapped of this.iterSnapshots(targetUrl, crawlOpts, crawlerOptions)) { + lastScrapped = scrapped; + if (rpcReflect.signal.aborted) { + break; + } + if (!scrapped || !crawlerOptions.isSnapshotAcceptableForEarlyResponse(scrapped)) { + continue; + } + if (!scrapped.title) { + continue; + } + + const formatted = await this.formatSnapshot(crawlerOptions, scrapped, targetUrl, this.urlValidMs, crawlOpts); + chargeAmount = this.assignChargeAmount(formatted, tierPolicy); + + if (scrapped?.pdfs?.length && !chargeAmount) { + continue; + } + + return formatted; + } + } catch (err) { + if (!lastScrapped) { + throw err; + } + } + + if (!lastScrapped) { + if (crawlOpts.targetSelector) { + throw new AssertionFailureError(`No content available for URL ${targetUrl} with target selector ${Array.isArray(crawlOpts.targetSelector) ? crawlOpts.targetSelector.join(', ') : crawlOpts.targetSelector}`); + } + throw new AssertionFailureError(`No content available for URL ${targetUrl}`); + } + + const formatted = await this.formatSnapshot(crawlerOptions, lastScrapped, targetUrl, this.urlValidMs, crawlOpts); + chargeAmount = this.assignChargeAmount(formatted, tierPolicy); + + return formatted; + } + + if (crawlerOptions.isRequestingCompoundContentFormat()) { + throw new ParamValidationError({ + path: 'respondWith', + message: `You are requesting compound content format, please explicitly accept 'text/event-stream' or 'application/json' in header.` + }); + } + + try { + for await (const scrapped of this.iterSnapshots(targetUrl, crawlOpts, crawlerOptions)) { + lastScrapped = scrapped; + if (rpcReflect.signal.aborted) { + break; + } + if (!scrapped || !crawlerOptions.isSnapshotAcceptableForEarlyResponse(scrapped)) { + continue; + } + if (!scrapped.title) { + continue; + } + + const formatted = await this.formatSnapshot(crawlerOptions, scrapped, targetUrl, this.urlValidMs, crawlOpts); + chargeAmount = this.assignChargeAmount(formatted, tierPolicy); + + if (crawlerOptions.respondWith === 'screenshot' && Reflect.get(formatted, 'screenshotUrl')) { + return assignTransferProtocolMeta(`${formatted.textRepresentation}`, + { code: 302, envelope: null, headers: { Location: Reflect.get(formatted, 'screenshotUrl') } } + ); + } + if (crawlerOptions.respondWith === 'pageshot' && Reflect.get(formatted, 'pageshotUrl')) { + return assignTransferProtocolMeta(`${formatted.textRepresentation}`, + { code: 302, envelope: null, headers: { Location: Reflect.get(formatted, 'pageshotUrl') } } + ); + } + + return assignTransferProtocolMeta(`${formatted.textRepresentation}`, { contentType: 'text/plain; charset=utf-8', envelope: null }); + } + } catch (err) { + if (!lastScrapped) { + throw err; + } + } + + if (!lastScrapped) { + if (crawlOpts.targetSelector) { + throw new AssertionFailureError(`No content available for URL ${targetUrl} with target selector ${Array.isArray(crawlOpts.targetSelector) ? crawlOpts.targetSelector.join(', ') : crawlOpts.targetSelector}`); + } + throw new AssertionFailureError(`No content available for URL ${targetUrl}`); + } + const formatted = await this.formatSnapshot(crawlerOptions, lastScrapped, targetUrl, this.urlValidMs, crawlOpts); + chargeAmount = this.assignChargeAmount(formatted, tierPolicy); + + if (crawlerOptions.respondWith === 'screenshot' && Reflect.get(formatted, 'screenshotUrl')) { + + return assignTransferProtocolMeta(`${formatted.textRepresentation}`, + { code: 302, envelope: null, headers: { Location: Reflect.get(formatted, 'screenshotUrl') } } + ); + } + if (crawlerOptions.respondWith === 'pageshot' && Reflect.get(formatted, 'pageshotUrl')) { + + return assignTransferProtocolMeta(`${formatted.textRepresentation}`, + { code: 302, envelope: null, headers: { Location: Reflect.get(formatted, 'pageshotUrl') } } + ); + } + + return assignTransferProtocolMeta(`${formatted.textRepresentation}`, { contentType: 'text/plain; charset=utf-8', envelope: null }); + + } + + async getTargetUrl(originPath: string, crawlerOptions: CrawlerOptions) { + let url: string = ''; + + const targetUrlFromGet = originPath.slice(1); + if (crawlerOptions.pdf) { + const pdfFile = crawlerOptions.pdf; + const identifier = pdfFile instanceof FancyFile ? (await pdfFile.sha256Sum) : randomUUID(); + url = `blob://pdf/${identifier}`; + crawlerOptions.url ??= url; + } else if (targetUrlFromGet) { + url = targetUrlFromGet.trim(); + } else if (crawlerOptions.url) { + url = crawlerOptions.url.trim(); + } + + if (!url) { + throw new ParamValidationError({ + message: 'No URL provided', + path: 'url' + }); + } + + const { url: safeURL, ips } = await this.miscService.assertNormalizedUrl(url); + if (this.puppeteerControl.circuitBreakerHosts.has(safeURL.hostname.toLowerCase())) { + throw new SecurityCompromiseError({ + message: `Circular hostname: ${safeURL.protocol}`, + path: 'url' + }); + } + crawlerOptions._hintIps = ips; + + return safeURL; + } + + getUrlDigest(urlToCrawl: URL) { + const normalizedURL = new URL(urlToCrawl); + if (!normalizedURL.hash.startsWith('#/')) { + normalizedURL.hash = ''; + } + const normalizedUrl = normalizedURL.toString().toLowerCase(); + const digest = md5Hasher.hash(normalizedUrl.toString()); + + return digest; + } + + async *queryCache(urlToCrawl: URL, cacheTolerance: number) { + const digest = this.getUrlDigest(urlToCrawl); + + const cache = ( + await + (Crawled.fromFirestoreQuery( + Crawled.COLLECTION.where('urlPathDigest', '==', digest).orderBy('createdAt', 'desc').limit(1) + ).catch((err) => { + this.logger.warn(`Failed to query cache, unknown issue`, { err }); + // https://github.com/grpc/grpc-node/issues/2647 + // https://github.com/googleapis/nodejs-firestore/issues/1023 + // https://github.com/googleapis/nodejs-firestore/issues/1023 + + return undefined; + })) + )?.[0]; + + yield cache; + + if (!cache) { + return; + } + + const age = Date.now() - cache.createdAt.valueOf(); + const stale = cache.createdAt.valueOf() < (Date.now() - cacheTolerance); + this.logger.info(`${stale ? 'Stale cache exists' : 'Cache hit'} for ${urlToCrawl}, normalized digest: ${digest}, ${age}ms old, tolerance ${cacheTolerance}ms`, { + url: urlToCrawl, digest, age, stale, cacheTolerance + }); + + let snapshot: PageSnapshot | undefined; + let screenshotUrl: string | undefined; + let pageshotUrl: string | undefined; + const preparations = [ + this.firebaseObjectStorage.downloadFile(`snapshots/${cache._id}`).then((r) => { + snapshot = JSON.parse(r.toString('utf-8')); + }), + cache.screenshotAvailable ? + this.firebaseObjectStorage.signDownloadUrl(`screenshots/${cache._id}`, Date.now() + this.urlValidMs).then((r) => { + screenshotUrl = r; + }) : + Promise.resolve(undefined), + cache.pageshotAvailable ? + this.firebaseObjectStorage.signDownloadUrl(`pageshots/${cache._id}`, Date.now() + this.urlValidMs).then((r) => { + pageshotUrl = r; + }) : + Promise.resolve(undefined) + ]; + try { + await Promise.all(preparations); + } catch (_err) { + // Swallow cache errors. + return undefined; + } + + yield { + isFresh: !stale, + ...cache, + snapshot: { + ...snapshot, + screenshot: undefined, + pageshot: undefined, + screenshotUrl, + pageshotUrl, + } as PageSnapshot & { screenshotUrl?: string; pageshotUrl?: string; } + }; + } + + async setToCache(urlToCrawl: URL, snapshot: PageSnapshot) { + const digest = this.getUrlDigest(urlToCrawl); + + this.logger.info(`Caching snapshot of ${urlToCrawl}...`, { url: urlToCrawl, digest, title: snapshot?.title, href: snapshot?.href }); + const nowDate = new Date(); + + const cache = Crawled.from({ + _id: randomUUID(), + url: urlToCrawl.toString(), + createdAt: nowDate, + expireAt: new Date(nowDate.valueOf() + this.cacheRetentionMs), + htmlSignificantlyModifiedByJs: snapshot.htmlSignificantlyModifiedByJs, + urlPathDigest: digest, + }); + + const savingOfSnapshot = this.firebaseObjectStorage.saveFile(`snapshots/${cache._id}`, + Buffer.from( + JSON.stringify({ + ...snapshot, + screenshot: undefined, + pageshot: undefined, + }), + 'utf-8' + ), + { + metadata: { + contentType: 'application/json', + } + } + ).then((r) => { + cache.snapshotAvailable = true; + return r; + }); + + if (snapshot.screenshot) { + await this.firebaseObjectStorage.saveFile(`screenshots/${cache._id}`, snapshot.screenshot, { + metadata: { + contentType: 'image/png', + } + }); + cache.screenshotAvailable = true; + } + if (snapshot.pageshot) { + await this.firebaseObjectStorage.saveFile(`pageshots/${cache._id}`, snapshot.pageshot, { + metadata: { + contentType: 'image/png', + } + }); + cache.pageshotAvailable = true; + } + await savingOfSnapshot; + this.batchedCaches.push(cache); + // const r = await Crawled.save(cache.degradeForFireStore()).catch((err) => { + // this.logger.error(`Failed to save cache for ${urlToCrawl}`, { err: marshalErrorLike(err) }); + + // return undefined; + // }); + + return cache; + } + + async *iterSnapshots(urlToCrawl: URL, crawlOpts?: ExtraScrappingOptions, crawlerOpts?: CrawlerOptions) { + // if (crawlerOpts?.respondWith.includes(CONTENT_FORMAT.VLM)) { + // const finalBrowserSnapshot = await this.getFinalSnapshot(urlToCrawl, { + // ...crawlOpts, engine: ENGINE_TYPE.BROWSER + // }, crawlerOpts); + + // yield* this.lmControl.geminiFromBrowserSnapshot(finalBrowserSnapshot); + + // return; + // } + + if (crawlerOpts?.respondWith.includes(CONTENT_FORMAT.READER_LM)) { + const finalAutoSnapshot = await this.getFinalSnapshot(urlToCrawl, { + ...crawlOpts, + engine: crawlOpts?.engine || ENGINE_TYPE.AUTO, + }, CrawlerOptions.from({ + ...crawlerOpts, + respondWith: 'html', + })); + + if (!finalAutoSnapshot?.html) { + throw new AssertionFailureError(`Unexpected non HTML content for ReaderLM: ${urlToCrawl}`); + } + + if (crawlerOpts?.instruction || crawlerOpts?.jsonSchema) { + const jsonSchema = crawlerOpts.jsonSchema ? JSON.stringify(crawlerOpts.jsonSchema, undefined, 2) : undefined; + yield* this.lmControl.readerLMFromSnapshot(crawlerOpts.instruction, jsonSchema, finalAutoSnapshot); + + return; + } + + try { + yield* this.lmControl.readerLMMarkdownFromSnapshot(finalAutoSnapshot); + } catch (err) { + if (err instanceof HTTPServiceError && err.status === 429) { + throw new ServiceNodeResourceDrainError(`Reader LM is at capacity, please try again later.`); + } + throw err; + } + + return; + } + + yield* this.cachedScrap(urlToCrawl, crawlOpts, crawlerOpts); + } + + async *cachedScrap(urlToCrawl: URL, crawlOpts?: ExtraScrappingOptions, crawlerOpts?: CrawlerOptions) { + if (crawlerOpts?.html) { + const snapshot = { + href: urlToCrawl.toString(), + html: crawlerOpts.html, + title: '', + text: '', + } as PageSnapshot; + yield this.jsdomControl.narrowSnapshot(snapshot, crawlOpts); + + return; + } + + if (crawlerOpts?.pdf) { + const pdfFile = crawlerOpts.pdf instanceof FancyFile ? crawlerOpts.pdf : this.tempFileManager.cacheBuffer(Buffer.from(crawlerOpts.pdf, 'base64')); + const pdfLocalPath = pathToFileURL((await pdfFile.filePath)); + const snapshot = { + href: urlToCrawl.toString(), + html: ``, + title: '', + text: '', + pdfs: [pdfLocalPath.href], + } as PageSnapshot; + + yield this.jsdomControl.narrowSnapshot(snapshot, crawlOpts); + + return; + } + + if ( + crawlOpts?.engine === ENGINE_TYPE.CURL || + // deprecated name + crawlOpts?.engine === 'direct' + ) { + let sideLoaded; + try { + sideLoaded = (crawlOpts?.allocProxy && !crawlOpts?.proxyUrl) ? + await this.sideLoadWithAllocatedProxy(urlToCrawl, crawlOpts) : + await this.curlControl.sideLoad(urlToCrawl, crawlOpts); + + } catch (err) { + if (err instanceof ServiceBadAttemptError) { + throw new AssertionFailureError(err.message); + } + throw err; + } + if (!sideLoaded?.file) { + throw new AssertionFailureError(`Remote server did not return a body: ${urlToCrawl}`); + } + const draftSnapshot = await this.snapshotFormatter.createSnapshotFromFile(urlToCrawl, sideLoaded.file, sideLoaded.contentType, sideLoaded.fileName); + draftSnapshot.status = sideLoaded.status; + draftSnapshot.statusText = sideLoaded.statusText; + yield this.jsdomControl.narrowSnapshot(draftSnapshot, crawlOpts); + return; + } + if (crawlOpts?.engine === ENGINE_TYPE.CF_BROWSER_RENDERING) { + const html = await this.cfBrowserRendering.fetchContent(urlToCrawl.href); + const snapshot = { + href: urlToCrawl.toString(), + html, + title: '', + text: '', + } as PageSnapshot; + yield this.jsdomControl.narrowSnapshot(snapshot, crawlOpts); + return; + } + + const cacheTolerance = crawlerOpts?.cacheTolerance ?? this.cacheValidMs; + const cacheIt = this.queryCache(urlToCrawl, cacheTolerance); + + let cache = (await cacheIt.next()).value; + if (cache?.htmlSignificantlyModifiedByJs === false) { + if (crawlerOpts && crawlerOpts.timeout === undefined) { + crawlerOpts.respondTiming ??= RESPOND_TIMING.HTML; + } + } + + if (!crawlerOpts || crawlerOpts.isCacheQueryApplicable()) { + cache = (await cacheIt.next()).value; + } + cacheIt.return(undefined); + + if (cache?.isFresh && + (!crawlOpts?.favorScreenshot || (crawlOpts?.favorScreenshot && (cache.screenshotAvailable && cache.pageshotAvailable))) && + (_.get(cache.snapshot, 'locale') === crawlOpts?.locale) + ) { + if (cache.snapshot) { + cache.snapshot.isFromCache = true; + } + yield this.jsdomControl.narrowSnapshot(cache.snapshot, crawlOpts); + + return; + } + + if (crawlOpts?.engine !== ENGINE_TYPE.BROWSER && !this.knownUrlThatSideLoadingWouldCrashTheBrowser(urlToCrawl)) { + const sideLoadSnapshotPermitted = crawlerOpts?.browserIsNotRequired() && + [RESPOND_TIMING.HTML, RESPOND_TIMING.VISIBLE_CONTENT].includes(crawlerOpts.presumedRespondTiming); + try { + const altOpts = { ...crawlOpts }; + let sideLoaded = (crawlOpts?.allocProxy && !crawlOpts?.proxyUrl) ? + await this.sideLoadWithAllocatedProxy(urlToCrawl, altOpts) : + await this.curlControl.sideLoad(urlToCrawl, altOpts).catch((err) => { + this.logger.warn(`Failed to side load ${urlToCrawl.origin}`, { err: marshalErrorLike(err), href: urlToCrawl.href }); + + if (err instanceof ApplicationError && !(err instanceof ServiceBadAttemptError)) { + return Promise.reject(err); + } + + return this.sideLoadWithAllocatedProxy(urlToCrawl, altOpts); + }); + if (!sideLoaded.file) { + throw new ServiceBadAttemptError(`Remote server did not return a body: ${urlToCrawl}`); + } + const draftSnapshot = await this.snapshotFormatter.createSnapshotFromFile( + urlToCrawl, sideLoaded.file, sideLoaded.contentType, sideLoaded.fileName + ).catch((err) => { + if (err instanceof ApplicationError) { + return Promise.reject(new ServiceBadAttemptError(err.message)); + } + return Promise.reject(err); + }); + draftSnapshot.status = sideLoaded.status; + draftSnapshot.statusText = sideLoaded.statusText; + if (sideLoaded.status == 200 && !sideLoaded.contentType.startsWith('text/html')) { + yield draftSnapshot; + return; + } + + let analyzed = await this.jsdomControl.analyzeHTMLTextLite(draftSnapshot.html); + draftSnapshot.title ??= analyzed.title; + draftSnapshot.isIntermediate = true; + if (sideLoadSnapshotPermitted) { + yield this.jsdomControl.narrowSnapshot(draftSnapshot, crawlOpts); + } + let fallbackProxyIsUsed = false; + if ( + ((!crawlOpts?.allocProxy || crawlOpts.allocProxy !== 'none') && !crawlOpts?.proxyUrl) && + (analyzed.tokens < 42 || sideLoaded.status !== 200) + ) { + const proxyLoaded = await this.sideLoadWithAllocatedProxy(urlToCrawl, altOpts); + if (!proxyLoaded.file) { + throw new ServiceBadAttemptError(`Remote server did not return a body: ${urlToCrawl}`); + } + const proxySnapshot = await this.snapshotFormatter.createSnapshotFromFile( + urlToCrawl, proxyLoaded.file, proxyLoaded.contentType, proxyLoaded.fileName + ).catch((err) => { + if (err instanceof ApplicationError) { + return Promise.reject(new ServiceBadAttemptError(err.message)); + } + return Promise.reject(err); + }); + proxySnapshot.status = proxyLoaded.status; + proxySnapshot.statusText = proxyLoaded.statusText; + if (proxyLoaded.status === 200 && crawlerOpts?.browserIsNotRequired()) { + } + analyzed = await this.jsdomControl.analyzeHTMLTextLite(proxySnapshot.html); + if (proxyLoaded.status === 200 || analyzed.tokens >= 200) { + proxySnapshot.isIntermediate = true; + if (sideLoadSnapshotPermitted) { + yield this.jsdomControl.narrowSnapshot(proxySnapshot, crawlOpts); + } + sideLoaded = proxyLoaded; + fallbackProxyIsUsed = true; + } + } + + if (crawlOpts && (sideLoaded.status === 200 || analyzed.tokens >= 200 || crawlOpts.allocProxy)) { + this.logger.info(`Side load seems to work, applying to crawler.`, { url: urlToCrawl.href }); + crawlOpts.sideLoad ??= sideLoaded.sideLoadOpts; + if (fallbackProxyIsUsed) { + this.logger.info(`Proxy seems to salvage the page`, { url: urlToCrawl.href }); + } + } + } catch (err: any) { + this.logger.warn(`Failed to side load ${urlToCrawl.origin}`, { err: marshalErrorLike(err), href: urlToCrawl.href }); + if (err instanceof ApplicationError && + !(err instanceof ServiceBadAttemptError) && + !(err instanceof DataStreamBrokenError) + ) { + throw err; + } + } + } else if (crawlOpts?.allocProxy && crawlOpts.allocProxy !== 'none' && !crawlOpts.proxyUrl) { + const proxyUrl = await this.proxyProvider.alloc(this.figureOutBestProxyCountry(crawlOpts)); + crawlOpts.proxyUrl = proxyUrl.href; + } + + try { + if (crawlOpts?.targetSelector || crawlOpts?.removeSelector || crawlOpts?.withIframe || crawlOpts?.withShadowDom) { + for await (const x of this.puppeteerControl.scrap(urlToCrawl, crawlOpts)) { + yield this.jsdomControl.narrowSnapshot(x, crawlOpts); + } + + return; + } + + yield* this.puppeteerControl.scrap(urlToCrawl, crawlOpts); + } catch (err: any) { + if (cache && !(err instanceof SecurityCompromiseError)) { + this.logger.warn(`Failed to scrap ${urlToCrawl}, but a stale cache is available. Falling back to cache`, { err: marshalErrorLike(err) }); + yield this.jsdomControl.narrowSnapshot(cache.snapshot, crawlOpts); + return; + } + throw err; + } + } + + assignChargeAmount(formatted: FormattedPage, saasTierPolicy?: Parameters[0]) { + if (!formatted) { + return 0; + } + + let amount = 0; + if (formatted.content) { + amount = estimateToken(formatted.content); + } else if (formatted.description) { + amount += estimateToken(formatted.description); + } + + if (formatted.text) { + amount += estimateToken(formatted.text); + } + + if (formatted.html) { + amount += estimateToken(formatted.html); + } + if (formatted.screenshotUrl || formatted.screenshot) { + // OpenAI image token count for 1024x1024 image + amount += 765; + } + + if (saasTierPolicy) { + amount = this.saasApplyTierPolicy(saasTierPolicy, amount); + } + + Object.assign(formatted, { usage: { tokens: amount } }); + assignMeta(formatted, { usage: { tokens: amount } }); + + return amount; + } + + + async *scrapMany(urls: URL[], options?: ExtraScrappingOptions, crawlerOpts?: CrawlerOptions) { + const iterators = urls.map((url) => this.cachedScrap(url, options, crawlerOpts)); + + const results: (PageSnapshot | undefined)[] = iterators.map((_x) => undefined); + + let nextDeferred = Defer(); + let concluded = false; + + const handler = async (it: AsyncGenerator, idx: number) => { + try { + for await (const x of it) { + results[idx] = x; + + if (x) { + nextDeferred.resolve(); + nextDeferred = Defer(); + } + + } + } catch (err: any) { + this.logger.warn(`Failed to scrap ${urls[idx]}`, { err: marshalErrorLike(err) }); + } + }; + + Promise.allSettled( + iterators.map((it, idx) => handler(it, idx)) + ).finally(() => { + concluded = true; + nextDeferred.resolve(); + }); + + yield results; + + try { + while (!concluded) { + await nextDeferred.promise; + + yield results; + } + yield results; + } finally { + for (const x of iterators) { + x.return(); + } + } + } + + async configure(opts: CrawlerOptions) { + + this.threadLocal.set('withGeneratedAlt', opts.withGeneratedAlt); + this.threadLocal.set('withLinksSummary', opts.withLinksSummary); + this.threadLocal.set('withImagesSummary', opts.withImagesSummary); + this.threadLocal.set('keepImgDataUrl', opts.keepImgDataUrl); + this.threadLocal.set('cacheTolerance', opts.cacheTolerance); + this.threadLocal.set('withIframe', opts.withIframe); + this.threadLocal.set('withShadowDom', opts.withShadowDom); + this.threadLocal.set('userAgent', opts.userAgent); + if (opts.timeout) { + this.threadLocal.set('timeout', opts.timeout * 1000); + } + this.threadLocal.set('retainImages', opts.retainImages); + this.threadLocal.set('noGfm', opts.noGfm); + this.threadLocal.set('DNT', Boolean(opts.doNotTrack)); + if (opts.markdown) { + this.threadLocal.set('turndownOpts', opts.markdown); + } + + const crawlOpts: ExtraScrappingOptions = { + proxyUrl: opts.proxyUrl, + cookies: opts.setCookies, + favorScreenshot: ['screenshot', 'pageshot'].some((x) => opts.respondWith.includes(x)), + removeSelector: opts.removeSelector, + targetSelector: opts.targetSelector, + waitForSelector: opts.waitForSelector, + overrideUserAgent: opts.userAgent, + timeoutMs: opts.timeout ? opts.timeout * 1000 : undefined, + withIframe: opts.withIframe, + withShadowDom: opts.withShadowDom, + locale: opts.locale, + referer: opts.referer, + viewport: opts.viewport, + engine: opts.engine, + allocProxy: opts.proxy?.endsWith('+') ? opts.proxy.slice(0, -1) : opts.proxy, + proxyResources: (opts.proxyUrl || opts.proxy?.endsWith('+')) ? true : false, + private: Boolean(opts.doNotTrack), + }; + + if (crawlOpts.targetSelector?.length) { + if (typeof crawlOpts.targetSelector === 'string') { + crawlOpts.targetSelector = [crawlOpts.targetSelector]; + } + for (const s of crawlOpts.targetSelector) { + for (const e of s.split(',').map((x) => x.trim())) { + if (e.startsWith('*') || e.startsWith(':') || e.includes('*:')) { + throw new ParamValidationError({ + message: `Unacceptable selector: '${e}'. We cannot accept match-all selector for performance reasons. Sorry.`, + path: 'targetSelector' + }); + } + } + } + } + + if (opts._hintIps?.length) { + const hints = await this.geoIpService.lookupCities(opts._hintIps); + const board: Record = {}; + for (const x of hints) { + if (x.country?.code) { + board[x.country.code] = (board[x.country.code] || 0) + 1; + } + } + const hintCountry = _.maxBy(Array.from(Object.entries(board)), 1)?.[0]; + crawlOpts.countryHint = hintCountry?.toLowerCase(); + } + + if (opts.locale) { + crawlOpts.extraHeaders ??= {}; + crawlOpts.extraHeaders['Accept-Language'] = opts.locale; + } + + if (opts.respondWith.includes(CONTENT_FORMAT.VLM)) { + crawlOpts.favorScreenshot = true; + } + + if (opts.injectFrameScript?.length) { + crawlOpts.injectFrameScripts = (await Promise.all( + opts.injectFrameScript.map((x) => { + if (URL.canParse(x)) { + return fetch(x).then((r) => r.text()); + } + + return x; + }) + )).filter(Boolean); + } + + if (opts.injectPageScript?.length) { + crawlOpts.injectPageScripts = (await Promise.all( + opts.injectPageScript.map((x) => { + if (URL.canParse(x)) { + return fetch(x).then((r) => r.text()); + } + + return x; + }) + )).filter(Boolean); + } + + return crawlOpts; + } + + protected async formatSnapshot( + crawlerOptions: CrawlerOptions, + snapshot: PageSnapshot & { + screenshotUrl?: string; + pageshotUrl?: string; + }, + nominalUrl?: URL, + urlValidMs?: number, + scrappingOptions?: ScrappingOptions + ) { + const presumedURL = crawlerOptions.base === 'final' ? new URL(snapshot.href) : nominalUrl; + + const respondWith = crawlerOptions.respondWith; + if (respondWith === CONTENT_FORMAT.READER_LM || respondWith === CONTENT_FORMAT.VLM) { + const output: FormattedPage = { + title: snapshot.title, + content: snapshot.parsed?.textContent, + url: presumedURL?.href || snapshot.href, + }; + + Object.defineProperty(output, 'textRepresentation', { + value: snapshot.parsed?.textContent, + enumerable: false, + }); + + return output; + } + + return this.formatSnapshotWithPDFSideLoad(respondWith, snapshot, presumedURL, urlValidMs, scrappingOptions); + } + + async formatSnapshotWithPDFSideLoad(mode: string, snapshot: PageSnapshot, nominalUrl?: URL, urlValidMs?: number, scrappingOptions?: ScrappingOptions) { + const snapshotCopy = _.cloneDeep(snapshot); + + if (snapshotCopy.pdfs?.length) { + const pdfUrl = snapshotCopy.pdfs[0]; + if (pdfUrl.startsWith('http')) { + const sideLoaded = scrappingOptions?.sideLoad?.impersonate[pdfUrl]; + if (sideLoaded?.status === 200 && sideLoaded.body) { + snapshotCopy.pdfs[0] = pathToFileURL(await sideLoaded?.body.filePath).href; + return this.snapshotFormatter.formatSnapshot(mode, snapshotCopy, nominalUrl, urlValidMs); + } + + const r = await this.curlControl.sideLoad(new URL(pdfUrl), scrappingOptions).catch((err) => { + if (err instanceof ServiceBadAttemptError) { + return Promise.reject(new AssertionFailureError(`Failed to load PDF(${pdfUrl}): ${err.message}`)); + } + + return Promise.reject(err); + }); + if (r.status !== 200) { + throw new AssertionFailureError(`Failed to load PDF(${pdfUrl}): Server responded status ${r.status}`); + } + if (!r.contentType.includes('application/pdf')) { + throw new AssertionFailureError(`Failed to load PDF(${pdfUrl}): Server responded with wrong content type ${r.contentType}`); + } + if (!r.file) { + throw new AssertionFailureError(`Failed to load PDF(${pdfUrl}): Server did not return a body`); + } + snapshotCopy.pdfs[0] = pathToFileURL(await r.file.filePath).href; + } + } + + return this.snapshotFormatter.formatSnapshot(mode, snapshotCopy, nominalUrl, urlValidMs); + } + + async getFinalSnapshot(url: URL, opts?: ExtraScrappingOptions, crawlerOptions?: CrawlerOptions): Promise { + const it = this.cachedScrap(url, opts, crawlerOptions); + + let lastSnapshot; + let lastError; + try { + for await (const x of it) { + lastSnapshot = x; + } + } catch (err) { + lastError = err; + } + + if (!lastSnapshot && lastError) { + throw lastError; + } + + if (!lastSnapshot) { + throw new AssertionFailureError(`No content available`); + } + + return lastSnapshot; + } + + async simpleCrawl(mode: string, url: URL, opts?: ExtraScrappingOptions) { + const it = this.iterSnapshots(url, { ...opts, minIntervalMs: 500 }); + + let lastSnapshot; + let goodEnough = false; + try { + for await (const x of it) { + lastSnapshot = x; + + if (goodEnough) { + break; + } + + if (lastSnapshot?.parsed?.content) { + // After it's good enough, wait for next snapshot; + goodEnough = true; + } + } + + } catch (err) { + if (lastSnapshot) { + return this.snapshotFormatter.formatSnapshot(mode, lastSnapshot, url, this.urlValidMs); + } + + throw err; + } + + if (!lastSnapshot) { + throw new AssertionFailureError(`No content available`); + } + + return this.snapshotFormatter.formatSnapshot(mode, lastSnapshot, url, this.urlValidMs); + } + + getDomainProfileUrlDigest(url: URL) { + const pathname = url.pathname; + const pathVec = pathname.split('/'); + const parentPath = pathVec.slice(0, -1).join('/'); + + const finalPath = parentPath || pathname; + + const key = url.origin.toLocaleLowerCase() + finalPath; + + return { + digest: md5Hasher.hash(key), + path: finalPath, + }; + } + + proxyIterMap = new WeakMap>(); + @retryWith((err) => { + if (err instanceof ServiceBadApproachError) { + return false; + } + if (err instanceof ServiceBadAttemptError) { + // Keep trying + return true; + } + if (err instanceof ApplicationError) { + // Quit with this error + return false; + } + return undefined; + }, 3) + async sideLoadWithAllocatedProxy(url: URL, opts?: ExtraScrappingOptions) { + if (opts?.allocProxy === 'none') { + return this.curlControl.sideLoad(url, opts); + } + let proxy; + if (opts) { + let it = this.proxyIterMap.get(opts); + if (!it) { + it = this.proxyProvider.iterAlloc(this.figureOutBestProxyCountry(opts)); + this.proxyIterMap.set(opts, it); + } + proxy = (await it.next()).value; + } + + proxy ??= await this.proxyProvider.alloc(this.figureOutBestProxyCountry(opts)); + this.logger.debug(`Proxy allocated`, { proxy: proxy.href }); + const r = await this.curlControl.sideLoad(url, { + ...opts, + proxyUrl: proxy.href, + }); + + if (opts && opts.allocProxy) { + opts.proxyUrl ??= proxy.href; + } + + return { ...r, proxy }; + } + + protected figureOutBestProxyCountry(opts?: ExtraScrappingOptions) { + if (!opts) { + return 'auto'; + } + + let draft; + + if (opts.allocProxy) { + if (this.proxyProvider.supports(opts.allocProxy)) { + draft = opts.allocProxy; + } else if (opts.allocProxy === 'none') { + return 'none'; + } + } + + if (opts.countryHint) { + if (this.proxyProvider.supports(opts.countryHint)) { + draft ??= opts.countryHint; + } + } + + draft ??= opts.allocProxy || 'auto'; + + return draft; + } + + knownUrlThatSideLoadingWouldCrashTheBrowser(url: URL) { + if (url.hostname === 'chromewebstore.google.com') { + return true; + } + + return false; + } + + async saasAssertTierPolicy(opts: CrawlerOptions, auth: JinaEmbeddingsAuthDTO) { + let chargeScalar = 1; + let minimalCharge = 0; + + if (opts.withGeneratedAlt) { + await auth.assertTier(0, 'Alt text generation'); + minimalCharge = 765; + } + + if (opts.injectPageScript || opts.injectFrameScript) { + await auth.assertTier(0, 'Script injection'); + minimalCharge = 4_000; + } + + if (opts.withIframe) { + await auth.assertTier(0, 'Iframe'); + } + + if (opts.engine === ENGINE_TYPE.CF_BROWSER_RENDERING) { + await auth.assertTier(0, 'Cloudflare browser rendering'); + minimalCharge = 4_000; + } + + if (opts.respondWith.includes('lm') || opts.engine?.includes('lm')) { + await auth.assertTier(0, 'Language model'); + minimalCharge = 4_000; + chargeScalar = 3; + } + + if (opts.proxy && opts.proxy !== 'none') { + await auth.assertTier(['auto', 'any'].includes(opts.proxy) ? 0 : 2, 'Proxy allocation'); + chargeScalar = 5; + } + + return { + budget: opts.tokenBudget || 0, + chargeScalar, + minimalCharge, + }; + } + + saasApplyTierPolicy(policy: Awaited>, chargeAmount: number) { + const effectiveChargeAmount = policy.chargeScalar * Math.max(chargeAmount, policy.minimalCharge); + if (policy.budget && policy.budget < effectiveChargeAmount) { + throw new BudgetExceededError(`Token budget (${policy.budget}) exceeded, intended charge amount ${effectiveChargeAmount}`); + } + + return effectiveChargeAmount; + } +} diff --git a/src/api/searcher.ts b/src/api/searcher.ts new file mode 100644 index 0000000..95fa4cc --- /dev/null +++ b/src/api/searcher.ts @@ -0,0 +1,909 @@ +import { singleton } from 'tsyringe'; +import { + assignTransferProtocolMeta, RPCHost, RPCReflection, AssertionFailureError, assignMeta, RawString, +} from 'civkit/civ-rpc'; +import { marshalErrorLike } from 'civkit/lang'; +import { objHashMd5B64Of } from 'civkit/hash'; +import _ from 'lodash'; + +import { RateLimitControl, RateLimitDesc, RateLimitTriggeredError } from '../shared/services/rate-limit'; + +import { CrawlerHost, ExtraScrappingOptions } from './crawler'; +import { CrawlerOptions, RESPOND_TIMING } from '../dto/crawler-options'; +import { SnapshotFormatter, FormattedPage as RealFormattedPage } from '../services/snapshot-formatter'; +import { GoogleSearchExplicitOperatorsDto } from '../services/serper-search'; + +import { GlobalLogger } from '../services/logger'; +import { AsyncLocalContext } from '../services/async-context'; +import { Context, Ctx, Method, Param, RPCReflect } from '../services/registry'; +import { OutputServerEventStream } from '../lib/transform-server-event-stream'; +import { JinaEmbeddingsAuthDTO } from '../dto/jina-embeddings-auth'; +import { InsufficientBalanceError } from '../services/errors'; + +import { SerperBingSearchService, SerperGoogleSearchService } from '../services/serp/serper'; +import { toAsyncGenerator } from '../utils/misc'; +import type { JinaEmbeddingsTokenAccount } from '../shared/db/jina-embeddings-token-account'; +import { LRUCache } from 'lru-cache'; +import { API_CALL_STATUS } from '../shared/db/api-roll'; +import { SERPResult } from '../db/searched'; +import { SerperSearchQueryParams, WORLD_COUNTRIES, WORLD_LANGUAGES } from '../shared/3rd-party/serper-search'; +import { InternalJinaSerpService } from '../services/serp/internal'; +import { WebSearchEntry } from '../services/serp/compat'; + +const WORLD_COUNTRY_CODES = Object.keys(WORLD_COUNTRIES).map((x) => x.toLowerCase()); + +interface FormattedPage extends RealFormattedPage { + favicon?: string; + date?: string; +} + +type RateLimitCache = { + blockedUntil?: Date; + user?: JinaEmbeddingsTokenAccount; +}; + +@singleton() +export class SearcherHost extends RPCHost { + logger = this.globalLogger.child({ service: this.constructor.name }); + + cacheRetentionMs = 1000 * 3600 * 24 * 7; + cacheValidMs = 1000 * 3600; + pageCacheToleranceMs = 1000 * 3600 * 24; + + reasonableDelayMs = 15_000; + + targetResultCount = 5; + + highFreqKeyCache = new LRUCache({ + max: 256, + ttl: 60 * 60 * 1000, + updateAgeOnGet: false, + updateAgeOnHas: false, + }); + + batchedCaches: SERPResult[] = []; + + constructor( + protected globalLogger: GlobalLogger, + protected rateLimitControl: RateLimitControl, + protected threadLocal: AsyncLocalContext, + protected crawler: CrawlerHost, + protected snapshotFormatter: SnapshotFormatter, + protected serperGoogle: SerperGoogleSearchService, + protected serperBing: SerperBingSearchService, + protected jinaSerp: InternalJinaSerpService, + ) { + super(...arguments); + + setInterval(() => { + const thisBatch = this.batchedCaches; + this.batchedCaches = []; + if (!thisBatch.length) { + return; + } + const batch = SERPResult.DB.batch(); + + for (const x of thisBatch) { + batch.set(SERPResult.COLLECTION.doc(), x.degradeForFireStore()); + } + batch.commit() + .then(() => { + this.logger.debug(`Saved ${thisBatch.length} caches by batch`); + }) + .catch((err) => { + this.logger.warn(`Failed to cache search result in batch`, { err }); + }); + }, 1000 * 10 + Math.round(1000 * Math.random())).unref(); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + @Method({ + name: 'searchIndex', + ext: { + http: { + action: ['get', 'post'], + path: '/search' + } + }, + tags: ['search'], + returnType: [String, OutputServerEventStream], + }) + @Method({ + ext: { + http: { + action: ['get', 'post'], + path: '::q' + } + }, + tags: ['search'], + returnType: [String, OutputServerEventStream, RawString], + }) + async search( + @RPCReflect() rpcReflect: RPCReflection, + @Ctx() ctx: Context, + auth: JinaEmbeddingsAuthDTO, + crawlerOptions: CrawlerOptions, + searchExplicitOperators: GoogleSearchExplicitOperatorsDto, + @Param('count', { validate: (v: number) => v >= 0 && v <= 20 }) + count: number, + @Param('type', { type: new Set(['web', 'images', 'news']), default: 'web' }) + variant: 'web' | 'images' | 'news', + @Param('provider', { type: new Set(['google', 'bing']), default: 'google' }) + searchEngine: 'google' | 'bing', + @Param('num', { validate: (v: number) => v >= 0 && v <= 20 }) + num?: number, + @Param('gl', { validate: (v: string) => WORLD_COUNTRY_CODES.includes(v?.toLowerCase()) }) gl?: string, + @Param('hl', { validate: (v: string) => WORLD_LANGUAGES.some(l => l.code === v) }) hl?: string, + @Param('location') location?: string, + @Param('page') page?: number, + @Param('fallback', { type: Boolean, default: true }) fallback?: boolean, + @Param('q') q?: string, + ) { + // We want to make our search API follow SERP schema, so we need to expose 'num' parameter. + // Since we used 'count' as 'num' previously, we need to keep 'count' for old users. + // Here we combine 'count' and 'num' to 'count' for the rest of the function. + count = (num !== undefined ? num : count) ?? 10; + + const authToken = auth.bearerToken; + let highFreqKey: RateLimitCache | undefined; + if (authToken && this.highFreqKeyCache.has(authToken)) { + highFreqKey = this.highFreqKeyCache.get(authToken)!; + auth.user = highFreqKey.user; + auth.uid = highFreqKey.user?.user_id; + } + + const uid = await auth.solveUID(); + // Return content by default + const crawlWithoutContent = crawlerOptions.respondWith.includes('no-content'); + const withFavicon = Boolean(ctx.get('X-With-Favicons')); + this.threadLocal.set('collect-favicon', withFavicon); + crawlerOptions.respondTiming ??= RESPOND_TIMING.VISIBLE_CONTENT; + + let chargeAmount = 0; + const noSlashPath = decodeURIComponent(ctx.path).slice(1); + if (!noSlashPath && !q) { + const index = await this.crawler.getIndex(auth); + if (!uid) { + index.note = 'Authentication is required to use this endpoint. Please provide a valid API key via Authorization header.'; + } + if (!ctx.accepts('text/plain') && (ctx.accepts('text/json') || ctx.accepts('application/json'))) { + + return index; + } + + return assignTransferProtocolMeta(`${index}`, + { contentType: 'text/plain', envelope: null } + ); + } + + const user = await auth.assertUser(); + if (!(user.wallet.total_balance > 0)) { + throw new InsufficientBalanceError(`Account balance not enough to run this query, please recharge.`); + } + + if (highFreqKey?.blockedUntil) { + const now = new Date(); + const blockedTimeRemaining = (highFreqKey.blockedUntil.valueOf() - now.valueOf()); + if (blockedTimeRemaining > 0) { + throw RateLimitTriggeredError.from({ + message: `Per UID rate limit exceeded (async)`, + retryAfter: Math.ceil(blockedTimeRemaining / 1000), + }); + } + } + + const rateLimitPolicy = auth.getRateLimits(rpcReflect.name.toUpperCase()) || [ + parseInt(user.metadata?.speed_level) >= 2 ? + RateLimitDesc.from({ + occurrence: 1000, + periodSeconds: 60 + }) : + RateLimitDesc.from({ + occurrence: 100, + periodSeconds: 60 + }) + ]; + + const apiRollPromise = this.rateLimitControl.simpleRPCUidBasedLimit( + rpcReflect, uid!, [rpcReflect.name.toUpperCase()], + ...rateLimitPolicy + ); + + if (!highFreqKey) { + // Normal path + await apiRollPromise; + + if (rateLimitPolicy.some( + (x) => { + const rpm = x.occurrence / (x.periodSeconds / 60); + if (rpm >= 400) { + return true; + } + + return false; + }) + ) { + this.highFreqKeyCache.set(auth.bearerToken!, { + user, + }); + } + + } else { + // High freq key path + apiRollPromise.then( + // Rate limit not triggered, make sure not blocking. + () => { + delete highFreqKey.blockedUntil; + }, + // Rate limit triggered + (err) => { + if (!(err instanceof RateLimitTriggeredError)) { + return; + } + const now = Date.now(); + let tgtDate; + if (err.retryAfterDate) { + tgtDate = err.retryAfterDate; + } else if (err.retryAfter) { + tgtDate = new Date(now + err.retryAfter * 1000); + } + + if (tgtDate) { + const dt = tgtDate.valueOf() - now; + highFreqKey.blockedUntil = tgtDate; + setTimeout(() => { + if (highFreqKey.blockedUntil === tgtDate) { + delete highFreqKey.blockedUntil; + } + }, dt).unref(); + } + } + ).finally(async () => { + // Always asynchronously update user(wallet); + const user = await auth.getBrief().catch(() => undefined); + if (user) { + highFreqKey.user = user; + } + }); + } + + rpcReflect.finally(async () => { + if (chargeAmount) { + auth.reportUsage(chargeAmount, `reader-${rpcReflect.name}`).catch((err) => { + this.logger.warn(`Unable to report usage for ${uid}`, { err: marshalErrorLike(err) }); + }); + try { + const apiRoll = await apiRollPromise; + apiRoll.chargeAmount = chargeAmount; + + } catch (err) { + await this.rateLimitControl.record({ + uid, + tags: [rpcReflect.name.toUpperCase()], + status: API_CALL_STATUS.SUCCESS, + chargeAmount, + }).save().catch((err) => { + this.logger.warn(`Failed to save rate limit record`, { err: marshalErrorLike(err) }); + }); + } + } + }); + + delete crawlerOptions.html; + + const crawlOpts = await this.crawler.configure(crawlerOptions); + const searchQuery = searchExplicitOperators.addTo(q || noSlashPath); + + let fetchNum = count; + if ((page ?? 1) === 1) { + fetchNum = count > 10 ? 30 : 20; + } + + let fallbackQuery: string | undefined; + let chargeAmountScaler = 1; + if (searchEngine === 'bing') { + this.threadLocal.set('bing-preferred', true); + chargeAmountScaler = 3; + } + + if (variant !== 'web') { + chargeAmountScaler = 5; + } + + // Search with fallback logic if enabled + const searchParams = { + variant, + provider: searchEngine, + q: searchQuery, + num: fetchNum, + gl, + hl, + location, + page, + }; + + const { results, query: successQuery, tryTimes } = await this.searchWithFallback( + searchParams, fallback, crawlerOptions.noCache + ); + chargeAmountScaler *= tryTimes; + + fallbackQuery = successQuery !== searchQuery ? successQuery : undefined; + + if (!results.length) { + throw new AssertionFailureError(`No search results available for query ${searchQuery}`); + } + + if (crawlOpts.timeoutMs && crawlOpts.timeoutMs < 30_000) { + delete crawlOpts.timeoutMs; + } + + + let lastScrapped: any[] | undefined; + const targetResultCount = crawlWithoutContent ? count : count + 2; + const trimmedResults: any[] = results.filter((x) => Boolean(x.link)).slice(0, targetResultCount).map((x) => this.mapToFinalResults(x)); + trimmedResults.toString = function () { + let r = this.map((x, i) => x ? Reflect.apply(x.toString, x, [i]) : '').join('\n\n').trimEnd() + '\n'; + if (fallbackQuery) { + r = `Fallback query: ${fallbackQuery}\n\n${r}`; + } + return r; + }; + if (!crawlerOptions.respondWith.includes('no-content') && + ['html', 'text', 'shot', 'markdown', 'content'].some((x) => crawlerOptions.respondWith.includes(x)) + ) { + for (const x of trimmedResults) { + x.content ??= ''; + } + } + const assigningOfGeneralMixins = Promise.allSettled( + trimmedResults.map((x) => this.assignGeneralMixin(x)) + ); + + let it; + + if (crawlWithoutContent || count === 0) { + it = toAsyncGenerator(trimmedResults); + await assigningOfGeneralMixins; + } else { + it = this.fetchSearchResults(crawlerOptions.respondWith, trimmedResults, crawlOpts, + CrawlerOptions.from({ ...crawlerOptions, cacheTolerance: crawlerOptions.cacheTolerance ?? this.pageCacheToleranceMs }), + count, + ); + } + + if (!ctx.accepts('text/plain') && ctx.accepts('text/event-stream')) { + const sseStream = new OutputServerEventStream(); + rpcReflect.return(sseStream); + try { + for await (const scrapped of it) { + if (!scrapped) { + continue; + } + if (rpcReflect.signal.aborted) { + break; + } + + chargeAmount = this.assignChargeAmount(scrapped, count, chargeAmountScaler, fallbackQuery); + lastScrapped = scrapped; + + if (fallbackQuery) { + sseStream.write({ + event: 'meta', + data: { fallback: fallbackQuery }, + }); + } + + sseStream.write({ + event: 'data', + data: scrapped, + }); + } + } catch (err: any) { + this.logger.error(`Failed to collect search result for query ${searchQuery}`, + { err: marshalErrorLike(err) } + ); + sseStream.write({ + event: 'error', + data: marshalErrorLike(err), + }); + } + + sseStream.end(); + + return sseStream; + } + + let earlyReturn = false; + if (!ctx.accepts('text/plain') && (ctx.accepts('text/json') || ctx.accepts('application/json'))) { + let earlyReturnTimer: ReturnType | undefined; + const setEarlyReturnTimer = () => { + if (earlyReturnTimer) { + return; + } + earlyReturnTimer = setTimeout(async () => { + if (!lastScrapped) { + return; + } + await assigningOfGeneralMixins; + chargeAmount = this.assignChargeAmount(lastScrapped, count, chargeAmountScaler, fallbackQuery); + + rpcReflect.return(lastScrapped); + earlyReturn = true; + }, ((crawlerOptions.timeout || 0) * 1000) || this.reasonableDelayMs); + }; + + for await (const scrapped of it) { + lastScrapped = scrapped; + if (rpcReflect.signal.aborted || earlyReturn) { + break; + } + if (_.some(scrapped, (x) => this.pageQualified(x))) { + setEarlyReturnTimer(); + } + if (!this.searchResultsQualified(scrapped, count)) { + continue; + } + if (earlyReturnTimer) { + clearTimeout(earlyReturnTimer); + } + await assigningOfGeneralMixins; + chargeAmount = this.assignChargeAmount(scrapped, count, chargeAmountScaler, fallbackQuery); + + return scrapped; + } + + if (earlyReturnTimer) { + clearTimeout(earlyReturnTimer); + } + + if (!lastScrapped) { + throw new AssertionFailureError(`No content available for query ${searchQuery}`); + } + + if (!earlyReturn) { + await assigningOfGeneralMixins; + chargeAmount = this.assignChargeAmount(lastScrapped, count, chargeAmountScaler, fallbackQuery); + } + + return lastScrapped; + } + + let earlyReturnTimer: ReturnType | undefined; + const setEarlyReturnTimer = () => { + if (earlyReturnTimer) { + return; + } + earlyReturnTimer = setTimeout(async () => { + if (!lastScrapped) { + return; + } + await assigningOfGeneralMixins; + chargeAmount = this.assignChargeAmount(lastScrapped, count, chargeAmountScaler, fallbackQuery); + + rpcReflect.return(assignTransferProtocolMeta(`${lastScrapped}`, { contentType: 'text/plain', envelope: null })); + earlyReturn = true; + }, ((crawlerOptions.timeout || 0) * 1000) || this.reasonableDelayMs); + }; + + for await (const scrapped of it) { + lastScrapped = scrapped; + if (rpcReflect.signal.aborted || earlyReturn) { + break; + } + if (_.some(scrapped, (x) => this.pageQualified(x))) { + setEarlyReturnTimer(); + } + + if (!this.searchResultsQualified(scrapped, count)) { + continue; + } + + if (earlyReturnTimer) { + clearTimeout(earlyReturnTimer); + } + await assigningOfGeneralMixins; + chargeAmount = this.assignChargeAmount(scrapped, count, chargeAmountScaler, fallbackQuery); + + return assignTransferProtocolMeta(`${scrapped}`, { contentType: 'text/plain', envelope: null }); + } + + if (earlyReturnTimer) { + clearTimeout(earlyReturnTimer); + } + + if (!lastScrapped) { + throw new AssertionFailureError(`No content available for query ${searchQuery}`); + } + + if (!earlyReturn) { + await assigningOfGeneralMixins; + chargeAmount = this.assignChargeAmount(lastScrapped, count, chargeAmountScaler, fallbackQuery); + } + + return assignTransferProtocolMeta(`${lastScrapped}`, { contentType: 'text/plain', envelope: null }); + } + + /** + * Search with fallback to progressively shorter queries if no results found + * @param params Search parameters + * @param useFallback Whether to use the fallback mechanism + * @param noCache Whether to bypass cache + * @returns Search response and the successful query + */ + async searchWithFallback( + params: SerperSearchQueryParams & { variant: 'web' | 'images' | 'news'; provider?: string; }, + useFallback: boolean = false, + noCache: boolean = false + ) { + // Try original query first + const originalQuery = params.q; + const containsRTL = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\u0590-\u05FF\uFB1D-\uFB4F\u0700-\u074F\u0780-\u07BF\u07C0-\u07FF]/.test(originalQuery); + + // Extract results based on variant + let tryTimes = 1; + const results = await this.cachedSearch(params.variant, params, noCache); + if (results.length || !useFallback) { + return { results, query: params.q, tryTimes }; + } + + let queryTerms = originalQuery.split(/\s+/); + const lastResort = containsRTL ? queryTerms.slice(queryTerms.length - 2) : queryTerms.slice(0, 2); + + this.logger.info(`No results for "${originalQuery}", trying fallback queries`); + + let terms: string[] = []; + // fallback n times + const n = 4; + + while (tryTimes < n) { + const delta = Math.ceil(queryTerms.length / n) * tryTimes; + terms = containsRTL ? queryTerms.slice(delta) : queryTerms.slice(0, queryTerms.length - delta); + const query = terms.join(' '); + if (!query) { + break; + } + tryTimes += 1; + this.logger.info(`Retrying search with fallback query: "${query}"`); + const fallbackParams = { ...params, q: query }; + const fallbackResults = await this.cachedSearch(params.variant, fallbackParams, noCache); + if (fallbackResults.length > 0) { + return { results: fallbackResults, query: fallbackParams.q, tryTimes }; + } + } + + if (terms.length > lastResort.length) { + const query = lastResort.join(' '); + this.logger.info(`Retrying search with fallback query: "${query}"`); + const fallbackParams = { ...params, q: query }; + tryTimes += 1; + const fallbackResults = await this.cachedSearch(params.variant, fallbackParams, noCache); + + if (fallbackResults.length > 0) { + return { results: fallbackResults, query, tryTimes }; + } + } + + return { results, query: originalQuery, tryTimes }; + } + + async *fetchSearchResults( + mode: string | 'markdown' | 'html' | 'text' | 'screenshot' | 'favicon' | 'content', + searchResults?: FormattedPage[], + options?: ExtraScrappingOptions, + crawlerOptions?: CrawlerOptions, + count?: number, + ) { + if (!searchResults) { + return; + } + const urls = searchResults.map((x) => new URL(x.url!)); + const snapshotMap = new WeakMap(); + for await (const scrapped of this.crawler.scrapMany(urls, options, crawlerOptions)) { + const mapped = scrapped.map((x, i) => { + if (!x) { + return {}; + } + if (snapshotMap.has(x)) { + return snapshotMap.get(x); + } + return this.crawler.formatSnapshotWithPDFSideLoad(mode, x, urls[i], undefined, options).then((r) => { + snapshotMap.set(x, r); + + return r; + }).catch((err) => { + this.logger.error(`Failed to format snapshot for ${urls[i].href}`, { err: marshalErrorLike(err) }); + + return {}; + }); + }); + + const resultArray = await Promise.all(mapped) as FormattedPage[]; + for (const [i, v] of resultArray.entries()) { + if (v) { + Object.assign(searchResults[i], v); + } + } + + yield this.reOrganizeSearchResults(searchResults, count); + } + } + + reOrganizeSearchResults(searchResults: FormattedPage[], count?: number) { + const targetResultCount = count || this.targetResultCount; + const [qualifiedPages, unqualifiedPages] = _.partition(searchResults, (x) => this.pageQualified(x)); + const acceptSet = new Set(qualifiedPages); + + const n = targetResultCount - qualifiedPages.length; + for (const x of unqualifiedPages.slice(0, n >= 0 ? n : 0)) { + acceptSet.add(x); + } + + const filtered = searchResults.filter((x) => acceptSet.has(x)).slice(0, targetResultCount); + + const resultArray = filtered; + + resultArray.toString = searchResults.toString; + + return resultArray; + } + + assignChargeAmount(formatted: FormattedPage[], num: number, scaler: number, fallbackQuery?: string) { + let contentCharge = 0; + for (const x of formatted) { + const itemAmount = this.crawler.assignChargeAmount(x) || 0; + + if (!itemAmount) { + continue; + } + + contentCharge += itemAmount; + } + + const numCharge = Math.ceil(formatted.length / 10) * 10000 * scaler; + + const final = Math.max(contentCharge, numCharge); + + if (final === numCharge) { + for (const x of formatted) { + x.usage = { tokens: Math.ceil(numCharge / formatted.length) }; + } + } + + const metadata: Record = { usage: { tokens: final } }; + if (fallbackQuery) { + metadata.fallback = fallbackQuery; + } + + assignMeta(formatted, metadata); + + return final; + } + + pageQualified(formattedPage: FormattedPage) { + return formattedPage.title && + formattedPage.content || + formattedPage.screenshotUrl || + formattedPage.pageshotUrl || + formattedPage.text || + formattedPage.html; + } + + searchResultsQualified(results: FormattedPage[], targetResultCount = this.targetResultCount) { + return _.every(results, (x) => this.pageQualified(x)) && results.length >= targetResultCount; + } + + async getFavicon(domain: string) { + const url = `https://www.google.com/s2/favicons?sz=32&domain_url=${domain}`; + + try { + const response = await fetch(url); + if (!response.ok) { + return ''; + } + const ab = await response.arrayBuffer(); + const buffer = Buffer.from(ab); + const base64 = buffer.toString('base64'); + return `data:image/png;base64,${base64}`; + } catch (error: any) { + this.logger.warn(`Failed to get favicon base64 string`, { err: marshalErrorLike(error) }); + return ''; + } + } + + *iterProviders(preference?: string, variant?: string) { + if (preference === 'bing') { + yield this.serperBing; + yield variant === 'web' ? this.jinaSerp : this.serperGoogle; + yield this.serperGoogle; + + return; + } + + if (preference === 'google') { + yield variant === 'web' ? this.jinaSerp : this.serperGoogle; + yield this.serperGoogle; + yield this.serperGoogle; + + return; + } + + yield variant === 'web' ? this.jinaSerp : this.serperGoogle; + yield this.serperGoogle; + yield this.serperGoogle; + } + + async cachedSearch(variant: 'web' | 'news' | 'images', query: Record, noCache?: boolean): Promise { + const queryDigest = objHashMd5B64Of({ ...query, variant }); + const provider = query.provider; + Reflect.deleteProperty(query, 'provider'); + let cache; + if (!noCache) { + cache = (await SERPResult.fromFirestoreQuery( + SERPResult.COLLECTION.where('queryDigest', '==', queryDigest) + .orderBy('createdAt', 'desc') + .limit(1) + ))[0]; + if (cache) { + const age = Date.now() - cache.createdAt.valueOf(); + const stale = cache.createdAt.valueOf() < (Date.now() - this.cacheValidMs); + this.logger.info(`${stale ? 'Stale cache exists' : 'Cache hit'} for search query "${query.q}", normalized digest: ${queryDigest}, ${age}ms old`, { + query, digest: queryDigest, age, stale + }); + + if (!stale) { + return cache.response as any; + } + } + } + + try { + let r: any[] | undefined; + let lastError; + outerLoop: + for (const client of this.iterProviders(provider, variant)) { + const t0 = Date.now(); + try { + switch (variant) { + case 'images': { + r = await Reflect.apply(client.imageSearch, client, [query]); + break; + } + case 'news': { + r = await Reflect.apply(client.newsSearch, client, [query]); + break; + } + case 'web': + default: { + r = await Reflect.apply(client.webSearch, client, [query]); + break; + } + } + const dt = Date.now() - t0; + this.logger.info(`Search took ${dt}ms, ${client.constructor.name}(${variant})`, { searchDt: dt, variant, client: client.constructor.name }); + break outerLoop; + } catch (err) { + lastError = err; + const dt = Date.now() - t0; + this.logger.warn(`Failed to do ${variant} search using ${client.constructor.name}`, { err, variant, searchDt: dt, }); + } + } + + if (r?.length) { + const nowDate = new Date(); + const record = SERPResult.from({ + query, + queryDigest, + response: r, + createdAt: nowDate, + expireAt: new Date(nowDate.valueOf() + this.cacheRetentionMs) + }); + + this.batchedCaches.push(record); + } else if (lastError) { + throw lastError; + } + + return r as WebSearchEntry[]; + } catch (err: any) { + if (cache) { + this.logger.warn(`Failed to fetch search result, but a stale cache is available. falling back to stale cache`, { err: marshalErrorLike(err) }); + + return cache.response as any; + } + + throw err; + } + } + + mapToFinalResults(input: WebSearchEntry) { + const whitelistedProps = [ + 'imageUrl', 'imageWidth', 'imageHeight', 'source', 'date', 'siteLinks' + ]; + const result = { + title: input.title, + url: input.link, + description: Reflect.get(input, 'snippet'), + ..._.pick(input, whitelistedProps), + }; + + return result; + } + + async assignGeneralMixin(result: FormattedPage) { + const collectFavicon = this.threadLocal.get('collect-favicon'); + + if (collectFavicon && result.url) { + const url = new URL(result.url); + Reflect.set(result, 'favicon', await this.getFavicon(url.origin)); + } + + Object.setPrototypeOf(result, searchResultProto); + } +} + +const dataItems = [ + { key: 'title', label: 'Title' }, + { key: 'source', label: 'Source' }, + { key: 'url', label: 'URL Source' }, + { key: 'imageUrl', label: 'Image URL' }, + { key: 'description', label: 'Description' }, + { key: 'publishedTime', label: 'Published Time' }, + { key: 'imageWidth', label: 'Image Width' }, + { key: 'imageHeight', label: 'Image Height' }, + { key: 'date', label: 'Date' }, + { key: 'favicon', label: 'Favicon' }, +]; + +const searchResultProto = { + toString(this: FormattedPage, i?: number) { + const chunks = []; + for (const item of dataItems) { + const v = Reflect.get(this, item.key); + if (typeof v !== 'undefined') { + if (i === undefined) { + chunks.push(`[${item.label}]: ${v}`); + } else { + chunks.push(`[${i + 1}] ${item.label}: ${v}`); + } + } + } + + if (this.content) { + chunks.push(`\n${this.content}`); + } + + if (this.images) { + const imageSummaryChunks = [`${i === undefined ? '' : `[${i + 1}] `}Images:`]; + for (const [k, v] of Object.entries(this.images)) { + imageSummaryChunks.push(`- ![${k}](${v})`); + } + if (imageSummaryChunks.length === 1) { + imageSummaryChunks.push('This page does not seem to contain any images.'); + } + chunks.push(imageSummaryChunks.join('\n')); + } + if (this.links) { + const linkSummaryChunks = [`${i === undefined ? '' : `[${i + 1}] `}Links/Buttons:`]; + if (Array.isArray(this.links)) { + for (const [k, v] of this.links) { + linkSummaryChunks.push(`- [${k}](${v})`); + } + } else { + for (const [k, v] of Object.entries(this.links)) { + linkSummaryChunks.push(`- [${k}](${v})`); + } + } + if (linkSummaryChunks.length === 1) { + linkSummaryChunks.push('This page does not seem to contain any buttons/links.'); + } + chunks.push(linkSummaryChunks.join('\n')); + } + + return chunks.join('\n'); + } +}; diff --git a/src/api/serp.ts b/src/api/serp.ts new file mode 100644 index 0000000..914f36c --- /dev/null +++ b/src/api/serp.ts @@ -0,0 +1,569 @@ +import { singleton } from 'tsyringe'; +import { + RPCHost, RPCReflection, assignMeta, RawString, + ParamValidationError, + assignTransferProtocolMeta, +} from 'civkit/civ-rpc'; +import { marshalErrorLike } from 'civkit/lang'; +import _ from 'lodash'; + +import { RateLimitControl, RateLimitDesc, RateLimitTriggeredError } from '../shared/services/rate-limit'; + +import { GlobalLogger } from '../services/logger'; +import { AsyncLocalContext } from '../services/async-context'; +import { Context, Ctx, Method, Param, RPCReflect } from '../services/registry'; +import { OutputServerEventStream } from '../lib/transform-server-event-stream'; +import { JinaEmbeddingsAuthDTO } from '../dto/jina-embeddings-auth'; +import { InsufficientBalanceError } from '../services/errors'; +import { WORLD_COUNTRIES, WORLD_LANGUAGES } from '../shared/3rd-party/serper-search'; +import { GoogleSERP } from '../services/serp/google'; +import { WebSearchEntry } from '../services/serp/compat'; +import { CrawlerOptions } from '../dto/crawler-options'; +import { ScrappingOptions } from '../services/serp/puppeteer'; +import { objHashMd5B64Of } from 'civkit/hash'; +import { SERPResult } from '../db/searched'; +import { SerperBingSearchService, SerperGoogleSearchService } from '../services/serp/serper'; +import type { JinaEmbeddingsTokenAccount } from '../shared/db/jina-embeddings-token-account'; +import { LRUCache } from 'lru-cache'; +import { API_CALL_STATUS } from '../shared/db/api-roll'; +import { InternalJinaSerpService } from '../services/serp/internal'; + +const WORLD_COUNTRY_CODES = Object.keys(WORLD_COUNTRIES).map((x) => x.toLowerCase()); + +type RateLimitCache = { + blockedUntil?: Date; + user?: JinaEmbeddingsTokenAccount; +}; + +const indexProto = { + toString: function (): string { + return _(this) + .toPairs() + .map(([k, v]) => k ? `[${_.upperFirst(_.lowerCase(k))}] ${v}` : '') + .value() + .join('\n') + '\n'; + } +}; + +@singleton() +export class SerpHost extends RPCHost { + logger = this.globalLogger.child({ service: this.constructor.name }); + + cacheRetentionMs = 1000 * 3600 * 24 * 7; + cacheValidMs = 1000 * 3600; + pageCacheToleranceMs = 1000 * 3600 * 24; + + reasonableDelayMs = 15_000; + + targetResultCount = 5; + + highFreqKeyCache = new LRUCache({ + max: 256, + ttl: 60 * 60 * 1000, + updateAgeOnGet: false, + updateAgeOnHas: false, + }); + + batchedCaches: SERPResult[] = []; + + async getIndex(ctx: Context, auth?: JinaEmbeddingsAuthDTO) { + const indexObject: Record = Object.create(indexProto); + Object.assign(indexObject, { + usage1: 'https://r.jina.ai/YOUR_URL', + usage2: 'https://s.jina.ai/YOUR_SEARCH_QUERY', + usage3: `${ctx.origin}/?q=YOUR_SEARCH_QUERY`, + homepage: 'https://jina.ai/reader', + }); + + if (auth && auth.user) { + indexObject[''] = undefined; + indexObject.authenticatedAs = `${auth.user.user_id} (${auth.user.full_name})`; + indexObject.balanceLeft = auth.user.wallet.total_balance; + } else { + indexObject.note = 'Authentication is required to use this endpoint. Please provide a valid API key via Authorization header.'; + } + + return indexObject; + } + + constructor( + protected globalLogger: GlobalLogger, + protected rateLimitControl: RateLimitControl, + protected threadLocal: AsyncLocalContext, + protected googleSerp: GoogleSERP, + protected serperGoogle: SerperGoogleSearchService, + protected serperBing: SerperBingSearchService, + protected jinaSerp: InternalJinaSerpService, + ) { + super(...arguments); + + setInterval(() => { + const thisBatch = this.batchedCaches; + this.batchedCaches = []; + if (!thisBatch.length) { + return; + } + const batch = SERPResult.DB.batch(); + + for (const x of thisBatch) { + batch.set(SERPResult.COLLECTION.doc(), x.degradeForFireStore()); + } + batch.commit() + .then(() => { + this.logger.debug(`Saved ${thisBatch.length} caches by batch`); + }) + .catch((err) => { + this.logger.warn(`Failed to cache search result in batch`, { err }); + }); + }, 1000 * 10 + Math.round(1000 * Math.random())).unref(); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + @Method({ + name: 'searchIndex', + ext: { + http: { + action: ['get', 'post'], + path: '/' + } + }, + tags: ['search'], + returnType: [String, OutputServerEventStream, RawString], + }) + @Method({ + ext: { + http: { + action: ['get', 'post'], + } + }, + tags: ['search'], + returnType: [String, OutputServerEventStream, RawString], + }) + async search( + @RPCReflect() rpcReflect: RPCReflection, + @Ctx() ctx: Context, + crawlerOptions: CrawlerOptions, + auth: JinaEmbeddingsAuthDTO, + @Param('type', { type: new Set(['web', 'images', 'news']), default: 'web' }) + variant: 'web' | 'images' | 'news', + @Param('q') q?: string, + @Param('provider', { type: new Set(['google', 'bing']) }) + searchEngine?: 'google' | 'bing', + @Param('num', { validate: (v: number) => v >= 0 && v <= 20 }) + num?: number, + @Param('gl', { validate: (v: string) => WORLD_COUNTRY_CODES.includes(v?.toLowerCase()) }) gl?: string, + @Param('hl', { validate: (v: string) => WORLD_LANGUAGES.some(l => l.code === v) }) _hl?: string, + @Param('location') location?: string, + @Param('page') page?: number, + @Param('fallback') fallback?: boolean, + ) { + const authToken = auth.bearerToken; + let highFreqKey: RateLimitCache | undefined; + if (authToken && this.highFreqKeyCache.has(authToken)) { + highFreqKey = this.highFreqKeyCache.get(authToken)!; + auth.user = highFreqKey.user; + auth.uid = highFreqKey.user?.user_id; + } + + const uid = await auth.solveUID(); + if (!q) { + if (ctx.path === '/') { + const indexObject = await this.getIndex(ctx, auth); + if (!ctx.accepts('text/plain') && (ctx.accepts('text/json') || ctx.accepts('application/json'))) { + return indexObject; + } + + return assignTransferProtocolMeta(`${indexObject}`, + { contentType: 'text/plain; charset=utf-8', envelope: null } + ); + } + throw new ParamValidationError({ + path: 'q', + message: `Required but not provided` + }); + } + // Return content by default + const user = await auth.assertUser(); + if (!(user.wallet.total_balance > 0)) { + throw new InsufficientBalanceError(`Account balance not enough to run this query, please recharge.`); + } + + if (highFreqKey?.blockedUntil) { + const now = new Date(); + const blockedTimeRemaining = (highFreqKey.blockedUntil.valueOf() - now.valueOf()); + if (blockedTimeRemaining > 0) { + this.logger.warn(`Rate limit triggered for ${uid}, this request should have been blocked`); + // throw RateLimitTriggeredError.from({ + // message: `Per UID rate limit exceeded (async)`, + // retryAfter: Math.ceil(blockedTimeRemaining / 1000), + // }); + } + } + + const PREMIUM_KEY_LIMIT = 400; + const rateLimitPolicy = auth.getRateLimits('SEARCH') || [ + parseInt(user.metadata?.speed_level) >= 2 ? + RateLimitDesc.from({ + occurrence: PREMIUM_KEY_LIMIT, + periodSeconds: 60 + }) : + RateLimitDesc.from({ + occurrence: 40, + periodSeconds: 60 + }) + ]; + + const apiRollPromise = this.rateLimitControl.simpleRPCUidBasedLimit( + rpcReflect, uid!, ['SEARCH'], + ...rateLimitPolicy + ); + + if (!highFreqKey) { + // Normal path + await apiRollPromise; + + if (rateLimitPolicy.some( + (x) => { + const rpm = x.occurrence / (x.periodSeconds / 60); + if (rpm >= PREMIUM_KEY_LIMIT) { + return true; + } + + return false; + }) + ) { + this.highFreqKeyCache.set(auth.bearerToken!, { + user, + }); + } + } else { + // High freq key path + apiRollPromise.then( + // Rate limit not triggered, make sure not blocking. + () => { + delete highFreqKey.blockedUntil; + }, + // Rate limit triggered + (err) => { + if (!(err instanceof RateLimitTriggeredError)) { + return; + } + const now = Date.now(); + let tgtDate; + if (err.retryAfterDate) { + tgtDate = err.retryAfterDate; + } else if (err.retryAfter) { + tgtDate = new Date(now + err.retryAfter * 1000); + } + + if (tgtDate) { + const dt = tgtDate.valueOf() - now; + highFreqKey.blockedUntil = tgtDate; + setTimeout(() => { + if (highFreqKey.blockedUntil === tgtDate) { + delete highFreqKey.blockedUntil; + } + }, dt).unref(); + } + } + ).finally(async () => { + // Always asynchronously update user(wallet); + const user = await auth.getBrief().catch(() => undefined); + if (user) { + highFreqKey.user = user; + } + }); + } + + let chargeAmount = 0; + rpcReflect.finally(async () => { + if (chargeAmount) { + auth.reportUsage(chargeAmount, `reader-search`).catch((err) => { + this.logger.warn(`Unable to report usage for ${uid}`, { err: marshalErrorLike(err) }); + }); + try { + const apiRoll = await apiRollPromise; + apiRoll.chargeAmount = chargeAmount; + } catch (err) { + await this.rateLimitControl.record({ + uid, + tags: [rpcReflect.name.toUpperCase()], + status: API_CALL_STATUS.SUCCESS, + chargeAmount, + }).save().catch((err) => { + this.logger.warn(`Failed to save rate limit record`, { err: marshalErrorLike(err) }); + }); + } + } + }); + + let chargeAmountScaler = 1; + if (searchEngine === 'bing') { + chargeAmountScaler = 3; + } + if (variant !== 'web') { + chargeAmountScaler = 5; + } + + let realQuery = q; + let queryTerms = q.split(/\s+/g).filter((x) => !!x); + + let results = await this.cachedSearch(variant, { + provider: searchEngine, + q, + num, + gl, + // hl, + location, + page, + }, crawlerOptions); + + + if (fallback && !results?.length && (!page || page === 1)) { + let tryTimes = 1; + const containsRTL = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\u0590-\u05FF\uFB1D-\uFB4F\u0700-\u074F\u0780-\u07BF\u07C0-\u07FF]/.test(q); + const lastResort = (containsRTL ? queryTerms.slice(queryTerms.length - 2) : queryTerms.slice(0, 2)).join(' '); + const n = 4; + let terms: string[] = []; + while (tryTimes < n) { + const delta = Math.ceil(queryTerms.length / n) * tryTimes; + terms = containsRTL ? queryTerms.slice(delta) : queryTerms.slice(0, queryTerms.length - delta); + const query = terms.join(' '); + if (!query) { + break; + } + if (realQuery === query) { + continue; + } + tryTimes += 1; + realQuery = query; + this.logger.info(`Retrying search with fallback query: "${realQuery}"`); + results = await this.cachedSearch(variant, { + provider: searchEngine, + q: realQuery, + num, + gl, + // hl, + location, + }, crawlerOptions); + if (results?.length) { + break; + } + } + + if (!results?.length && realQuery.length > lastResort.length) { + realQuery = lastResort; + this.logger.info(`Retrying search with fallback query: "${realQuery}"`); + tryTimes += 1; + results = await this.cachedSearch(variant, { + provider: searchEngine, + q: realQuery, + num, + gl, + // hl, + location, + }, crawlerOptions); + } + + chargeAmountScaler *= tryTimes; + } + + if (!results?.length) { + results = []; + } + + const finalResults = results.map((x: any) => this.mapToFinalResults(x)); + + await Promise.all(finalResults.map((x: any) => this.assignGeneralMixin(x))); + + chargeAmount = this.assignChargeAmount(finalResults, chargeAmountScaler); + assignMeta(finalResults, { + query: realQuery, + fallback: realQuery === q ? undefined : realQuery, + }); + + return finalResults; + } + + + assignChargeAmount(items: unknown[], scaler: number) { + const numCharge = Math.ceil(items.length / 10) * 10000 * scaler; + assignMeta(items, { usage: { tokens: numCharge } }); + + return numCharge; + } + + async getFavicon(domain: string) { + const url = `https://www.google.com/s2/favicons?sz=32&domain_url=${domain}`; + + try { + const response = await fetch(url); + if (!response.ok) { + return ''; + } + const ab = await response.arrayBuffer(); + const buffer = Buffer.from(ab); + const base64 = buffer.toString('base64'); + return `data:image/png;base64,${base64}`; + } catch (error: any) { + this.logger.warn(`Failed to get favicon base64 string`, { err: marshalErrorLike(error) }); + return ''; + } + } + + async configure(opts: CrawlerOptions) { + const crawlOpts: ScrappingOptions = { + proxyUrl: opts.proxyUrl, + cookies: opts.setCookies, + overrideUserAgent: opts.userAgent, + timeoutMs: opts.timeout ? opts.timeout * 1000 : undefined, + locale: opts.locale, + referer: opts.referer, + viewport: opts.viewport, + proxyResources: (opts.proxyUrl || opts.proxy?.endsWith('+')) ? true : false, + allocProxy: opts.proxy?.endsWith('+') ? opts.proxy.slice(0, -1) : opts.proxy, + }; + + if (opts.locale) { + crawlOpts.extraHeaders ??= {}; + crawlOpts.extraHeaders['Accept-Language'] = opts.locale; + } + + return crawlOpts; + } + + mapToFinalResults(input: WebSearchEntry) { + const whitelistedProps = [ + 'imageUrl', 'imageWidth', 'imageHeight', 'source', 'date', 'siteLinks' + ]; + const result = { + title: input.title, + url: input.link, + description: Reflect.get(input, 'snippet'), + ..._.pick(input, whitelistedProps), + }; + + return result; + } + + *iterProviders(preference?: string, variant?: string) { + if (preference === 'bing') { + yield this.serperBing; + yield this.serperGoogle; + yield this.googleSerp; + + return; + } + + if (preference === 'google') { + yield this.googleSerp; + yield this.googleSerp; + yield this.serperGoogle; + + return; + } + + // yield variant === 'web' ? this.jinaSerp : this.serperGoogle; + yield this.serperGoogle + yield this.serperGoogle; + yield this.googleSerp; + } + + async cachedSearch(variant: 'web' | 'news' | 'images', query: Record, opts: CrawlerOptions) { + const queryDigest = objHashMd5B64Of({ ...query, variant }); + const provider = query.provider; + Reflect.deleteProperty(query, 'provider'); + const noCache = opts.noCache; + let cache; + if (!noCache) { + cache = (await SERPResult.fromFirestoreQuery( + SERPResult.COLLECTION.where('queryDigest', '==', queryDigest) + .orderBy('createdAt', 'desc') + .limit(1) + ))[0]; + if (cache) { + const age = Date.now() - cache.createdAt.valueOf(); + const stale = cache.createdAt.valueOf() < (Date.now() - this.cacheValidMs); + this.logger.info(`${stale ? 'Stale cache exists' : 'Cache hit'} for search query "${query.q}", normalized digest: ${queryDigest}, ${age}ms old`, { + query, digest: queryDigest, age, stale + }); + + if (!stale) { + return cache.response as any; + } + } + } + const scrappingOptions = await this.configure(opts); + + try { + let r: any[] | undefined; + let lastError; + outerLoop: + for (const client of this.iterProviders(provider, variant)) { + const t0 = Date.now(); + try { + switch (variant) { + case 'images': { + r = await Reflect.apply(client.imageSearch, client, [query, scrappingOptions]); + break; + } + case 'news': { + r = await Reflect.apply(client.newsSearch, client, [query, scrappingOptions]); + break; + } + case 'web': + default: { + r = await Reflect.apply(client.webSearch, client, [query, scrappingOptions]); + break; + } + } + const dt = Date.now() - t0; + this.logger.info(`Search took ${dt}ms, ${client.constructor.name}(${variant})`, { searchDt: dt, variant, client: client.constructor.name }); + break outerLoop; + } catch (err) { + lastError = err; + const dt = Date.now() - t0; + this.logger.warn(`Failed to do ${variant} search using ${client.constructor.name}`, { err, variant, searchDt: dt, }); + } + } + + if (r?.length) { + const nowDate = new Date(); + const record = SERPResult.from({ + query, + queryDigest, + response: r, + createdAt: nowDate, + expireAt: new Date(nowDate.valueOf() + this.cacheRetentionMs) + }); + this.batchedCaches.push(record); + } else if (lastError) { + throw lastError; + } + + return r; + } catch (err: any) { + if (cache) { + this.logger.warn(`Failed to fetch search result, but a stale cache is available. falling back to stale cache`, { err: marshalErrorLike(err) }); + + return cache.response as any; + } + + throw err; + } + } + + async assignGeneralMixin(result: Partial) { + const collectFavicon = this.threadLocal.get('collect-favicon'); + + if (collectFavicon && result.link) { + const url = new URL(result.link); + Reflect.set(result, 'favicon', await this.getFavicon(url.origin)); + } + } +} diff --git a/src/cloud-functions/adaptive-crawler.ts b/src/cloud-functions/adaptive-crawler.ts new file mode 100644 index 0000000..971a0fc --- /dev/null +++ b/src/cloud-functions/adaptive-crawler.ts @@ -0,0 +1,598 @@ +import { + AssertionFailureError, + assignTransferProtocolMeta, + HashManager, + ParamValidationError, + RPCHost, RPCReflection, +} from 'civkit'; +import { singleton } from 'tsyringe'; +import { CloudHTTPv2, CloudTaskV2, Ctx, FirebaseStorageBucketControl, Logger, Param, RPCReflect } from '../shared'; +import _ from 'lodash'; +import { Request, Response } from 'express'; +import { JinaEmbeddingsAuthDTO } from '../shared/dto/jina-embeddings-auth'; +import robotsParser from 'robots-parser'; +import { DOMParser } from '@xmldom/xmldom'; + +import { AdaptiveCrawlerOptions } from '../dto/adaptive-crawler-options'; +import { CrawlerOptions } from '../dto/crawler-options'; +import { JinaEmbeddingsTokenAccount } from '../shared/db/jina-embeddings-token-account'; +import { AdaptiveCrawlTask, AdaptiveCrawlTaskStatus } from '../db/adaptive-crawl-task'; +import { getFunctions } from 'firebase-admin/functions'; +import { getFunctionUrl } from '../utils/get-function-url'; +import { Timestamp } from 'firebase-admin/firestore'; + +const md5Hasher = new HashManager('md5', 'hex'); +const removeURLHash = (url: string) => { + try { + const o = new URL(url); + o.hash = ''; + return o.toString(); + } catch (e) { + return url; + } +} + +@singleton() +export class AdaptiveCrawlerHost extends RPCHost { + logger = this.globalLogger.child({ service: this.constructor.name }); + // Actual cache storage (gcp buckets) exists for 7 days, so here we need to select a time < 7 days. + cacheExpiry = 3 * 1000 * 60 * 60 * 24; + + static readonly __singleCrawlQueueName = 'singleCrawlQueue'; + + constructor( + protected globalLogger: Logger, + protected firebaseObjectStorage: FirebaseStorageBucketControl, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + @CloudHTTPv2({ + runtime: { + memory: '1GiB', + timeoutSeconds: 300, + concurrency: 22, + }, + tags: ['Crawler'], + httpMethod: ['post', 'get'], + returnType: [String], + }) + async adaptiveCrawl( + @RPCReflect() rpcReflect: RPCReflection, + @Ctx() ctx: { + req: Request, + res: Response, + }, + auth: JinaEmbeddingsAuthDTO, + crawlerOptions: CrawlerOptions, + adaptiveCrawlerOptions: AdaptiveCrawlerOptions, + ) { + this.logger.debug({ + adaptiveCrawlerOptions, + crawlerOptions, + }); + + + const uid = await auth.solveUID(); + const { useSitemap, maxPages } = adaptiveCrawlerOptions; + + let tmpUrl = ctx.req.url.slice(1)?.trim(); + if (!tmpUrl) { + tmpUrl = crawlerOptions.url?.trim() ?? ''; + } + const targetUrl = new URL(tmpUrl); + + if (!targetUrl) { + const latestUser = uid ? await auth.assertUser() : undefined; + if (!ctx.req.accepts('text/plain') && (ctx.req.accepts('text/json') || ctx.req.accepts('application/json'))) { + return this.getIndex(latestUser); + } + + return assignTransferProtocolMeta(`${this.getIndex(latestUser)}`, + { contentType: 'text/plain', envelope: null } + ); + } + + const meta = { + targetUrl: targetUrl.toString(), + useSitemap, + maxPages, + }; + + const digest = md5Hasher.hash(JSON.stringify(meta)); + const shortDigest = Buffer.from(digest, 'hex').toString('base64url'); + const existing = await AdaptiveCrawlTask.fromFirestore(shortDigest); + + if (existing?.createdAt) { + if (existing.createdAt.getTime() > Date.now() - this.cacheExpiry) { + this.logger.info(`Cache hit for ${shortDigest}, created at ${existing.createdAt.toDateString()}`); + return { taskId: shortDigest }; + } else { + this.logger.info(`Cache expired for ${shortDigest}, created at ${existing.createdAt.toDateString()}`); + } + } + + await AdaptiveCrawlTask.COLLECTION.doc(shortDigest).set({ + _id: shortDigest, + status: AdaptiveCrawlTaskStatus.PENDING, + statusText: 'Pending', + meta, + createdAt: new Date(), + urls: [], + processed: {}, + failed: {}, + }); + + let urls: string[] = []; + if (useSitemap) { + urls = await this.crawlUrlsFromSitemap(targetUrl, maxPages); + } + + if (urls.length > 0) { + await AdaptiveCrawlTask.COLLECTION.doc(shortDigest).update({ + status: AdaptiveCrawlTaskStatus.PROCESSING, + statusText: `Processing 0/${urls.length}`, + urls, + }); + + const promises = []; + for (const url of urls) { + promises.push(getFunctions().taskQueue(AdaptiveCrawlerHost.__singleCrawlQueueName).enqueue({ + shortDigest, url, token: auth.bearerToken, meta + }, { + dispatchDeadlineSeconds: 1800, + uri: await getFunctionUrl(AdaptiveCrawlerHost.__singleCrawlQueueName), + })); + }; + + await Promise.all(promises); + } else { + meta.useSitemap = false; + + await AdaptiveCrawlTask.COLLECTION.doc(shortDigest).update({ + urls: [targetUrl.toString()], + }); + + await getFunctions().taskQueue(AdaptiveCrawlerHost.__singleCrawlQueueName).enqueue({ + shortDigest, url: targetUrl.toString(), token: auth.bearerToken, meta + }, { + dispatchDeadlineSeconds: 1800, + uri: await getFunctionUrl(AdaptiveCrawlerHost.__singleCrawlQueueName), + }) + } + + return { taskId: shortDigest }; + } + + @CloudHTTPv2({ + runtime: { + memory: '1GiB', + timeoutSeconds: 300, + concurrency: 22, + }, + tags: ['Crawler'], + httpMethod: ['post', 'get'], + returnType: AdaptiveCrawlTask, + }) + async adaptiveCrawlStatus( + @RPCReflect() rpcReflect: RPCReflection, + @Ctx() ctx: { + req: Request, + res: Response, + }, + auth: JinaEmbeddingsAuthDTO, + @Param('taskId') taskId: string, + @Param('urls') urls: string[] = [], + ) { + if (!taskId) { + throw new ParamValidationError('taskId is required'); + } + + const state = await AdaptiveCrawlTask.fromFirestore(taskId); + + if (!state) { + throw new AssertionFailureError('The task does not exist'); + } + + if (state?.createdAt && state.createdAt.getTime() < Date.now() - this.cacheExpiry) { + throw new AssertionFailureError('The task has expired'); + } + + if (urls.length) { + const promises = Object.entries(state?.processed ?? {}).map(async ([url, cachePath]) => { + if (urls.includes(url)) { + const raw = await this.firebaseObjectStorage.downloadFile(cachePath); + state!.processed[url] = JSON.parse(raw.toString('utf-8')); + } + }); + + await Promise.all(promises); + } + + + return state; + } + + @CloudTaskV2({ + name: AdaptiveCrawlerHost.__singleCrawlQueueName, + runtime: { + cpu: 1, + memory: '1GiB', + timeoutSeconds: 3600, + concurrency: 2, + maxInstances: 200, + retryConfig: { + maxAttempts: 3, + minBackoffSeconds: 60, + }, + rateLimits: { + maxConcurrentDispatches: 150, + maxDispatchesPerSecond: 5, + }, + } + }) + async singleCrawlQueue( + @Param('shortDigest') shortDigest: string, + @Param('url') url: string, + @Param('token') token: string, + @Param('meta') meta: AdaptiveCrawlTask['meta'], + ) { + const error = { + reason: '' + }; + + const state = await AdaptiveCrawlTask.fromFirestore(shortDigest); + if (state?.status === AdaptiveCrawlTaskStatus.COMPLETED) { + return; + } + + try { + url = removeURLHash(url); + } catch(e) { + error.reason = `Failed to parse url: ${url}`; + } + + this.logger.debug(shortDigest, url, meta); + const cachePath = `adaptive-crawl-task/${shortDigest}/${md5Hasher.hash(url)}`; + + if (!error.reason) { + const result = meta.useSitemap + ? await this.handleSingleCrawl(shortDigest, url, token, cachePath) + : await this.handleSingleCrawlRecursively(shortDigest, url, token, meta, cachePath); + + if (!result) { + return; + } + + error.reason = result.error.reason; + } + + await AdaptiveCrawlTask.DB.runTransaction(async (transaction) => { + const ref = AdaptiveCrawlTask.COLLECTION.doc(shortDigest); + const state = await transaction.get(ref); + const data = state.data() as AdaptiveCrawlTask & { createdAt: Timestamp }; + + if (error.reason) { + data.failed[url] = error; + } else { + data.processed[url] = cachePath; + } + + const status = Object.keys(data.processed).length + Object.keys(data.failed).length >= data.urls.length + ? AdaptiveCrawlTaskStatus.COMPLETED : AdaptiveCrawlTaskStatus.PROCESSING; + const statusText = Object.keys(data.processed).length + Object.keys(data.failed).length >= data.urls.length + ? `Completed ${Object.keys(data.processed).length} Succeeded, ${Object.keys(data.failed).length} Failed` + : `Processing ${Object.keys(data.processed).length + Object.keys(data.failed).length}/${data.urls.length}`; + + const payload: Partial = { + status, + statusText, + processed: data.processed, + failed: data.failed, + }; + + if (status === AdaptiveCrawlTaskStatus.COMPLETED) { + payload.finishedAt = new Date(); + payload.duration = new Date().getTime() - data.createdAt.toDate().getTime(); + } + + transaction.update(ref, payload); + }); + } + + async handleSingleCrawl(shortDigest: string, url: string, token: string, cachePath: string) { + const error = { + reason: '' + } + + const response = await fetch('https://r.jina.ai', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/json', + }, + body: JSON.stringify({ url }) + }) + + if (!response.ok) { + error.reason = `Failed to crawl ${url}, ${response.statusText}`; + } else { + const json = await response.json(); + + await this.firebaseObjectStorage.saveFile(cachePath, + Buffer.from( + JSON.stringify(json), + 'utf-8' + ), + { + metadata: { + contentType: 'application/json', + } + } + ) + } + + return { + error, + } + } + + async handleSingleCrawlRecursively( + shortDigest: string, url: string, token: string, meta: AdaptiveCrawlTask['meta'], cachePath: string + ) { + const error = { + reason: '' + } + const response = await fetch('https://r.jina.ai', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/json', + 'X-With-Links-Summary': 'true', + }, + body: JSON.stringify({ url }) + }); + + if (!response.ok) { + error.reason = `Failed to crawl ${url}, ${response.statusText}`; + } else { + const json = await response.json(); + await this.firebaseObjectStorage.saveFile(cachePath, + Buffer.from( + JSON.stringify(json), + 'utf-8' + ), + { + metadata: { + contentType: 'application/json', + } + } + ) + + const title = json.data.title; + const description = json.data.description; + const links = json.data.links as Record; + + const relevantUrls = await this.getRelevantUrls(token, { title, description, links }); + this.logger.debug(`Total urls: ${Object.keys(links).length}, relevant urls: ${relevantUrls.length}`); + + for (const url of relevantUrls) { + let abortContinue = false; + let abortBreak = false; + await AdaptiveCrawlTask.DB.runTransaction(async (transaction) => { + const ref = AdaptiveCrawlTask.COLLECTION.doc(shortDigest); + const state = await transaction.get(ref); + const data = state.data() as AdaptiveCrawlTask & { createdAt: Timestamp }; + + if (data.urls.includes(url)) { + this.logger.debug('Recursive CONTINUE', data); + abortContinue = true; + return; + } + + const urls = [ + ...data.urls, + url + ]; + + if (urls.length > meta.maxPages || data.status === AdaptiveCrawlTaskStatus.COMPLETED) { + this.logger.debug('Recursive BREAK', data); + abortBreak = true; + return; + } + + transaction.update(ref, { urls }); + }); + + if (abortContinue) { + continue; + } + if (abortBreak) { + break; + } + + await getFunctions().taskQueue(AdaptiveCrawlerHost.__singleCrawlQueueName).enqueue({ + shortDigest, url, token, meta + }, { + dispatchDeadlineSeconds: 1800, + uri: await getFunctionUrl(AdaptiveCrawlerHost.__singleCrawlQueueName), + }); + }; + } + + return { + error, + } + } + + async getRelevantUrls(token: string, { + title, description, links + }: { + title: string; + description: string; + links: Record; + }) { + const invalidSuffix = [ + '.zip', + '.docx', + '.pptx', + '.xlsx', + ]; + + const validLinks = Object.entries(links) + .map(([title, link]) => link) + .filter(link => link.startsWith('http') && !invalidSuffix.some(suffix => link.endsWith(suffix))); + + let query = ''; + if (!description) { + query += title; + } else { + query += `TITLE: ${title}; DESCRIPTION: ${description}`; + } + + const data = { + model: 'jina-reranker-v2-base-multilingual', + query, + top_n: 15, + documents: validLinks, + }; + + const response = await fetch('https://api.jina.ai/v1/rerank', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(data) + }); + + const json = (await response.json()) as { + results: { + index: number; + document: { + text: string; + }; + relevance_score: number; + }[]; + }; + + const highestRelevanceScore = json.results[0]?.relevance_score ?? 0; + return json.results.filter(r => r.relevance_score > Math.max(highestRelevanceScore * 0.6, 0.1)).map(r => removeURLHash(r.document.text)); + } + + getIndex(user?: JinaEmbeddingsTokenAccount) { + // TODO: 需要更新使用方式 + // const indexObject: Record = Object.create(indexProto); + + // Object.assign(indexObject, { + // usage1: 'https://r.jina.ai/YOUR_URL', + // usage2: 'https://s.jina.ai/YOUR_SEARCH_QUERY', + // homepage: 'https://jina.ai/reader', + // sourceCode: 'https://github.com/jina-ai/reader', + // }); + + // if (user) { + // indexObject[''] = undefined; + // indexObject.authenticatedAs = `${user.user_id} (${user.full_name})`; + // indexObject.balanceLeft = user.wallet.total_balance; + // } + + // return indexObject; + } + + async crawlUrlsFromSitemap(url: URL, maxPages: number) { + const sitemapsFromRobotsTxt = await this.getSitemapsFromRobotsTxt(url); + + const initialSitemaps: string[] = []; + if (sitemapsFromRobotsTxt === null) { + initialSitemaps.push(`${url.origin}/sitemap.xml`); + } else { + initialSitemaps.push(...sitemapsFromRobotsTxt); + } + + + const allUrls: Set = new Set(); + const processedSitemaps: Set = new Set(); + + const fetchSitemapUrls = async (sitemapUrl: string) => { + sitemapUrl = sitemapUrl.trim(); + + if (processedSitemaps.has(sitemapUrl)) { + return; + } + + processedSitemaps.add(sitemapUrl); + + try { + const response = await fetch(sitemapUrl); + const sitemapContent = await response.text(); + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(sitemapContent, 'text/xml'); + + // handle normal sitemap + const urlElements = xmlDoc.getElementsByTagName('url'); + for (let i = 0; i < urlElements.length; i++) { + const locElement = urlElements[i].getElementsByTagName('loc')[0]; + if (locElement) { + const loc = locElement.textContent?.trim() || ''; + if (loc.startsWith(url.origin) && !loc.endsWith('.xml')) { + allUrls.add(removeURLHash(loc)); + } + if (allUrls.size >= maxPages) { + return; + } + } + } + + // handle sitemap index + const sitemapElements = xmlDoc.getElementsByTagName('sitemap'); + for (let i = 0; i < sitemapElements.length; i++) { + const locElement = sitemapElements[i].getElementsByTagName('loc')[0]; + if (locElement) { + await fetchSitemapUrls(locElement.textContent?.trim() || ''); + if (allUrls.size >= maxPages) { + return; + } + } + } + } catch (error) { + this.logger.error(`Error fetching sitemap ${sitemapUrl}:`, error); + } + }; + + for (const sitemapUrl of initialSitemaps) { + await fetchSitemapUrls(sitemapUrl); + if (allUrls.size >= maxPages) { + break; + } + } + + const urlsToProcess = Array.from(allUrls).slice(0, maxPages); + + return urlsToProcess; + } + + async getSitemapsFromRobotsTxt(url: URL) { + const hostname = url.origin; + const robotsUrl = `${hostname}/robots.txt`; + const response = await fetch(robotsUrl); + if (response.status === 404) { + return null; + } + const robotsTxt = await response.text(); + if (robotsTxt.length) { + const robot = robotsParser(robotsUrl, robotsTxt); + return robot.getSitemaps(); + } + + return null; + } +} diff --git a/src/cloud-functions/data-crunching.ts b/src/cloud-functions/data-crunching.ts new file mode 100644 index 0000000..fd7488e --- /dev/null +++ b/src/cloud-functions/data-crunching.ts @@ -0,0 +1,281 @@ +import { + Defer, + PromiseThrottle, + RPCHost, +} from 'civkit'; +import { singleton } from 'tsyringe'; +import { + // CloudScheduleV2, CloudTaskV2, + FirebaseStorageBucketControl, Logger, Param, TempFileManager +} from '../shared'; +import _ from 'lodash'; +import { CrawlerHost } from '../api/crawler'; + +import { Crawled } from '../db/crawled'; +import dayjs from 'dayjs'; +import { createReadStream } from 'fs'; +import { appendFile } from 'fs/promises'; +import { createGzip } from 'zlib'; +import { getFunctions } from 'firebase-admin/functions'; +import { SnapshotFormatter } from '../services/snapshot-formatter'; +import { getFunctionUrl } from '../utils/get-function-url'; + +dayjs.extend(require('dayjs/plugin/utc')); + +@singleton() +export class DataCrunchingHost extends RPCHost { + logger = this.globalLogger.child({ service: this.constructor.name }); + + pageCacheCrunchingPrefix = 'crunched-pages'; + pageCacheCrunchingBatchSize = 5000; + pageCacheCrunchingTMinus = 6 * 24 * 60 * 60 * 1000; + rev = 7; + + constructor( + protected globalLogger: Logger, + + protected crawler: CrawlerHost, + protected snapshotFormatter: SnapshotFormatter, + protected tempFileManager: TempFileManager, + protected firebaseObjectStorage: FirebaseStorageBucketControl, + ) { + super(..._.without(arguments, crawler)); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + // @CloudTaskV2({ + // runtime: { + // cpu: 2, + // memory: '4GiB', + // timeoutSeconds: 3600, + // concurrency: 2, + // maxInstances: 200, + // retryConfig: { + // maxAttempts: 3, + // minBackoffSeconds: 60, + // }, + // rateLimits: { + // maxConcurrentDispatches: 150, + // maxDispatchesPerSecond: 2, + // }, + // }, + // tags: ['DataCrunching'], + // }) + async crunchPageCacheWorker( + @Param('date') date: string, + @Param('offset', { default: 0 }) offset: number + ) { + this.logger.info(`Crunching page cache @${date}+${offset}...`); + for await (const { fileName, records } of this.iterPageCacheRecords(date, offset)) { + this.logger.info(`Crunching ${fileName}...`); + const fileOnDrive = await this.crunchCacheRecords(records); + const fstream = createReadStream(fileOnDrive.path); + const gzipStream = createGzip(); + fstream.pipe(gzipStream, { end: true }); + await this.firebaseObjectStorage.bucket.file(fileName).save(gzipStream, { + contentType: 'application/jsonl+gzip', + }); + } + + this.logger.info(`Crunching page cache @${date}+${offset} done.`); + + return true; + } + + // @CloudScheduleV2('2 0 * * *', { + // name: 'crunchPageCacheEveryday', + // runtime: { + // cpu: 2, + // memory: '4GiB', + // timeoutSeconds: 1800, + // timeZone: 'UTC', + // retryCount: 3, + // minBackoffSeconds: 60, + // }, + // tags: ['DataCrunching'], + // }) + async dispatchPageCacheCrunching() { + for await (const { fileName, date, offset } of this.iterPageCacheChunks()) { + this.logger.info(`Dispatching ${fileName}...`); + // sse.write({ data: `Dispatching ${fileName}...` }); + + await getFunctions().taskQueue('crunchPageCacheWorker').enqueue({ date, offset }, { + dispatchDeadlineSeconds: 1800, + uri: await getFunctionUrl('crunchPageCacheWorker'), + }); + } + + return true; + } + + // @CloudHTTPv2({ + // runtime: { + // cpu: 2, + // memory: '4GiB', + // timeoutSeconds: 3600, + // concurrency: 2, + // maxInstances: 200, + // }, + // tags: ['DataCrunching'], + // }) + // async dispatchPageCacheCrunching( + // @RPCReflect() rpcReflect: RPCReflection + // ) { + // const sse = new OutputServerEventStream({ highWaterMark: 4096 }); + // rpcReflect.return(sse); + // rpcReflect.catch((err) => { + // sse.end({ data: `Error: ${err.message}` }); + // }); + // for await (const { fileName, date, offset } of this.iterPageCacheChunks()) { + // this.logger.info(`Dispatching ${fileName}...`); + // sse.write({ data: `Dispatching ${fileName}...` }); + + // await getFunctions().taskQueue('crunchPageCacheWorker').enqueue({ date, offset }, { + // dispatchDeadlineSeconds: 1800, + // uri: await getFunctionUrl('crunchPageCacheWorker'), + // }); + // } + + // sse.end({ data: 'done' }); + + // return true; + // } + + async* iterPageCacheRecords(date?: string, inputOffset?: number | string) { + const startOfToday = dayjs().utc().startOf('day'); + const startingPoint = dayjs().utc().subtract(this.pageCacheCrunchingTMinus, 'ms').startOf('day'); + let theDay = startingPoint; + + if (date) { + theDay = dayjs(date).utc().startOf('day'); + } + + let counter = 0; + if (inputOffset) { + counter = parseInt(inputOffset as string, 10); + } + + while (theDay.isBefore(startOfToday)) { + const fileName = `${this.pageCacheCrunchingPrefix}/r${this.rev}/${theDay.format('YYYY-MM-DD')}/${counter}.jsonl.gz`; + const offset = counter; + counter += this.pageCacheCrunchingBatchSize; + const fileExists = (await this.firebaseObjectStorage.bucket.file(fileName).exists())[0]; + if (fileExists) { + continue; + } + + const records = await Crawled.fromFirestoreQuery(Crawled.COLLECTION + .where('createdAt', '>=', theDay.toDate()) + .where('createdAt', '<', theDay.add(1, 'day').toDate()) + .orderBy('createdAt', 'asc') + .offset(offset) + .limit(this.pageCacheCrunchingBatchSize) + ); + + this.logger.info(`Found ${records.length} records for ${theDay.format('YYYY-MM-DD')} at offset ${offset}`, { fileName, counter }); + + if (!records.length) { + if (date) { + break; + } + theDay = theDay.add(1, 'day'); + counter = 0; + continue; + } + + yield { fileName, records }; + + if (offset) { + break; + } + } + } + + async* iterPageCacheChunks() { + const startOfToday = dayjs().utc().startOf('day'); + const startingPoint = dayjs().utc().subtract(this.pageCacheCrunchingTMinus, 'ms').startOf('day'); + let theDay = startingPoint; + + let counter = 0; + + while (theDay.isBefore(startOfToday)) { + const fileName = `${this.pageCacheCrunchingPrefix}/r${this.rev}/${theDay.format('YYYY-MM-DD')}/${counter}.jsonl.gz`; + const offset = counter; + counter += this.pageCacheCrunchingBatchSize; + const fileExists = (await this.firebaseObjectStorage.bucket.file(fileName).exists())[0]; + if (fileExists) { + continue; + } + + const nRecords = (await Crawled.COLLECTION + .where('createdAt', '>=', theDay.toDate()) + .where('createdAt', '<', theDay.add(1, 'day').toDate()) + .orderBy('createdAt', 'asc') + .offset(offset) + .limit(this.pageCacheCrunchingBatchSize) + .count().get()).data().count; + + this.logger.info(`Found ${nRecords} records for ${theDay.format('YYYY-MM-DD')} at offset ${offset}`, { fileName, counter }); + if (nRecords < this.pageCacheCrunchingBatchSize) { + theDay = theDay.add(1, 'day'); + counter = 0; + } + if (nRecords) { + yield { fileName, date: theDay.toISOString(), offset }; + } + } + } + + async crunchCacheRecords(records: Crawled[]) { + const throttle = new PromiseThrottle(30); + const localFilePath = this.tempFileManager.alloc(); + let nextDrainDeferred = Defer(); + nextDrainDeferred.resolve(); + + for (const record of records) { + await throttle.acquire(); + this.firebaseObjectStorage.downloadFile(`snapshots/${record._id}`) + .then(async (snapshotTxt) => { + try { + const snapshot = JSON.parse(snapshotTxt.toString('utf-8')); + + let formatted = await this.snapshotFormatter.formatSnapshot('default', snapshot); + if (!formatted.content) { + formatted = await this.snapshotFormatter.formatSnapshot('markdown', snapshot); + } + + await nextDrainDeferred.promise; + await appendFile(localFilePath, JSON.stringify({ + url: snapshot.href, + title: snapshot.title || '', + html: snapshot.html || '', + text: snapshot.text || '', + content: formatted.content || '', + }) + '\n', { encoding: 'utf-8' }); + + } catch (err) { + this.logger.warn(`Failed to parse snapshot for ${record._id}`, { err }); + } + }) + .finally(() => { + throttle.release(); + }); + } + + await throttle.nextDrain(); + + + const ro = { + path: localFilePath + }; + + this.tempFileManager.bindPathTo(ro, localFilePath); + + return ro; + } +} diff --git a/src/db/adaptive-crawl-task.ts b/src/db/adaptive-crawl-task.ts new file mode 100644 index 0000000..651b4d1 --- /dev/null +++ b/src/db/adaptive-crawl-task.ts @@ -0,0 +1,86 @@ +import { Also, Prop, parseJSONText } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; +import _ from 'lodash'; + +export enum AdaptiveCrawlTaskStatus { + PENDING = 'pending', + PROCESSING = 'processing', + COMPLETED = 'completed', + FAILED = 'failed', +} + +@Also({ + dictOf: Object +}) +export class AdaptiveCrawlTask extends FirestoreRecord { + static override collectionName = 'adaptiveCrawlTasks'; + + override _id!: string; + + @Prop({ + required: true + }) + status!: AdaptiveCrawlTaskStatus; + + @Prop({ + required: true + }) + statusText!: string; + + @Prop() + meta!: { + useSitemap: boolean; + maxPages: number; + targetUrl: string; + }; + + @Prop() + urls!: string[]; + + @Prop() + processed!: { + [url: string]: string; + }; + + @Prop() + failed!: { + [url: string]: any; + }; + + @Prop() + createdAt!: Date; + + @Prop() + finishedAt?: Date; + + @Prop() + duration?: number; + + static patchedFields = [ + 'meta', + ]; + + static override from(input: any) { + for (const field of this.patchedFields) { + if (typeof input[field] === 'string') { + input[field] = parseJSONText(input[field]); + } + } + + return super.from(input) as AdaptiveCrawlTask; + } + + override degradeForFireStore() { + const copy: any = { ...this }; + + for (const field of (this.constructor as typeof AdaptiveCrawlTask).patchedFields) { + if (typeof copy[field] === 'object') { + copy[field] = JSON.stringify(copy[field]) as any; + } + } + + return copy; + } + + [k: string]: any; +} diff --git a/src/db/crawled.ts b/src/db/crawled.ts new file mode 100644 index 0000000..100da62 --- /dev/null +++ b/src/db/crawled.ts @@ -0,0 +1,72 @@ +import { Also, parseJSONText, Prop } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; +import _ from 'lodash'; +import type { PageSnapshot } from '../services/puppeteer'; + +@Also({ + dictOf: Object +}) +export class Crawled extends FirestoreRecord { + static override collectionName = 'crawled'; + + override _id!: string; + + @Prop({ + required: true + }) + url!: string; + + @Prop({ + required: true + }) + urlPathDigest!: string; + + @Prop() + htmlSignificantlyModifiedByJs?: boolean; + + @Prop() + snapshot?: PageSnapshot & { screenshot: never; pageshot: never; }; + + @Prop() + screenshotAvailable?: boolean; + + @Prop() + pageshotAvailable?: boolean; + + @Prop() + snapshotAvailable?: boolean; + + @Prop() + createdAt!: Date; + + @Prop() + expireAt!: Date; + + static patchedFields = [ + 'snapshot' + ]; + + static override from(input: any) { + for (const field of this.patchedFields) { + if (typeof input[field] === 'string') { + input[field] = parseJSONText(input[field]); + } + } + + return super.from(input) as Crawled; + } + + override degradeForFireStore() { + const copy: any = { ...this }; + + for (const field of (this.constructor as typeof Crawled).patchedFields) { + if (typeof copy[field] === 'object') { + copy[field] = JSON.stringify(copy[field]) as any; + } + } + + return copy; + } + + [k: string]: any; +} diff --git a/src/db/domain-blockade.ts b/src/db/domain-blockade.ts new file mode 100644 index 0000000..72955a2 --- /dev/null +++ b/src/db/domain-blockade.ts @@ -0,0 +1,30 @@ +import { Also, Prop } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; + +@Also({ + dictOf: Object +}) +export class DomainBlockade extends FirestoreRecord { + static override collectionName = 'domainBlockades'; + + override _id!: string; + + @Prop({ + required: true + }) + domain!: string; + + @Prop({ required: true }) + triggerReason!: string; + + @Prop() + triggerUrl?: string; + + @Prop() + createdAt!: Date; + + @Prop() + expireAt?: Date; + + [k: string]: any; +} diff --git a/src/db/domain-profile.ts b/src/db/domain-profile.ts new file mode 100644 index 0000000..3ee9476 --- /dev/null +++ b/src/db/domain-profile.ts @@ -0,0 +1,31 @@ +import { Also, Prop } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; +import { ENGINE_TYPE } from '../dto/crawler-options'; + +@Also({ + dictOf: Object +}) +export class DomainProfile extends FirestoreRecord { + static override collectionName = 'domainProfiles'; + + override _id!: string; + + @Prop({ + required: true + }) + path!: string; + + @Prop() + triggerUrl?: string; + + @Prop({ required: true, type: ENGINE_TYPE }) + engine!: string; + + @Prop() + createdAt!: Date; + + @Prop() + expireAt?: Date; + + [k: string]: any; +} diff --git a/src/db/img-alt.ts b/src/db/img-alt.ts new file mode 100644 index 0000000..a1221ba --- /dev/null +++ b/src/db/img-alt.ts @@ -0,0 +1,42 @@ +import { Also, Prop } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; +import _ from 'lodash'; + +@Also({ + dictOf: Object +}) +export class ImgAlt extends FirestoreRecord { + static override collectionName = 'imgAlts'; + + override _id!: string; + + @Prop({ + required: true + }) + src!: string; + + @Prop({ + required: true + }) + urlDigest!: string; + + @Prop() + width?: number; + + @Prop() + height?: number; + + @Prop() + generatedAlt?: string; + + @Prop() + originalAlt?: string; + + @Prop() + createdAt!: Date; + + @Prop() + expireAt?: Date; + + [k: string]: any; +} diff --git a/src/db/pdf.ts b/src/db/pdf.ts new file mode 100644 index 0000000..bb088e9 --- /dev/null +++ b/src/db/pdf.ts @@ -0,0 +1,65 @@ +import { Also, Prop, parseJSONText } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; +import _ from 'lodash'; + +@Also({ + dictOf: Object +}) +export class PDFContent extends FirestoreRecord { + static override collectionName = 'pdfs'; + + override _id!: string; + + @Prop({ + required: true + }) + src!: string; + + @Prop({ + required: true + }) + urlDigest!: string; + + @Prop() + meta?: { [k: string]: any; }; + + @Prop() + text?: string; + + @Prop() + content?: string; + + @Prop() + createdAt!: Date; + + @Prop() + expireAt?: Date; + + static patchedFields = [ + 'meta' + ]; + + static override from(input: any) { + for (const field of this.patchedFields) { + if (typeof input[field] === 'string') { + input[field] = parseJSONText(input[field]); + } + } + + return super.from(input) as PDFContent; + } + + override degradeForFireStore() { + const copy: any = { ...this }; + + for (const field of (this.constructor as typeof PDFContent).patchedFields) { + if (typeof copy[field] === 'object') { + copy[field] = JSON.stringify(copy[field]) as any; + } + } + + return copy; + } + + [k: string]: any; +} diff --git a/src/db/searched.ts b/src/db/searched.ts new file mode 100644 index 0000000..aa05ad0 --- /dev/null +++ b/src/db/searched.ts @@ -0,0 +1,68 @@ +import { Also, parseJSONText, Prop } from 'civkit'; +import { FirestoreRecord } from '../shared/lib/firestore'; +import _ from 'lodash'; + +@Also({ + dictOf: Object +}) +export class SearchResult extends FirestoreRecord { + static override collectionName = 'searchResults'; + + override _id!: string; + + @Prop({ + required: true + }) + query!: any; + + @Prop({ + required: true + }) + queryDigest!: string; + + @Prop() + response?: any; + + @Prop() + createdAt!: Date; + + @Prop() + expireAt?: Date; + + [k: string]: any; + + static patchedFields = [ + 'query', + 'response', + ]; + + static override from(input: any) { + for (const field of this.patchedFields) { + if (typeof input[field] === 'string') { + input[field] = parseJSONText(input[field]); + } + } + + return super.from(input) as SearchResult; + } + + override degradeForFireStore() { + const copy: any = { ...this }; + + for (const field of (this.constructor as typeof SearchResult).patchedFields) { + if (typeof copy[field] === 'object') { + copy[field] = JSON.stringify(copy[field]) as any; + } + } + + return copy; + } +} + +export class SerperSearchResult extends SearchResult { + static override collectionName = 'serperSearchResults'; +} + +export class SERPResult extends SearchResult { + static override collectionName = 'SERPResults'; +} \ No newline at end of file diff --git a/src/dto/adaptive-crawler-options.ts b/src/dto/adaptive-crawler-options.ts new file mode 100644 index 0000000..e7ac0d9 --- /dev/null +++ b/src/dto/adaptive-crawler-options.ts @@ -0,0 +1,61 @@ +import { Also, AutoCastable, Prop, RPC_CALL_ENVIRONMENT } from 'civkit'; +import type { Request, Response } from 'express'; + + +@Also({ + openapi: { + operation: { + parameters: { + 'X-Use-Sitemap': { + description: 'Use sitemap to crawl the website.', + in: 'header', + schema: { type: 'string' } + }, + 'X-Max-Depth': { + description: 'Max deep level to crawl.', + in: 'header', + schema: { type: 'string' } + }, + 'X-Max-Pages': { + description: 'Max number of pages to crawl.', + in: 'header', + schema: { type: 'string' } + }, + } + } + } +}) +export class AdaptiveCrawlerOptions extends AutoCastable { + @Prop({ + default: true, + desc: 'Use sitemap to crawl the website.', + }) + useSitemap!: boolean; + + @Prop({ + default: 10, + desc: 'Max number of pages to crawl.', + validate: (v: number) => v >= 1 && v <= 100, + }) + maxPages!: number; + + static override from(input: any) { + const instance = super.from(input) as AdaptiveCrawlerOptions; + const ctx = Reflect.get(input, RPC_CALL_ENVIRONMENT) as { + req: Request, + res: Response, + } | undefined; + + let maxPages = parseInt(ctx?.req.get('x-max-pages') || ''); + if (!isNaN(maxPages) && maxPages > 0) { + instance.maxPages = maxPages <= 100 ? maxPages : 100; + } + + const useSitemap = ctx?.req.get('x-use-sitemap'); + if (useSitemap !== undefined) { + instance.useSitemap = Boolean(useSitemap); + } + + return instance; + } +} diff --git a/src/dto/crawler-options.ts b/src/dto/crawler-options.ts new file mode 100644 index 0000000..01dea59 --- /dev/null +++ b/src/dto/crawler-options.ts @@ -0,0 +1,727 @@ +import { Also, AutoCastable, ParamValidationError, Prop, RPC_CALL_ENVIRONMENT } from 'civkit/civ-rpc'; +import { FancyFile } from 'civkit/fancy-file'; +import { Cookie, parseString as parseSetCookieString } from 'set-cookie-parser'; +import { Context } from '../services/registry'; +import { TurnDownTweakableOptions } from './turndown-tweakable-options'; +import type { PageSnapshot } from '../services/puppeteer'; + +export enum CONTENT_FORMAT { + CONTENT = 'content', + MARKDOWN = 'markdown', + HTML = 'html', + TEXT = 'text', + PAGESHOT = 'pageshot', + SCREENSHOT = 'screenshot', + VLM = 'vlm', + READER_LM = 'readerlm-v2', +} + +export enum ENGINE_TYPE { + AUTO = 'auto', + BROWSER = 'browser', + CURL = 'curl', + CF_BROWSER_RENDERING = 'cf-browser-rendering', +} + +export enum RESPOND_TIMING { + HTML = 'html', + VISIBLE_CONTENT = 'visible-content', + MUTATION_IDLE = 'mutation-idle', + RESOURCE_IDLE = 'resource-idle', + MEDIA_IDLE = 'media-idle', + NETWORK_IDLE = 'network-idle', +} + +const CONTENT_FORMAT_VALUES = new Set(Object.values(CONTENT_FORMAT)); + +export const IMAGE_RETENTION_MODES = ['none', 'all', 'alt', 'all_p', 'alt_p'] as const; +const IMAGE_RETENTION_MODE_VALUES = new Set(IMAGE_RETENTION_MODES); +export const BASE_URL_MODES = ['initial', 'final'] as const; +const BASE_URL_MODE_VALUES = new Set(BASE_URL_MODES); + +class Viewport extends AutoCastable { + @Prop({ + default: 1024 + }) + width!: number; + @Prop({ + default: 1024 + }) + height!: number; + @Prop() + deviceScaleFactor?: number; + @Prop() + isMobile?: boolean; + @Prop() + isLandscape?: boolean; + @Prop() + hasTouch?: boolean; +} + +@Also({ + openapi: { + operation: { + parameters: { + 'Accept': { + description: `Specifies your preference for the response format.\n\n` + + `Supported formats: \n` + + `- text/event-stream\n` + + `- application/json or text/json\n` + + `- text/plain` + , + in: 'header', + schema: { type: 'string' } + }, + 'X-Cache-Tolerance': { + description: `Sets internal cache tolerance in seconds if this header is specified with a integer.`, + in: 'header', + schema: { type: 'string' } + }, + 'X-No-Cache': { + description: `Ignores internal cache if this header is specified with a value.\n\nEquivalent to X-Cache-Tolerance: 0`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Respond-With': { + description: `Specifies the (non-default) form of the crawled data you prefer.\n\n` + + `Supported formats: \n` + + `- markdown\n` + + `- html\n` + + `- text\n` + + `- pageshot\n` + + `- screenshot\n` + + `- content\n` + + `- any combination of the above\n` + + `- readerlm-v2\n` + + `- vlm\n\n` + + `Default: content\n` + , + in: 'header', + schema: { type: 'string' } + }, + 'X-Wait-For-Selector': { + description: `Specifies a CSS selector to wait for the appearance of such an element before returning.\n\n` + + 'Example: `X-Wait-For-Selector: .content-block`\n' + , + in: 'header', + schema: { type: 'string' } + }, + 'X-Target-Selector': { + description: `Specifies a CSS selector for return target instead of the full html.\n\n` + + 'Implies `X-Wait-For-Selector: (same selector)`' + , + in: 'header', + schema: { type: 'string' } + }, + 'X-Remove-Selector': { + description: `Specifies a CSS selector to remove elements from the full html.\n\n` + + 'Example `X-Remove-Selector: nav`' + , + in: 'header', + schema: { type: 'string' } + }, + 'X-Keep-Img-Data-Url': { + description: `Keep data-url as it instead of transforming them to object-url. (Only applicable when targeting markdown format)\n\n` + + 'Example `X-Keep-Img-Data-Url: true`' + , + in: 'header', + schema: { type: 'string' } + }, + 'X-Proxy-Url': { + description: `Specifies your custom proxy if you prefer to use one.\n\n` + + `Supported protocols: \n` + + `- http\n` + + `- https\n` + + `- socks4\n` + + `- socks5\n\n` + + `For authentication, https://user:pass@host:port`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Proxy': { + description: `Use a proxy server provided by us.\n\nOptionally specify two-letter country code.`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Robots-Txt': { + description: `Load and conform to the respective robot.txt on the target origin.\n\nOptionally specify a bot UA to check against.\n\n`, + in: 'header', + schema: { type: 'string' } + }, + 'DNT': { + description: `When set to 1, prevent the result of this request to be cached in the system.\n\n`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Set-Cookie': { + description: `Sets cookie(s) to the headless browser for your request. \n\n` + + `Syntax is the same with standard Set-Cookie`, + in: 'header', + schema: { type: 'string' } + }, + 'X-With-Generated-Alt': { + description: `Enable automatic alt-text generating for images without an meaningful alt-text.\n\n` + + `Note: Does not work when \`X-Respond-With\` is specified`, + in: 'header', + schema: { type: 'string' } + }, + 'X-With-Images-Summary': { + description: `Enable dedicated summary section for images on the page.`, + in: 'header', + schema: { type: 'string' } + }, + 'X-With-links-Summary': { + description: `Enable dedicated summary section for hyper links on the page.`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Retain-Images': { + description: `Image retention modes.\n\n` + + `Supported modes: \n` + + `- all: all images\n` + + `- none: no images\n` + + `- alt: only alt text\n` + + `- all_p: all images and with generated alt text\n` + + `- alt_p: only alt text and with generated alt\n\n`, + in: 'header', + schema: { type: 'string' } + }, + 'X-With-Iframe': { + description: `Enable filling iframe contents into main. (violates standards)`, + in: 'header', + schema: { type: 'string' } + }, + 'X-With-Shadow-Dom': { + description: `Enable filling shadow dom contents into main. (violates standards)`, + in: 'header', + schema: { type: 'string' } + }, + 'X-User-Agent': { + description: `Override User-Agent.`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Timeout': { + description: `Specify timeout in seconds. Max 180.`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Locale': { + description: 'Specify browser locale for the page.', + in: 'header', + schema: { type: 'string' } + }, + 'X-Referer': { + description: 'Specify referer for the page.', + in: 'header', + schema: { type: 'string' } + }, + 'X-Token-Budget': { + description: 'Specify a budget in tokens.\n\nIf the resulting token cost exceeds the budget, the request is rejected.', + in: 'header', + schema: { type: 'string' } + }, + 'X-Respond-Timing': { + description: `Explicitly specify the respond timing. One of the following:\n\n` + + `- html: directly return unrendered HTML\n` + + `- visible-content: return immediately when any content becomes available\n` + + `- mutation-idle: wait for DOM mutations to settle and remain unchanged for at least 0.2s\n` + + `- resource-idle: wait for no additional resources that would affect page logic and content has SUCCEEDED loading in 0.5s\n` + + `- media-idle: wait for no additional resources, including media resources, has SUCCEEDED loading in 0.5s\n` + + `- network-idle: wait for full load of webpage, also known as networkidle0.\n\n`, + in: 'header', + schema: { type: 'string' } + }, + 'X-Engine': { + description: 'Specify the engine to use for crawling.\n\nSupported: browser, direct, cf-browser-rendering', + in: 'header', + schema: { type: 'string' } + }, + 'X-Base': { + description: 'Select base modes of relative URLs.\n\nSupported: initial, final', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Heading-Style': { + description: 'Heading style of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).\n\nSupported: setext, atx', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Hr': { + description: 'Hr text of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Bullet-List-Marker': { + description: 'Bullet list marker of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).\n\nSupported: -, +, *', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Em-Delimiter': { + description: 'Em delimiter of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).\n\nSupported: _, *', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Strong-Delimiter': { + description: 'Strong delimiter of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).\n\nSupported: **, __', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Link-Style': { + description: 'Link style of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).\n\nSupported: inlined, referenced, discarded', + in: 'header', + schema: { type: 'string' } + }, + 'X-Md-Link-Reference-Style': { + description: 'Link reference style of the generated markdown.\n\nThis is an option passed through to [Turndown](https://github.com/mixmark-io/turndown?tab=readme-ov-file#options).\n\nSupported: full, collapsed, shortcut, discarded', + in: 'header', + schema: { type: 'string' } + }, + } + } + } +}) +export class CrawlerOptions extends AutoCastable { + + @Prop() + url?: string; + + @Prop() + html?: string; + + @Prop({ + type: BASE_URL_MODE_VALUES, + default: 'initial', + }) + base?: typeof BASE_URL_MODES[number]; + + @Prop({ + desc: 'Base64 encoded PDF.', + type: [FancyFile, String] + }) + pdf?: FancyFile | string; + + @Prop({ + default: CONTENT_FORMAT.CONTENT, + type: [CONTENT_FORMAT, String] + }) + respondWith!: string; + + @Prop({ + default: false, + }) + withGeneratedAlt!: boolean; + + @Prop({ default: 'all', type: IMAGE_RETENTION_MODE_VALUES }) + retainImages?: typeof IMAGE_RETENTION_MODES[number]; + + @Prop({ + default: false, + }) + withLinksSummary!: boolean | string; + + @Prop({ + default: false, + }) + withImagesSummary!: boolean; + + @Prop({ + default: false, + }) + noCache!: boolean; + + @Prop({ + default: false, + }) + noGfm!: string | boolean; + + @Prop() + cacheTolerance?: number; + + @Prop({ arrayOf: String }) + targetSelector?: string | string[]; + + @Prop({ arrayOf: String }) + waitForSelector?: string | string[]; + + @Prop({ arrayOf: String }) + removeSelector?: string | string[]; + + @Prop({ + default: false, + }) + keepImgDataUrl!: boolean; + + @Prop({ + default: false, + type: [String, Boolean] + }) + withIframe!: boolean | 'quoted'; + + @Prop({ + default: false, + }) + withShadowDom!: boolean; + + @Prop({ + arrayOf: String, + }) + setCookies?: Cookie[]; + + @Prop() + proxyUrl?: string; + + @Prop() + proxy?: string; + + @Prop() + userAgent?: string; + + @Prop() + engine?: string; + + @Prop({ + arrayOf: String, + }) + injectPageScript?: string[]; + + @Prop({ + arrayOf: String, + }) + injectFrameScript?: string[]; + + @Prop({ + validate: (v: number) => v > 0 && v <= 180, + type: Number, + nullable: true, + }) + timeout?: number | null; + + @Prop() + locale?: string; + + @Prop() + referer?: string; + + @Prop() + tokenBudget?: number; + + @Prop() + viewport?: Viewport; + + @Prop() + instruction?: string; + + @Prop() + jsonSchema?: object; + + @Prop() + robotsTxt?: string; + + @Prop() + doNotTrack?: number | null; + + @Prop() + markdown?: TurnDownTweakableOptions; + + @Prop({ + type: RESPOND_TIMING, + }) + respondTiming?: RESPOND_TIMING; + + _hintIps?: string[]; + + static override from(input: any) { + const instance = super.from(input) as CrawlerOptions; + const ctx = Reflect.get(input, RPC_CALL_ENVIRONMENT) as Context | undefined; + + const customMode = ctx?.get('x-respond-with') || ctx?.get('x-return-format'); + if (customMode) { + instance.respondWith = customMode; + } + if (instance.respondWith) { + instance.respondWith = instance.respondWith.toLowerCase(); + } + if (instance.respondWith?.includes('lm')) { + if (instance.respondWith.includes('content') || instance.respondWith.includes('markdown')) { + throw new ParamValidationError({ + path: 'respondWith', + message: `LM formats conflicts with content/markdown.`, + }); + } + } + + const locale = ctx?.get('x-locale'); + if (locale) { + instance.locale = locale; + } + + const referer = ctx?.get('x-referer'); + if (referer) { + instance.referer = referer; + } + + const withGeneratedAlt = ctx?.get('x-with-generated-alt'); + if (withGeneratedAlt) { + instance.withGeneratedAlt = Boolean(withGeneratedAlt); + } + const withLinksSummary = ctx?.get('x-with-links-summary'); + if (withLinksSummary) { + if (withLinksSummary === 'all') { + instance.withLinksSummary = withLinksSummary; + } else { + instance.withLinksSummary = Boolean(withLinksSummary); + } + } + const withImagesSummary = ctx?.get('x-with-images-summary'); + if (withImagesSummary) { + instance.withImagesSummary = Boolean(withImagesSummary); + } + const retainImages = ctx?.get('x-retain-images'); + if (retainImages && IMAGE_RETENTION_MODE_VALUES.has(retainImages)) { + instance.retainImages = retainImages as any; + } + if (instance.withGeneratedAlt) { + instance.retainImages = 'all_p'; + } + const noCache = ctx?.get('x-no-cache'); + if (noCache) { + instance.noCache = Boolean(noCache); + } + if (instance.noCache && instance.cacheTolerance === undefined) { + instance.cacheTolerance = 0; + } + let cacheTolerance = parseInt(ctx?.get('x-cache-tolerance') || ''); + if (!isNaN(cacheTolerance)) { + instance.cacheTolerance = cacheTolerance; + } + + const noGfm = ctx?.get('x-no-gfm'); + if (noGfm) { + instance.noGfm = noGfm === 'table' ? noGfm : Boolean(noGfm); + } + + let timeoutSeconds = parseInt(ctx?.get('x-timeout') || ''); + if (!isNaN(timeoutSeconds) && timeoutSeconds > 0) { + instance.timeout = timeoutSeconds <= 180 ? timeoutSeconds : 180; + } else if (ctx?.get('x-timeout')) { + instance.timeout = null; + } + + const removeSelector = ctx?.get('x-remove-selector')?.split(', ').filter(Boolean); + instance.removeSelector ??= removeSelector?.length ? removeSelector : undefined; + const targetSelector = ctx?.get('x-target-selector')?.split(', ').filter(Boolean); + instance.targetSelector ??= targetSelector?.length ? targetSelector : undefined; + const waitForSelector = ctx?.get('x-wait-for-selector')?.split(', ').filter(Boolean); + instance.waitForSelector ??= (waitForSelector?.length ? waitForSelector : undefined) || instance.targetSelector; + const overrideUserAgent = ctx?.get('x-user-agent') || undefined; + instance.userAgent ??= overrideUserAgent; + + const engine = ctx?.get('x-engine'); + if (engine) { + instance.engine = engine; + } + if (instance.engine) { + instance.engine = instance.engine.toLowerCase(); + } + if (instance.engine === 'vlm') { + instance.engine = ENGINE_TYPE.BROWSER; + instance.respondWith = CONTENT_FORMAT.VLM; + } else if (instance.engine === 'readerlm-v2') { + instance.engine = ENGINE_TYPE.AUTO; + instance.respondWith = CONTENT_FORMAT.READER_LM; + } + + const keepImgDataUrl = ctx?.get('x-keep-img-data-url'); + if (keepImgDataUrl) { + instance.keepImgDataUrl = Boolean(keepImgDataUrl); + } + const withIframe = ctx?.get('x-with-iframe'); + if (withIframe) { + instance.withIframe = withIframe.toLowerCase() === 'quoted' ? 'quoted' : Boolean(withIframe); + } + if (instance.withIframe) { + instance.timeout ??= null; + } + const withShadowDom = ctx?.get('x-with-shadow-dom'); + if (withShadowDom) { + instance.withShadowDom = Boolean(withShadowDom); + } + if (instance.withShadowDom) { + instance.timeout ??= null; + } + + const cookies: Cookie[] = []; + const setCookieHeaders = (ctx?.get('x-set-cookie')?.split(', ') || (instance.setCookies as any as string[])).filter(Boolean); + if (Array.isArray(setCookieHeaders)) { + for (const setCookie of setCookieHeaders) { + cookies.push({ + ...parseSetCookieString(setCookie, { decodeValues: true }), + }); + } + } else if (setCookieHeaders && typeof setCookieHeaders === 'string') { + cookies.push({ + ...parseSetCookieString(setCookieHeaders, { decodeValues: true }), + }); + } + instance.setCookies = cookies; + + const proxyUrl = ctx?.get('x-proxy-url'); + instance.proxyUrl ??= proxyUrl || undefined; + const proxy = ctx?.get('x-proxy'); + instance.proxy ??= proxy || undefined; + const robotsTxt = ctx?.get('x-robots-txt'); + instance.robotsTxt ??= robotsTxt || undefined; + + const tokenBudget = ctx?.get('x-token-budget'); + instance.tokenBudget ??= parseInt(tokenBudget || '') || undefined; + + const baseMode = ctx?.get('x-base'); + if (baseMode) { + instance.base = baseMode as any; + } + + const dnt = ctx?.get('dnt'); + instance.doNotTrack ??= (parseInt(dnt || '') || null); + + const respondTiming = ctx?.get('x-respond-timing'); + if (respondTiming) { + instance.respondTiming ??= respondTiming as RESPOND_TIMING; + } + + if (instance.cacheTolerance) { + instance.cacheTolerance = instance.cacheTolerance * 1000; + } + + if (ctx) { + instance.markdown ??= TurnDownTweakableOptions.fromCtx(ctx); + } + + return instance; + } + + get presumedRespondTiming() { + if (this.respondTiming) { + return this.respondTiming; + } + if (this.timeout && this.timeout >= 20) { + return RESPOND_TIMING.NETWORK_IDLE; + } + if (this.respondWith.includes('shot') || this.respondWith.includes('vlm')) { + return RESPOND_TIMING.MEDIA_IDLE; + } + + return RESPOND_TIMING.RESOURCE_IDLE; + } + + isSnapshotAcceptableForEarlyResponse(snapshot: PageSnapshot) { + if (this.waitForSelector?.length) { + return false; + } + const presumedTiming = this.presumedRespondTiming; + if (presumedTiming === RESPOND_TIMING.MEDIA_IDLE && snapshot.lastMediaResourceLoaded && snapshot.lastMutationIdle) { + const now = Date.now(); + if ((Math.max(snapshot.lastMediaResourceLoaded, snapshot.lastContentResourceLoaded || 0) + 500) < now) { + return true; + } + } + if ((this.respondWith.includes('vlm') || this.respondWith.includes('pageshot')) && !snapshot.pageshot) { + return false; + } + if ((this.respondWith.includes('vlm') || this.respondWith.includes('screenshot')) && !snapshot.screenshot) { + return false; + } + if (presumedTiming === RESPOND_TIMING.RESOURCE_IDLE && snapshot.lastContentResourceLoaded && snapshot.lastMutationIdle) { + const now = Date.now(); + if ((snapshot.lastContentResourceLoaded + 500) < now) { + return true; + } + } + if (this.injectFrameScript?.length || this.injectPageScript?.length) { + return false; + } + if (presumedTiming === RESPOND_TIMING.VISIBLE_CONTENT && snapshot.parsed?.content) { + return true; + } + if (presumedTiming === RESPOND_TIMING.HTML && snapshot.html) { + return true; + } + if (presumedTiming === RESPOND_TIMING.NETWORK_IDLE) { + return false; + } + if (presumedTiming === RESPOND_TIMING.MUTATION_IDLE && snapshot.lastMutationIdle) { + return true; + } + if (this.respondWith.includes('lm')) { + return false; + } + if (this.withIframe) { + return false; + } + + return !snapshot.isIntermediate; + } + + isCacheQueryApplicable() { + if (this.noCache) { + return false; + } + if (this.cacheTolerance === 0) { + return false; + } + if (this.setCookies?.length) { + return false; + } + if (this.injectFrameScript?.length || this.injectPageScript?.length) { + return false; + } + if (this.viewport) { + return false; + } + + return true; + } + + isRequestingCompoundContentFormat() { + return !CONTENT_FORMAT_VALUES.has(this.respondWith); + } + + browserIsNotRequired() { + if (this.respondTiming && ![RESPOND_TIMING.HTML, RESPOND_TIMING.VISIBLE_CONTENT].includes(this.respondTiming)) { + return false; + } + if (this.respondWith.includes(CONTENT_FORMAT.PAGESHOT) || this.respondWith.includes(CONTENT_FORMAT.SCREENSHOT)) { + return false; + } + if (this.injectFrameScript?.length || this.injectPageScript?.length) { + return false; + } + if (this.waitForSelector?.length) { + return false; + } + if (this.withIframe || this.withShadowDom) { + return false; + } + if (this.viewport) { + return false; + } + if (this.pdf) { + return false; + } + if (this.html) { + return false; + } + + return true; + } +} + +export class CrawlerOptionsHeaderOnly extends CrawlerOptions { + static override from(input: any) { + const instance = super.from({ + [RPC_CALL_ENVIRONMENT]: Reflect.get(input, RPC_CALL_ENVIRONMENT), + }) as CrawlerOptionsHeaderOnly; + + return instance; + } +} \ No newline at end of file diff --git a/src/dto/jina-embeddings-auth.ts b/src/dto/jina-embeddings-auth.ts new file mode 100644 index 0000000..095a5a9 --- /dev/null +++ b/src/dto/jina-embeddings-auth.ts @@ -0,0 +1,273 @@ +import _ from 'lodash'; +import { + Also, AuthenticationFailedError, AuthenticationRequiredError, + RPC_CALL_ENVIRONMENT, + AutoCastable, + DownstreamServiceError, +} from 'civkit/civ-rpc'; +import { htmlEscape } from 'civkit/escape'; +import { marshalErrorLike } from 'civkit/lang'; + +import type { Context } from 'koa'; + +import logger from '../services/logger'; +import { InjectProperty } from '../services/registry'; +import { AsyncLocalContext } from '../services/async-context'; + +import envConfig from '../shared/services/secrets'; +import { JinaEmbeddingsDashboardHTTP } from '../shared/3rd-party/jina-embeddings'; +import { JinaEmbeddingsTokenAccount } from '../shared/db/jina-embeddings-token-account'; +import { TierFeatureConstraintError } from '../services/errors'; + +const authDtoLogger = logger.child({ service: 'JinaAuthDTO' }); + + +const THE_VERY_SAME_JINA_EMBEDDINGS_CLIENT = new JinaEmbeddingsDashboardHTTP(envConfig.JINA_EMBEDDINGS_DASHBOARD_API_KEY); + +@Also({ + openapi: { + operation: { + parameters: { + 'Authorization': { + description: htmlEscape`Jina Token for authentication.\n\n` + + htmlEscape`- Member of \n\n` + + `- Authorization: Bearer {YOUR_JINA_TOKEN}` + , + in: 'header', + schema: { + anyOf: [ + { type: 'string', format: 'token' } + ] + } + } + } + } + } +}) +export class JinaEmbeddingsAuthDTO extends AutoCastable { + uid?: string; + bearerToken?: string; + user?: JinaEmbeddingsTokenAccount; + + @InjectProperty(AsyncLocalContext) + ctxMgr!: AsyncLocalContext; + + jinaEmbeddingsDashboard = THE_VERY_SAME_JINA_EMBEDDINGS_CLIENT; + + static override from(input: any) { + const instance = super.from(input) as JinaEmbeddingsAuthDTO; + + const ctx = input[RPC_CALL_ENVIRONMENT] as Context; + + if (ctx) { + const authorization = ctx.get('authorization'); + + if (authorization) { + const authToken = authorization.split(' ')[1] || authorization; + instance.bearerToken = authToken; + } + + } + + if (!instance.bearerToken && input._token) { + instance.bearerToken = input._token; + } + + return instance; + } + + async getBrief(ignoreCache?: boolean | string) { + if (!this.bearerToken) { + throw new AuthenticationRequiredError({ + message: 'Jina API key is required to authenticate. Please get one from https://jina.ai' + }); + } + + let firestoreDegradation = false; + let account; + try { + account = await JinaEmbeddingsTokenAccount.fromFirestore(this.bearerToken); + } catch (err) { + // FireStore would not accept any string as input and may throw if not happy with it + firestoreDegradation = true; + logger.warn(`Firestore issue`, { err }); + } + + + const age = account?.lastSyncedAt ? Date.now() - account.lastSyncedAt.valueOf() : Infinity; + const jitter = Math.ceil(Math.random() * 30 * 1000); + + if (account && !ignoreCache) { + if ((age < (180_000 - jitter)) && (account.wallet?.total_balance > 0)) { + this.user = account; + this.uid = this.user?.user_id; + + return account; + } + } + + if (firestoreDegradation) { + logger.debug(`Using remote UC cached user`); + let r; + try { + r = await this.jinaEmbeddingsDashboard.authorization(this.bearerToken); + } catch (err: any) { + if (err?.status === 401) { + throw new AuthenticationFailedError({ + message: 'Invalid API key, please get a new one from https://jina.ai' + }); + } + logger.warn(`Failed load remote cached user: ${err}`, { err }); + throw new DownstreamServiceError(`Failed to authenticate: ${err}`); + } + const brief = r?.data; + const draftAccount = JinaEmbeddingsTokenAccount.from({ + ...account, ...brief, _id: this.bearerToken, + lastSyncedAt: new Date() + }); + this.user = draftAccount; + this.uid = this.user?.user_id; + + return draftAccount; + } + + try { + // TODO: go back using validateToken after performance issue fixed + const r = ((account?.wallet?.total_balance || 0) > 0) ? + await this.jinaEmbeddingsDashboard.authorization(this.bearerToken) : + await this.jinaEmbeddingsDashboard.validateToken(this.bearerToken); + const brief = r.data; + const draftAccount = JinaEmbeddingsTokenAccount.from({ + ...account, ...brief, _id: this.bearerToken, + lastSyncedAt: new Date() + }); + await JinaEmbeddingsTokenAccount.save(draftAccount.degradeForFireStore(), undefined, { merge: true }); + + this.user = draftAccount; + this.uid = this.user?.user_id; + + return draftAccount; + } catch (err: any) { + authDtoLogger.warn(`Failed to get user brief: ${err}`, { err: marshalErrorLike(err) }); + + if (err?.status === 401) { + throw new AuthenticationFailedError({ + message: 'Invalid API key, please get a new one from https://jina.ai' + }); + } + + if (account) { + this.user = account; + this.uid = this.user?.user_id; + + return account; + } + + + throw new DownstreamServiceError(`Failed to authenticate: ${err}`); + } + } + + async reportUsage(tokenCount: number, mdl: string, endpoint: string = '/encode') { + const user = await this.assertUser(); + const uid = user.user_id; + user.wallet.total_balance -= tokenCount; + + return this.jinaEmbeddingsDashboard.reportUsage(this.bearerToken!, { + model_name: mdl, + api_endpoint: endpoint, + consumer: { + id: uid, + user_id: uid, + }, + usage: { + total_tokens: tokenCount + }, + labels: { + model_name: mdl + } + }).then((r) => { + JinaEmbeddingsTokenAccount.COLLECTION.doc(this.bearerToken!) + .update({ 'wallet.total_balance': JinaEmbeddingsTokenAccount.OPS.increment(-tokenCount) }) + .catch((err) => { + authDtoLogger.warn(`Failed to update cache for ${uid}: ${err}`, { err: marshalErrorLike(err) }); + }); + + return r; + }).catch((err) => { + user.wallet.total_balance += tokenCount; + authDtoLogger.warn(`Failed to report usage for ${uid}: ${err}`, { err: marshalErrorLike(err) }); + }); + } + + async solveUID() { + if (this.uid) { + this.ctxMgr.set('uid', this.uid); + + return this.uid; + } + + if (this.bearerToken) { + await this.getBrief(); + this.ctxMgr.set('uid', this.uid); + + return this.uid; + } + + return undefined; + } + + async assertUID() { + const uid = await this.solveUID(); + + if (!uid) { + throw new AuthenticationRequiredError('Authentication failed'); + } + + return uid; + } + + async assertUser() { + if (this.user) { + return this.user; + } + + await this.getBrief(); + + return this.user!; + } + + async assertTier(n: number, feature?: string) { + let user; + try { + user = await this.assertUser(); + } catch (err) { + if (err instanceof AuthenticationRequiredError) { + throw new AuthenticationRequiredError({ + message: `Authentication is required to use this feature${feature ? ` (${feature})` : ''}. Please provide a valid API key.` + }); + } + + throw err; + } + + const tier = parseInt(user.metadata?.speed_level); + if (isNaN(tier) || tier < n) { + throw new TierFeatureConstraintError({ + message: `Your current plan does not support this feature${feature ? ` (${feature})` : ''}. Please upgrade your plan.` + }); + } + + return true; + } + + getRateLimits(...tags: string[]) { + const descs = tags.map((x) => this.user?.customRateLimits?.[x] || []).flat().filter((x) => x.isEffective()); + + if (descs.length) { + return descs; + } + + return undefined; + } +} diff --git a/src/dto/turndown-tweakable-options.ts b/src/dto/turndown-tweakable-options.ts new file mode 100644 index 0000000..7c7e8de --- /dev/null +++ b/src/dto/turndown-tweakable-options.ts @@ -0,0 +1,61 @@ +import { AutoCastable, Prop } from 'civkit/civ-rpc'; +import {Context} from '../services/registry'; +import _ from 'lodash'; + + +export class TurnDownTweakableOptions extends AutoCastable { + @Prop({ + desc: 'Turndown options > headingStyle', + type: new Set(['setext', 'atx']), + }) + headingStyle?: 'setext' | 'atx'; + + @Prop({ + desc: 'Turndown options > hr', + validate: (v: string) => v.length > 0 && v.length <= 128 + }) + hr?: string; + + @Prop({ + desc: 'Turndown options > bulletListMarker', + type: new Set(['-', '+', '*']), + }) + bulletListMarker?: '-' | '+' | '*'; + + @Prop({ + desc: 'Turndown options > emDelimiter', + type: new Set(['_', '*']), + }) + emDelimiter?: '_' | '*'; + + @Prop({ + desc: 'Turndown options > strongDelimiter', + type: new Set(['__', '**']), + }) + strongDelimiter?: '__' | '**'; + + @Prop({ + desc: 'Turndown options > linkStyle', + type: new Set(['inlined', 'referenced', 'discarded']), + }) + linkStyle?: 'inlined' | 'referenced' | 'discarded'; + + @Prop({ + desc: 'Turndown options > linkReferenceStyle', + type: new Set(['full', 'collapsed', 'shortcut', 'discarded']), + }) + linkReferenceStyle?: 'full' | 'collapsed' | 'shortcut' | 'discarded'; + + static fromCtx(ctx: Context, prefix= 'x-md-') { + const draft: Record = {}; + for (const [k, v] of Object.entries(ctx.headers)) { + if (k.startsWith(prefix)) { + const prop = k.slice(prefix.length); + const sk = _.camelCase(prop); + draft[sk] = v as string; + } + } + + return this.from(draft); + } +} diff --git a/src/fetch.d.ts b/src/fetch.d.ts new file mode 100644 index 0000000..2bc68c0 --- /dev/null +++ b/src/fetch.d.ts @@ -0,0 +1,13 @@ +declare global { + export const { + fetch, + FormData, + Headers, + Request, + Response, + File, + }: typeof import('undici'); + export type { FormData, Headers, Request, RequestInit, Response, RequestInit, File } from 'undici'; +} + +export { }; diff --git a/src/lib/transform-server-event-stream.ts b/src/lib/transform-server-event-stream.ts new file mode 100644 index 0000000..1f833a7 --- /dev/null +++ b/src/lib/transform-server-event-stream.ts @@ -0,0 +1,169 @@ +import { TPM, parseJSONText } from 'civkit'; +import { Transform, TransformCallback, TransformOptions } from 'stream'; + +export class InputServerEventStream extends Transform { + cache: string[] = []; + + constructor(options?: TransformOptions) { + super({ + ...options, + readableObjectMode: true + }); + } + + decodeRoutine() { + if (!this.cache.length) { + return; + } + + const vecs = this.cache.join('').split(/\r?\n\r?\n/); + this.cache.length = 0; + const lastVec = vecs.pop(); + if (lastVec) { + this.cache.push(lastVec); + } + + for (const x of vecs) { + const lines: string[] = x.split(/\r?\n/); + + const event: { + event?: string; + data?: string; + id?: string; + retry?: number; + } = {}; + + for (const l of lines) { + const columnPos = l.indexOf(':'); + if (columnPos <= 0) { + continue; + } + const key = l.substring(0, columnPos); + const rawValue = l.substring(columnPos + 1); + const value = rawValue.startsWith(' ') ? rawValue.slice(1) : rawValue; + if (key === 'data') { + if (event.data) { + event.data += value || '\n'; + } else if (event.data === '') { + event.data += '\n'; + event.data += value || '\n'; + } else { + event.data = value; + } + } else if (key === 'retry') { + event.retry = parseInt(value, 10); + } else { + Reflect.set(event, key, value); + } + } + + if (event.data) { + const parsed = parseJSONText(event.data); + if (parsed && typeof parsed === 'object') { + event.data = parsed; + } + } + + if (Object.keys(event).length) { + this.push(event); + } + } + } + + override _transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback): void { + if (chunk === null) { + this.push(null); + } + + this.cache.push(chunk.toString()); + this.decodeRoutine(); + + callback(); + } + + override _final(callback: (error?: Error | null | undefined) => void): void { + this.decodeRoutine(); + callback(); + } +} + +@TPM({ + contentType: 'text/event-stream', +}) +export class OutputServerEventStream extends Transform { + n: number = 0; + + constructor(options?: TransformOptions) { + super({ + ...options, writableObjectMode: true, encoding: 'utf-8' + }); + } + + encodeRoutine(chunk: { + event?: string; + data?: any; + id?: string; + retry?: number; + } | string) { + if (typeof chunk === 'object') { + const lines: string[] = []; + + if (chunk.event) { + lines.push(`event: ${chunk.event}`); + } + if (chunk.data) { + if (typeof chunk.data === 'string') { + for (const x of chunk.data.split(/\r?\n/)) { + lines.push(`data: ${x}`); + } + } else { + lines.push(`data: ${JSON.stringify(chunk.data)}`); + } + } + if (chunk.id) { + lines.push(`id: ${chunk.id}`); + } + if (chunk.retry) { + lines.push(`retry: ${chunk.retry}`); + } + if (!lines.length) { + lines.push(`data: ${JSON.stringify(chunk)}`); + } + this.push(lines.join('\n')); + this.push('\n\n'); + this.n++; + + return; + } else if (typeof chunk === 'string') { + const lines: string[] = []; + for (const x of chunk.split(/\r?\n/)) { + lines.push(`data: ${x}`); + } + + this.push(lines.join('\n')); + this.push('\n\n'); + this.n++; + } + } + + override _transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback): void { + if (chunk === null) { + this.push(null); + } + + this.encodeRoutine(chunk); + + callback(); + } +} + +export interface OutputServerEventStream extends Transform { + write(chunk: string | { + event?: string; + data?: any; + id?: string; + retry?: number; + }, callback?: (error: Error | null | undefined) => void): boolean; + write(chunk: any, callback?: (error: Error | null | undefined) => void): boolean; + write(chunk: any, encoding: BufferEncoding, callback?: (error: Error | null | undefined) => void): boolean; +} diff --git a/src/services/alt-text.ts b/src/services/alt-text.ts new file mode 100644 index 0000000..55a5ba1 --- /dev/null +++ b/src/services/alt-text.ts @@ -0,0 +1,134 @@ +import { AssertionFailureError, AsyncService, HashManager } from 'civkit'; +import { singleton } from 'tsyringe'; +import { GlobalLogger } from './logger'; +import { CanvasService } from './canvas'; +import { ImageInterrogationManager } from '../shared/services/common-iminterrogate'; +import { ImgBrief } from './puppeteer'; +import { ImgAlt } from '../db/img-alt'; +import { AsyncLocalContext } from './async-context'; + +const md5Hasher = new HashManager('md5', 'hex'); + +@singleton() +export class AltTextService extends AsyncService { + + altsToIgnore = 'image,img,photo,picture,pic,alt,figure,fig'.split(','); + logger = this.globalLogger.child({ service: this.constructor.name }); + + constructor( + protected globalLogger: GlobalLogger, + protected imageInterrogator: ImageInterrogationManager, + protected canvasService: CanvasService, + protected asyncLocalContext: AsyncLocalContext + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + } + + async caption(url: string) { + try { + const img = await this.canvasService.loadImage(url); + const contentTypeHint = Reflect.get(img, 'contentType'); + if (Math.min(img.naturalHeight, img.naturalWidth) <= 1) { + return `A ${img.naturalWidth}x${img.naturalHeight} image, likely be a tacker probe`; + } + if (Math.min(img.naturalHeight, img.naturalWidth) < 64) { + return `A ${img.naturalWidth}x${img.naturalHeight} small image, likely a logo, icon or avatar`; + } + const resized = this.canvasService.fitImageToSquareBox(img, 1024); + const exported = await this.canvasService.canvasToBuffer(resized, 'image/png'); + + const svgHint = contentTypeHint.includes('svg') ? `Beware this image is a SVG rendered on a gray background, the gray background is not part of the image.\n\n` : ''; + const svgSystemHint = contentTypeHint.includes('svg') ? ` Sometimes the system renders SVG on a gray background. When this happens, you must not include the gray background in the description.` : ''; + + const r = await this.imageInterrogator.interrogate('vertex-gemini-2.0-flash', { + image: exported, + prompt: `${svgHint}Give a concise image caption descriptive sentence in third person. Start directly with the description.`, + system: `You are BLIP2, an image caption model. You will generate Alt Text (in web pages) for any image for a11y purposes. You must not start with "This image is sth...", instead, start direly with "sth..."${svgSystemHint}`, + }); + + return r.replaceAll(/[\n\"]|(\.\s*$)/g, '').trim(); + } catch (err) { + throw new AssertionFailureError({ message: `Could not generate alt text for url ${url}`, cause: err }); + } + } + + async getAltText(imgBrief: ImgBrief) { + if (!imgBrief.src) { + return undefined; + } + if (imgBrief.alt && !this.altsToIgnore.includes(imgBrief.alt.trim().toLowerCase())) { + return imgBrief.alt; + } + const digest = md5Hasher.hash(imgBrief.src); + const shortDigest = Buffer.from(digest, 'hex').toString('base64url'); + let dims: number[] = []; + do { + if (imgBrief.loaded) { + if (imgBrief.naturalWidth && imgBrief.naturalHeight) { + if (Math.min(imgBrief.naturalWidth, imgBrief.naturalHeight) < 64) { + dims = [imgBrief.naturalWidth, imgBrief.naturalHeight]; + break; + } + } + } + + if (imgBrief.width && imgBrief.height) { + if (Math.min(imgBrief.width, imgBrief.height) < 64) { + dims = [imgBrief.width, imgBrief.height]; + break; + } + } + + } while (false); + + if (Math.min(...dims) <= 1) { + return `A ${dims[0]}x${dims[1]} image, likely be a tacker probe`; + } + if (Math.min(...dims) < 64) { + return `A ${dims[0]}x${dims[1]} small image, likely a logo, icon or avatar`; + } + + const existing = await ImgAlt.fromFirestore(shortDigest); + + if (existing) { + return existing.generatedAlt || existing.originalAlt || ''; + } + + let generatedCaption = ''; + + try { + generatedCaption = await this.caption(imgBrief.src); + } catch (err) { + this.logger.warn(`Unable to generate alt text for ${imgBrief.src}`, { err }); + } + + if (this.asyncLocalContext.ctx.DNT) { + // Don't cache alt text if DNT is set + return generatedCaption; + } + + // Don't try again until the next day + const expireMixin = generatedCaption ? {} : { expireAt: new Date(Date.now() + 1000 * 3600 * 24) }; + + await ImgAlt.COLLECTION.doc(shortDigest).set( + { + _id: shortDigest, + src: imgBrief.src || '', + width: imgBrief.naturalWidth || 0, + height: imgBrief.naturalHeight || 0, + urlDigest: digest, + originalAlt: imgBrief.alt || '', + generatedAlt: generatedCaption || '', + createdAt: new Date(), + ...expireMixin + }, { merge: true } + ); + + return generatedCaption; + } +}; diff --git a/src/services/async-context.ts b/src/services/async-context.ts new file mode 100644 index 0000000..f99e506 --- /dev/null +++ b/src/services/async-context.ts @@ -0,0 +1,10 @@ +import { GlobalAsyncContext } from 'civkit/async-context'; +import { container, singleton } from 'tsyringe'; + +@singleton() +export class AsyncLocalContext extends GlobalAsyncContext { } + +const instance = container.resolve(AsyncLocalContext); +Reflect.set(process, 'asyncLocalContext', instance); + +export default instance; diff --git a/src/services/blackhole-detector.ts b/src/services/blackhole-detector.ts new file mode 100644 index 0000000..abe1070 --- /dev/null +++ b/src/services/blackhole-detector.ts @@ -0,0 +1,78 @@ +import { singleton } from 'tsyringe'; +import { AsyncService } from 'civkit/async-service'; +import { GlobalLogger } from './logger'; +import { delay } from 'civkit/timeout'; + + +@singleton() +export class BlackHoleDetector extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + lastWorkedTs?: number; + lastDoneRequestTs?: number; + lastIncomingRequestTs?: number; + + maxDelay = 1000 * 30; + concurrentRequests = 0; + + strikes = 0; + + constructor(protected globalLogger: GlobalLogger) { + super(...arguments); + + if (process.env.NODE_ENV?.startsWith('prod')) { + setInterval(() => { + this.routine(); + }, 1000 * 30).unref(); + } + } + + override async init() { + await this.dependencyReady(); + this.logger.debug('BlackHoleDetector started'); + this.emit('ready'); + } + + async routine() { + // We give routine a 3s grace period for potentially paused CPU to spin up and process some requests + await delay(3000); + const now = Date.now(); + const lastWorked = this.lastWorkedTs; + if (!lastWorked) { + return; + } + const dt = (now - lastWorked); + if (this.concurrentRequests > 1 && + this.lastIncomingRequestTs && lastWorked && + this.lastIncomingRequestTs >= lastWorked && + (dt > (this.maxDelay * (this.strikes + 1))) + ) { + this.logger.warn(`BlackHole detected, last worked: ${Math.ceil(dt / 1000)}s ago, concurrentRequests: ${this.concurrentRequests}`); + this.strikes += 1; + } + + if (this.strikes >= 3) { + this.logger.error(`BlackHole detected for ${this.strikes} strikes, last worked: ${Math.ceil(dt / 1000)}s ago, concurrentRequests: ${this.concurrentRequests}`); + process.nextTick(() => { + this.emit('error', new Error(`BlackHole detected for ${this.strikes} strikes, last worked: ${Math.ceil(dt / 1000)}s ago, concurrentRequests: ${this.concurrentRequests}`)); + // process.exit(1); + }); + } + } + + incomingRequest() { + this.lastIncomingRequestTs = Date.now(); + this.lastWorkedTs ??= Date.now(); + this.concurrentRequests++; + } + doneWithRequest() { + this.concurrentRequests--; + this.lastDoneRequestTs = Date.now(); + } + + itWorked() { + this.lastWorkedTs = Date.now(); + this.strikes = 0; + } + +}; diff --git a/src/services/brave-search.ts b/src/services/brave-search.ts new file mode 100644 index 0000000..8dcfe2c --- /dev/null +++ b/src/services/brave-search.ts @@ -0,0 +1,186 @@ +import { AsyncService, AutoCastable, DownstreamServiceFailureError, Prop, RPC_CALL_ENVIRONMENT, delay, marshalErrorLike } from 'civkit'; +import { singleton } from 'tsyringe'; +import { GlobalLogger } from './logger'; +import { SecretExposer } from '../shared/services/secrets'; +import { BraveSearchHTTP, WebSearchQueryParams } from '../shared/3rd-party/brave-search'; +import { GEOIP_SUPPORTED_LANGUAGES, GeoIPService } from './geoip'; +import { AsyncLocalContext } from './async-context'; +import { WebSearchOptionalHeaderOptions } from '../shared/3rd-party/brave-types'; +import type { Request, Response } from 'express'; +import { BlackHoleDetector } from './blackhole-detector'; + +@singleton() +export class BraveSearchService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + braveSearchHTTP!: BraveSearchHTTP; + + constructor( + protected globalLogger: GlobalLogger, + protected secretExposer: SecretExposer, + protected geoipControl: GeoIPService, + protected threadLocal: AsyncLocalContext, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + + this.braveSearchHTTP = new BraveSearchHTTP(this.secretExposer.BRAVE_SEARCH_API_KEY); + } + + async webSearch(query: WebSearchQueryParams) { + const ip = this.threadLocal.get('ip'); + const extraHeaders: WebSearchOptionalHeaderOptions = {}; + if (ip) { + const geoip = await this.geoipControl.lookupCity(ip, GEOIP_SUPPORTED_LANGUAGES.EN); + + if (geoip?.city) { + extraHeaders['X-Loc-City'] = encodeURIComponent(geoip.city); + } + if (geoip?.country) { + extraHeaders['X-Loc-Country'] = geoip.country.code; + } + if (geoip?.timezone) { + extraHeaders['X-Loc-Timezone'] = geoip.timezone; + } + if (geoip?.coordinates) { + extraHeaders['X-Loc-Lat'] = `${geoip.coordinates[0]}`; + extraHeaders['X-Loc-Long'] = `${geoip.coordinates[1]}`; + } + if (geoip?.subdivisions?.length) { + extraHeaders['X-Loc-State'] = encodeURIComponent(`${geoip.subdivisions[0].code}`); + extraHeaders['X-Loc-State-Name'] = encodeURIComponent(`${geoip.subdivisions[0].name}`); + } + } + if (this.threadLocal.get('userAgent')) { + extraHeaders['User-Agent'] = this.threadLocal.get('userAgent'); + } + + const encoded = { ...query }; + if (encoded.q) { + encoded.q = (Buffer.from(encoded.q).toString('ascii') === encoded.q) ? encoded.q : encodeURIComponent(encoded.q); + } + + let maxTries = 11; + + while (maxTries--) { + try { + const r = await this.braveSearchHTTP.webSearch(encoded, { headers: extraHeaders as Record }); + this.blackHoleDetector.itWorked(); + + return r.parsed; + } catch (err: any) { + this.logger.error(`Web search failed: ${err?.message}`, { err: marshalErrorLike(err) }); + if (err?.status === 429) { + await delay(500 + 1000 * Math.random()); + continue; + } + + throw new DownstreamServiceFailureError({ message: `Search failed` }); + } + } + + throw new DownstreamServiceFailureError({ message: `Search failed` }); + } + +} + + +export class BraveSearchExplicitOperatorsDto extends AutoCastable { + @Prop({ + arrayOf: String, + desc: `Returns web pages with a specific file extension. Example: to find the Honda GX120 Owner’s manual in PDF, type “Honda GX120 ownners manual ext:pdf”.` + }) + ext?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages created in the specified file type. Example: to find a web page created in PDF format about the evaluation of age-related cognitive changes, type “evaluation of age cognitive changes filetype:pdf”.` + }) + filetype?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages containing the specified term in the body of the page. Example: to find information about the Nvidia GeForce GTX 1080 Ti, making sure the page contains the keywords “founders edition” in the body, type “nvidia 1080 ti inbody:“founders edition””.` + }) + inbody?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns webpages containing the specified term in the title of the page. Example: to find pages about SEO conferences making sure the results contain 2023 in the title, type “seo conference intitle:2023”.` + }) + intitle?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns webpages containing the specified term either in the title or in the body of the page. Example: to find pages about the 2024 Oscars containing the keywords “best costume design” in the page, type “oscars 2024 inpage:“best costume design””.` + }) + inpage?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages written in the specified language. The language code must be in the ISO 639-1 two-letter code format. Example: to find information on visas only in Spanish, type “visas lang:es”.` + }) + lang?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages written in the specified language. The language code must be in the ISO 639-1 two-letter code format. Example: to find information on visas only in Spanish, type “visas lang:es”.` + }) + loc?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages coming only from a specific web site. Example: to find information about Goggles only on Brave pages, type “goggles site:brave.com”.` + }) + site?: string | string[]; + + addTo(searchTerm: string) { + const chunks = []; + for (const [key, value] of Object.entries(this)) { + if (value) { + const values = Array.isArray(value) ? value : [value]; + const textValue = values.map((v) => `${key}:${v}`).join(' OR '); + if (textValue) { + chunks.push(textValue); + } + } + } + const opPart = chunks.length > 1 ? chunks.map((x) => `(${x})`).join(' AND ') : chunks; + + if (opPart.length) { + return [searchTerm, opPart].join(' '); + } + + return searchTerm; + } + + static override from(input: any) { + const instance = super.from(input) as BraveSearchExplicitOperatorsDto; + const ctx = Reflect.get(input, RPC_CALL_ENVIRONMENT) as { + req: Request, + res: Response, + } | undefined; + + const params = ['ext', 'filetype', 'inbody', 'intitle', 'inpage', 'lang', 'loc', 'site']; + + for (const p of params) { + const customValue = ctx?.req.get(`x-${p}`) || ctx?.req.get(`${p}`); + if (!customValue) { + continue; + } + + const filtered = customValue.split(', ').filter(Boolean); + if (filtered.length) { + Reflect.set(instance, p, filtered); + } + } + + return instance; + } +} diff --git a/src/services/canvas.ts b/src/services/canvas.ts new file mode 100644 index 0000000..15db6e9 --- /dev/null +++ b/src/services/canvas.ts @@ -0,0 +1,191 @@ +import { singleton, container } from 'tsyringe'; +import { AsyncService, mimeOf, ParamValidationError, SubmittedDataMalformedError, /* downloadFile */ } from 'civkit'; +import { readFile } from 'fs/promises'; + +import type canvas from '@napi-rs/canvas'; +export type { Canvas, Image } from '@napi-rs/canvas'; + +import { GlobalLogger } from './logger'; +import { TempFileManager } from './temp-file'; + +import { isMainThread } from 'worker_threads'; +import type { svg2png } from 'svg2png-wasm' with { 'resolution-mode': 'import' }; +import path from 'path'; +import { Threaded } from './threaded'; + +const downloadFile = async (uri: string) => { + const resp = await fetch(uri); + if (!(resp.ok && resp.body)) { + throw new Error(`Unexpected response ${resp.statusText}`); + } + const contentLength = parseInt(resp.headers.get('content-length') || '0'); + if (contentLength > 1024 * 1024 * 100) { + throw new Error('File too large'); + } + const buff = await resp.arrayBuffer(); + + return { buff, contentType: resp.headers.get('content-type') }; +}; + +@singleton() +export class CanvasService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + svg2png!: typeof svg2png; + canvas!: typeof canvas; + + constructor( + protected temp: TempFileManager, + protected globalLogger: GlobalLogger, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + if (!isMainThread) { + const { createSvg2png, initialize } = require('svg2png-wasm'); + const wasmBuff = await readFile(path.resolve(path.dirname(require.resolve('svg2png-wasm')), '../svg2png_wasm_bg.wasm')); + const fontBuff = await readFile(path.resolve(__dirname, '../../licensed/SourceHanSansSC-Regular.otf')); + await initialize(wasmBuff); + this.svg2png = createSvg2png({ + fonts: [Uint8Array.from(fontBuff)], + defaultFontFamily: { + serifFamily: 'Source Han Sans SC', + sansSerifFamily: 'Source Han Sans SC', + cursiveFamily: 'Source Han Sans SC', + fantasyFamily: 'Source Han Sans SC', + monospaceFamily: 'Source Han Sans SC', + } + }); + } + this.canvas = require('@napi-rs/canvas'); + + this.emit('ready'); + } + + @Threaded() + async renderSvgToPng(svgContent: string,) { + return this.svg2png(svgContent, { backgroundColor: '#D3D3D3' }); + } + + protected async _loadImage(input: string | Buffer) { + let buff; + let contentType; + do { + if (typeof input === 'string') { + if (input.startsWith('data:')) { + const firstComma = input.indexOf(','); + const header = input.slice(0, firstComma); + const data = input.slice(firstComma + 1); + const encoding = header.split(';')[1]; + contentType = header.split(';')[0].split(':')[1]; + if (encoding?.startsWith('base64')) { + buff = Buffer.from(data, 'base64'); + } else { + buff = Buffer.from(decodeURIComponent(data), 'utf-8'); + } + break; + } + if (input.startsWith('http')) { + const r = await downloadFile(input); + buff = Buffer.from(r.buff); + contentType = r.contentType; + break; + } + } + if (Buffer.isBuffer(input)) { + buff = input; + const mime = await mimeOf(buff); + contentType = `${mime.mediaType}/${mime.subType}`; + break; + } + throw new ParamValidationError('Invalid input'); + } while (false); + + if (!buff) { + throw new ParamValidationError('Invalid input'); + } + + if (contentType?.includes('svg')) { + buff = await this.renderSvgToPng(buff.toString('utf-8')); + } + + const img = await this.canvas.loadImage(buff); + Reflect.set(img, 'contentType', contentType); + + return img; + } + + async loadImage(uri: string | Buffer) { + const t0 = Date.now(); + try { + const theImage = await this._loadImage(uri); + const t1 = Date.now(); + this.logger.debug(`Image loaded in ${t1 - t0}ms`); + + return theImage; + } catch (err: any) { + if (err?.message?.includes('Unsupported image type') || err?.message?.includes('unsupported')) { + this.logger.warn(`Failed to load image ${uri.slice(0, 128)}`, { err }); + throw new SubmittedDataMalformedError(`Unknown image format for ${uri.slice(0, 128)}`); + } + throw err; + } + } + + fitImageToSquareBox(image: canvas.Image | canvas.Canvas, size: number = 1024) { + // this.logger.debug(`Fitting image(${ image.width }x${ image.height }) to ${ size } box`); + // const t0 = Date.now(); + if (image.width <= size && image.height <= size) { + if (image instanceof this.canvas.Canvas) { + return image; + } + const canvasInstance = this.canvas.createCanvas(image.width, image.height); + const ctx = canvasInstance.getContext('2d'); + ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvasInstance.width, canvasInstance.height); + // this.logger.debug(`No need to resize, copied to canvas in ${ Date.now() - t0 } ms`); + + return canvasInstance; + } + + const aspectRatio = image.width / image.height; + + const resizedWidth = Math.round(aspectRatio > 1 ? size : size * aspectRatio); + const resizedHeight = Math.round(aspectRatio > 1 ? size / aspectRatio : size); + + const canvasInstance = this.canvas.createCanvas(resizedWidth, resizedHeight); + const ctx = canvasInstance.getContext('2d'); + ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, resizedWidth, resizedHeight); + // this.logger.debug(`Resized to ${ resizedWidth }x${ resizedHeight } in ${ Date.now() - t0 } ms`); + + return canvasInstance; + } + + corpImage(image: canvas.Image | canvas.Canvas, x: number, y: number, w: number, h: number) { + // this.logger.debug(`Cropping image(${ image.width }x${ image.height }) to ${ w }x${ h } at ${ x },${ y } `); + // const t0 = Date.now(); + const canvasInstance = this.canvas.createCanvas(w, h); + const ctx = canvasInstance.getContext('2d'); + ctx.drawImage(image, x, y, w, h, 0, 0, w, h); + // this.logger.debug(`Crop complete in ${ Date.now() - t0 } ms`); + + return canvasInstance; + } + + canvasToDataUrl(canvas: canvas.Canvas, mimeType?: 'image/png' | 'image/jpeg') { + // this.logger.debug(`Exporting canvas(${ canvas.width }x${ canvas.height })`); + // const t0 = Date.now(); + return canvas.toDataURLAsync((mimeType || 'image/png') as 'image/png'); + } + + async canvasToBuffer(canvas: canvas.Canvas, mimeType?: 'image/png' | 'image/jpeg') { + // this.logger.debug(`Exporting canvas(${ canvas.width }x${ canvas.height })`); + // const t0 = Date.now(); + return canvas.toBuffer((mimeType || 'image/png') as 'image/png'); + } + +} + +const instance = container.resolve(CanvasService); +export default instance; diff --git a/src/services/cf-browser-rendering.ts b/src/services/cf-browser-rendering.ts new file mode 100644 index 0000000..5fa54a0 --- /dev/null +++ b/src/services/cf-browser-rendering.ts @@ -0,0 +1,54 @@ +import { container, singleton } from 'tsyringe'; +import { AsyncService } from 'civkit/async-service'; +import { SecretExposer } from '../shared/services/secrets'; +import { GlobalLogger } from './logger'; +import { CloudFlareHTTP } from '../shared/3rd-party/cloud-flare'; +import { HTTPServiceError } from 'civkit/http'; +import { ServiceNodeResourceDrainError } from './errors'; + +@singleton() +export class CFBrowserRendering extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + client!: CloudFlareHTTP; + + constructor( + protected globalLogger: GlobalLogger, + protected secretExposer: SecretExposer, + ) { + super(...arguments); + } + + + override async init() { + await this.dependencyReady(); + const [account, key] = this.secretExposer.CLOUD_FLARE_API_KEY?.split(':'); + this.client = new CloudFlareHTTP(account, key); + + this.emit('ready'); + } + + async fetchContent(url: string) { + try { + const r = await this.client.fetchBrowserRenderedHTML({ url }); + + return r.parsed.result; + } catch (err) { + if (err instanceof HTTPServiceError) { + if (err.status === 429) { + // Rate limit exceeded, return empty result + this.logger.warn('Cloudflare browser rendering rate limit exceeded', { url }); + + throw new ServiceNodeResourceDrainError(`Cloudflare browser rendering (our account) is at capacity, please try again later or switch to another engine.`,); + } + } + + throw err; + } + } + +} + +const instance = container.resolve(CFBrowserRendering); + +export default instance; diff --git a/src/services/curl.ts b/src/services/curl.ts new file mode 100644 index 0000000..fdb7539 --- /dev/null +++ b/src/services/curl.ts @@ -0,0 +1,456 @@ +import { AsyncService } from 'civkit/async-service'; +import { singleton } from 'tsyringe'; + +import { Curl, CurlCode, CurlFeature, HeaderInfo } from 'node-libcurl'; +import { parseString as parseSetCookieString } from 'set-cookie-parser'; + +import { ScrappingOptions } from './puppeteer'; +import { GlobalLogger } from './logger'; +import { AssertionFailureError, FancyFile } from 'civkit'; +import { ServiceBadAttemptError, ServiceBadApproachError } from './errors'; +import { TempFileManager } from '../services/temp-file'; +import { createBrotliDecompress, createInflate, createGunzip } from 'zlib'; +import { ZSTDDecompress } from 'simple-zstd'; +import _ from 'lodash'; +import { Readable } from 'stream'; +import { AsyncLocalContext } from './async-context'; +import { BlackHoleDetector } from './blackhole-detector'; + +export interface CURLScrappingOptions extends ScrappingOptions { + method?: string; + body?: string | Buffer; +} + +@singleton() +export class CurlControl extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + chromeVersion: string = `132`; + safariVersion: string = `537.36`; + platform: string = `Linux`; + ua: string = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/${this.safariVersion} (KHTML, like Gecko) Chrome/${this.chromeVersion}.0.0.0 Safari/${this.safariVersion}`; + + lifeCycleTrack = new WeakMap(); + + constructor( + protected globalLogger: GlobalLogger, + protected tempFileManager: TempFileManager, + protected asyncLocalContext: AsyncLocalContext, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + + if (process.platform === 'darwin') { + this.platform = `macOS`; + } else if (process.platform === 'win32') { + this.platform = `Windows`; + } + + this.emit('ready'); + } + + impersonateChrome(ua: string) { + this.chromeVersion = ua.match(/Chrome\/(\d+)/)![1]; + this.safariVersion = ua.match(/AppleWebKit\/([\d\.]+)/)![1]; + this.ua = ua; + } + + curlImpersonateHeader(curl: Curl, headers?: object) { + let uaPlatform = this.platform; + if (this.ua.includes('Windows')) { + uaPlatform = 'Windows'; + } else if (this.ua.includes('Android')) { + uaPlatform = 'Android'; + } else if (this.ua.includes('iPhone') || this.ua.includes('iPad') || this.ua.includes('iPod')) { + uaPlatform = 'iOS'; + } else if (this.ua.includes('CrOS')) { + uaPlatform = 'Chrome OS'; + } else if (this.ua.includes('Macintosh')) { + uaPlatform = 'macOS'; + } + + const mixinHeaders: Record = { + 'Sec-Ch-Ua': `Not A(Brand";v="8", "Chromium";v="${this.chromeVersion}", "Google Chrome";v="${this.chromeVersion}"`, + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': `"${uaPlatform}"`, + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': this.ua, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Sec-Fetch-Site': 'none', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-User': '?1', + 'Sec-Fetch-Dest': 'document', + 'Accept-Encoding': 'gzip, deflate, br, zstd', + 'Accept-Language': 'en-US,en;q=0.9', + }; + const headersCopy: Record = { ...headers }; + for (const k of Object.keys(mixinHeaders)) { + const lowerK = k.toLowerCase(); + if (headersCopy[lowerK]) { + mixinHeaders[k] = headersCopy[lowerK]; + delete headersCopy[lowerK]; + } + } + Object.assign(mixinHeaders, headersCopy); + + curl.setOpt(Curl.option.HTTPHEADER, Object.entries(mixinHeaders).flatMap(([k, v]) => { + if (Array.isArray(v) && v.length) { + return v.map((v2) => `${k}: ${v2}`); + } + return [`${k}: ${v}`]; + })); + + return curl; + } + + urlToFile1Shot(urlToCrawl: URL, crawlOpts?: CURLScrappingOptions) { + return new Promise<{ + statusCode: number, + statusText?: string, + data?: FancyFile, + headers: HeaderInfo[], + }>((resolve, reject) => { + let contentType = ''; + const curl = new Curl(); + curl.enable(CurlFeature.StreamResponse); + curl.setOpt('URL', urlToCrawl.toString()); + curl.setOpt(Curl.option.FOLLOWLOCATION, false); + curl.setOpt(Curl.option.SSL_VERIFYPEER, false); + curl.setOpt(Curl.option.TIMEOUT_MS, crawlOpts?.timeoutMs || 30_000); + curl.setOpt(Curl.option.CONNECTTIMEOUT_MS, 3_000); + curl.setOpt(Curl.option.LOW_SPEED_LIMIT, 32768); + curl.setOpt(Curl.option.LOW_SPEED_TIME, 5_000); + if (crawlOpts?.method) { + curl.setOpt(Curl.option.CUSTOMREQUEST, crawlOpts.method.toUpperCase()); + } + if (crawlOpts?.body) { + curl.setOpt(Curl.option.POSTFIELDS, crawlOpts.body.toString()); + } + + const headersToSet = { ...crawlOpts?.extraHeaders }; + if (crawlOpts?.cookies?.length) { + const cookieKv: Record = {}; + for (const cookie of crawlOpts.cookies) { + cookieKv[cookie.name] = cookie.value; + } + for (const cookie of crawlOpts.cookies) { + if (cookie.maxAge && cookie.maxAge < 0) { + delete cookieKv[cookie.name]; + continue; + } + if (cookie.expires && cookie.expires < new Date()) { + delete cookieKv[cookie.name]; + continue; + } + if (cookie.secure && urlToCrawl.protocol !== 'https:') { + delete cookieKv[cookie.name]; + continue; + } + if (cookie.domain && !urlToCrawl.hostname.endsWith(cookie.domain)) { + delete cookieKv[cookie.name]; + continue; + } + if (cookie.path && !urlToCrawl.pathname.startsWith(cookie.path)) { + delete cookieKv[cookie.name]; + continue; + } + } + const cookieChunks = Object.entries(cookieKv).map(([k, v]) => `${k}=${encodeURIComponent(v)}`); + headersToSet.cookie ??= cookieChunks.join('; '); + } + if (crawlOpts?.referer) { + headersToSet.referer ??= crawlOpts.referer; + } + if (crawlOpts?.overrideUserAgent) { + headersToSet['user-agent'] ??= crawlOpts.overrideUserAgent; + } + + this.curlImpersonateHeader(curl, headersToSet); + + if (crawlOpts?.proxyUrl) { + const proxyUrlCopy = new URL(crawlOpts.proxyUrl); + curl.setOpt(Curl.option.PROXY, proxyUrlCopy.href); + } + + let curlStream: Readable | undefined; + curl.on('error', (err, errCode) => { + curl.close(); + this.logger.warn(`Curl ${urlToCrawl.origin}: ${err}`, { err, urlToCrawl }); + const err2 = this.digestCurlCode(errCode, err.message) || + new AssertionFailureError(`Failed to access ${urlToCrawl.origin}: ${err.message}`); + err2.cause ??= err; + if (curlStream) { + // For some reason, manually emitting error event is required for curlStream. + curlStream.emit('error', err2); + curlStream.destroy(err2); + } + reject(err2); + }); + curl.setOpt(Curl.option.MAXFILESIZE, 4 * 1024 * 1024 * 1024); // 4GB + let status = -1; + let statusText: string|undefined; + let contentEncoding = ''; + curl.once('end', () => { + if (curlStream) { + curlStream.once('end', () => curl.close()); + return; + } + curl.close(); + }); + curl.on('stream', (stream, statusCode, headers) => { + this.logger.debug(`CURL: [${statusCode}] ${urlToCrawl.origin}`, { statusCode }); + status = statusCode; + curlStream = stream; + for (const headerSet of (headers as HeaderInfo[])) { + for (const [k, v] of Object.entries(headerSet)) { + if (k.trim().endsWith(':')) { + Reflect.set(headerSet, k.slice(0, k.indexOf(':')), v || ''); + Reflect.deleteProperty(headerSet, k); + continue; + } + if (v === undefined) { + Reflect.set(headerSet, k, ''); + continue; + } + if (k.toLowerCase() === 'content-type' && typeof v === 'string') { + contentType = v.toLowerCase(); + } + } + } + const lastResHeaders = headers[headers.length - 1]; + statusText = (lastResHeaders as HeaderInfo).result?.reason; + for (const [k, v] of Object.entries(lastResHeaders)) { + const kl = k.toLowerCase(); + if (kl === 'content-type') { + contentType = (v || '').toLowerCase(); + } + if (kl === 'content-encoding') { + contentEncoding = (v || '').toLowerCase(); + } + if (contentType && contentEncoding) { + break; + } + } + + if ([301, 302, 303, 307, 308].includes(statusCode)) { + if (stream) { + stream.resume(); + } + resolve({ + statusCode: status, + statusText, + data: undefined, + headers: headers as HeaderInfo[], + }); + return; + } + + if (!stream) { + resolve({ + statusCode: status, + statusText, + data: undefined, + headers: headers as HeaderInfo[], + }); + return; + } + + switch (contentEncoding) { + case 'gzip': { + const decompressed = createGunzip(); + stream.pipe(decompressed); + stream.once('error', (err) => { + decompressed.destroy(err); + }); + stream = decompressed; + break; + } + case 'deflate': { + const decompressed = createInflate(); + stream.pipe(decompressed); + stream.once('error', (err) => { + decompressed.destroy(err); + }); + stream = decompressed; + break; + } + case 'br': { + const decompressed = createBrotliDecompress(); + stream.pipe(decompressed); + stream.once('error', (err) => { + decompressed.destroy(err); + }); + stream = decompressed; + break; + } + case 'zstd': { + const decompressed = ZSTDDecompress(); + stream.pipe(decompressed); + stream.once('error', (err) => { + decompressed.destroy(err); + }); + stream = decompressed; + break; + } + default: { + break; + } + } + + const fpath = this.tempFileManager.alloc(); + const fancyFile = FancyFile.auto(stream, fpath); + this.tempFileManager.bindPathTo(fancyFile, fpath); + resolve({ + statusCode: status, + statusText, + data: fancyFile, + headers: headers as HeaderInfo[], + }); + }); + + curl.perform(); + }); + } + + async urlToFile(urlToCrawl: URL, crawlOpts?: CURLScrappingOptions) { + let leftRedirection = 6; + let cookieRedirects = 0; + let opts = { ...crawlOpts }; + let nextHopUrl = urlToCrawl; + const fakeHeaderInfos: HeaderInfo[] = []; + do { + const r = await this.urlToFile1Shot(nextHopUrl, opts); + + if ([301, 302, 303, 307, 308].includes(r.statusCode)) { + fakeHeaderInfos.push(...r.headers); + const headers = r.headers[r.headers.length - 1]; + const location: string | undefined = headers.Location || headers.location; + + const setCookieHeader = headers['Set-Cookie'] || headers['set-cookie']; + if (setCookieHeader) { + const cookieAssignments = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader]; + const parsed = cookieAssignments.filter(Boolean).map((x) => parseSetCookieString(x, { decodeValues: true })); + if (parsed.length) { + opts.cookies = [...(opts.cookies || []), ...parsed]; + } + if (!location) { + cookieRedirects += 1; + } + } + + if (!location && !setCookieHeader) { + // Follow curl behavior + return { + statusCode: r.statusCode, + data: r.data, + headers: fakeHeaderInfos.concat(r.headers), + }; + } + if (!location && cookieRedirects > 1) { + throw new ServiceBadApproachError(`Failed to access ${urlToCrawl}: Browser required to solve complex cookie preconditions.`); + } + + nextHopUrl = new URL(location || '', nextHopUrl); + leftRedirection -= 1; + continue; + } + + return { + statusCode: r.statusCode, + statusText: r.statusText, + data: r.data, + headers: fakeHeaderInfos.concat(r.headers), + }; + } while (leftRedirection > 0); + + throw new ServiceBadAttemptError(`Failed to access ${urlToCrawl}: Too many redirections.`); + } + + async sideLoad(targetUrl: URL, crawlOpts?: CURLScrappingOptions) { + const curlResult = await this.urlToFile(targetUrl, crawlOpts); + this.blackHoleDetector.itWorked(); + let finalURL = targetUrl; + const sideLoadOpts: CURLScrappingOptions['sideLoad'] = { + impersonate: {}, + proxyOrigin: {}, + }; + for (const headers of curlResult.headers) { + sideLoadOpts.impersonate[finalURL.href] = { + status: headers.result?.code || -1, + headers: _.omit(headers, 'result'), + contentType: headers['Content-Type'] || headers['content-type'], + }; + if (crawlOpts?.proxyUrl) { + sideLoadOpts.proxyOrigin[finalURL.origin] = crawlOpts.proxyUrl; + } + if (headers.result?.code && [301, 302, 307, 308].includes(headers.result.code)) { + const location = headers.Location || headers.location; + if (location) { + finalURL = new URL(location, finalURL); + } + } + } + const lastHeaders = curlResult.headers[curlResult.headers.length - 1]; + const contentType = (lastHeaders['Content-Type'] || lastHeaders['content-type'])?.toLowerCase() || (await curlResult.data?.mimeType) || 'application/octet-stream'; + const contentDisposition = lastHeaders['Content-Disposition'] || lastHeaders['content-disposition']; + const fileName = contentDisposition?.match(/filename="([^"]+)"/i)?.[1] || finalURL.pathname.split('/').pop(); + + if (sideLoadOpts.impersonate[finalURL.href] && (await curlResult.data?.size)) { + sideLoadOpts.impersonate[finalURL.href].body = curlResult.data; + } + + // This should keep the file from being garbage collected and deleted until this asyncContext/request is done. + this.lifeCycleTrack.set(this.asyncLocalContext.ctx, curlResult.data); + + return { + finalURL, + sideLoadOpts, + chain: curlResult.headers, + status: curlResult.statusCode, + statusText: curlResult.statusText, + headers: lastHeaders, + contentType, + contentDisposition, + fileName, + file: curlResult.data + }; + } + + digestCurlCode(code: CurlCode, msg: string) { + switch (code) { + // 400 User errors + case CurlCode.CURLE_COULDNT_RESOLVE_HOST: { + return new AssertionFailureError(msg); + } + + // Maybe retry but dont retry with curl again + case CurlCode.CURLE_OPERATION_TIMEDOUT: + case CurlCode.CURLE_UNSUPPORTED_PROTOCOL: + case CurlCode.CURLE_PEER_FAILED_VERIFICATION: { + return new ServiceBadApproachError(msg); + } + + // Retryable errors + case CurlCode.CURLE_REMOTE_ACCESS_DENIED: + case CurlCode.CURLE_SEND_ERROR: + case CurlCode.CURLE_RECV_ERROR: + case CurlCode.CURLE_GOT_NOTHING: + case CurlCode.CURLE_SSL_CONNECT_ERROR: + case CurlCode.CURLE_QUIC_CONNECT_ERROR: + case CurlCode.CURLE_COULDNT_RESOLVE_PROXY: + case CurlCode.CURLE_COULDNT_CONNECT: + case CurlCode.CURLE_PARTIAL_FILE: { + return new ServiceBadAttemptError(msg); + } + + default: { + return undefined; + } + } + } +} diff --git a/src/services/errors.ts b/src/services/errors.ts new file mode 100644 index 0000000..8941ff7 --- /dev/null +++ b/src/services/errors.ts @@ -0,0 +1,48 @@ +import { ApplicationError, StatusCode } from 'civkit/civ-rpc'; +import _ from 'lodash'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); + +@StatusCode(50301) +export class ServiceDisabledError extends ApplicationError { } + +@StatusCode(50302) +export class ServiceCrashedError extends ApplicationError { } + +@StatusCode(50303) +export class ServiceNodeResourceDrainError extends ApplicationError { } + +@StatusCode(50304) +export class ServiceBadAttemptError extends ApplicationError { } + +@StatusCode(50305) +export class ServiceBadApproachError extends ServiceBadAttemptError { } + +@StatusCode(40104) +export class EmailUnverifiedError extends ApplicationError { } + +@StatusCode(40201) +export class InsufficientCreditsError extends ApplicationError { } + +@StatusCode(40202) +export class TierFeatureConstraintError extends ApplicationError { } + +@StatusCode(40203) +export class InsufficientBalanceError extends ApplicationError { } + +@StatusCode(40903) +export class LockConflictError extends ApplicationError { } + +@StatusCode(40904) +export class BudgetExceededError extends ApplicationError { } + +@StatusCode(45101) +export class HarmfulContentError extends ApplicationError { } + +@StatusCode(45102) +export class SecurityCompromiseError extends ApplicationError { } + +@StatusCode(41201) +export class BatchSizeTooLargeError extends ApplicationError { } diff --git a/src/services/finalizer.ts b/src/services/finalizer.ts new file mode 100644 index 0000000..46db29d --- /dev/null +++ b/src/services/finalizer.ts @@ -0,0 +1,39 @@ +import { AbstractFinalizerService } from 'civkit/finalizer'; +import { container, singleton } from 'tsyringe'; +import { isMainThread } from 'worker_threads'; +import { GlobalLogger } from './logger'; + +const realProcessExit = process.exit; +process.exit = ((code?: number) => { + if (isMainThread) { + return; + } + return realProcessExit(code); +}) as typeof process.exit; + +@singleton() +export class FinalizerService extends AbstractFinalizerService { + + container = container; + logger = this.globalLogger.child({ service: this.constructor.name }); + + override quitProcess(code?: string | number | null | undefined): never { + return realProcessExit(code); + } + + constructor(protected globalLogger: GlobalLogger) { + super(...arguments); + } + + override onUnhandledRejection(err: unknown, _triggeringPromise: Promise): void { + this.logger.warn(`Unhandled promise rejection in pid ${process.pid}`, { err }); + } +} + +const instance = container.resolve(FinalizerService); +export const { Finalizer } = instance.decorators(); +export default instance; + +if (isMainThread) { + instance.serviceReady(); +} diff --git a/src/services/geoip.ts b/src/services/geoip.ts new file mode 100644 index 0000000..f6501d7 --- /dev/null +++ b/src/services/geoip.ts @@ -0,0 +1,132 @@ +import { container, singleton } from 'tsyringe'; +import fsp from 'fs/promises'; +import { CityResponse, Reader } from 'maxmind'; +import { AsyncService, AutoCastable, Prop, runOnce } from 'civkit'; +import { GlobalLogger } from './logger'; +import path from 'path'; +import { Threaded } from './threaded'; + +export enum GEOIP_SUPPORTED_LANGUAGES { + EN = 'en', + ZH_CN = 'zh-CN', + JA = 'ja', + DE = 'de', + FR = 'fr', + ES = 'es', + PT_BR = 'pt-BR', + RU = 'ru', +} + +export class GeoIPInfo extends AutoCastable { + @Prop() + code?: string; + + @Prop() + name?: string; +} + +export class GeoIPCountryInfo extends GeoIPInfo { + @Prop() + eu?: boolean; +} + +export class GeoIPCityResponse extends AutoCastable { + @Prop() + continent?: GeoIPInfo; + + @Prop() + country?: GeoIPCountryInfo; + + @Prop({ + arrayOf: GeoIPInfo + }) + subdivisions?: GeoIPInfo[]; + + @Prop() + city?: string; + + @Prop({ + arrayOf: Number + }) + coordinates?: [number, number, number]; + + @Prop() + timezone?: string; +} + +@singleton() +export class GeoIPService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + mmdbCity!: Reader; + + constructor( + protected globalLogger: GlobalLogger, + ) { + super(...arguments); + } + + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + @runOnce() + async _lazyload() { + const mmdpPath = path.resolve(__dirname, '..', '..', 'licensed', 'GeoLite2-City.mmdb'); + + const dbBuff = await fsp.readFile(mmdpPath, { flag: 'r', encoding: null }); + + this.mmdbCity = new Reader(dbBuff); + + this.logger.info(`Loaded GeoIP database, ${dbBuff.byteLength} bytes`); + } + + + @Threaded() + async lookupCity(ip: string, lang: GEOIP_SUPPORTED_LANGUAGES = GEOIP_SUPPORTED_LANGUAGES.EN) { + await this._lazyload(); + + const r = this.mmdbCity.get(ip); + + if (!r) { + return undefined; + } + + return GeoIPCityResponse.from({ + continent: r.continent ? { + code: r.continent?.code, + name: r.continent?.names?.[lang] || r.continent?.names?.en, + } : undefined, + country: r.country ? { + code: r.country?.iso_code, + name: r.country?.names?.[lang] || r.country?.names.en, + eu: r.country?.is_in_european_union, + } : undefined, + city: r.city?.names?.[lang] || r.city?.names?.en, + subdivisions: r.subdivisions?.map((x) => ({ + code: x.iso_code, + name: x.names?.[lang] || x.names?.en, + })), + coordinates: r.location ? [ + r.location.latitude, r.location.longitude, r.location.accuracy_radius + ] : undefined, + timezone: r.location?.time_zone, + }); + } + + @Threaded() + async lookupCities(ips: string[], lang: GEOIP_SUPPORTED_LANGUAGES = GEOIP_SUPPORTED_LANGUAGES.EN) { + const r = (await Promise.all(ips.map((ip) => this.lookupCity(ip, lang)))).filter(Boolean) as GeoIPCityResponse[]; + + return r; + } + +} + +const instance = container.resolve(GeoIPService); + +export default instance; diff --git a/src/services/jsdom.ts b/src/services/jsdom.ts new file mode 100644 index 0000000..fa97125 --- /dev/null +++ b/src/services/jsdom.ts @@ -0,0 +1,390 @@ +import { container, singleton } from 'tsyringe'; +import { GlobalLogger } from './logger'; +import { ExtendedSnapshot, ImgBrief, PageSnapshot } from './puppeteer'; +import { Readability } from '@mozilla/readability'; +import TurndownService from 'turndown'; +import { Threaded } from '../services/threaded'; +import type { ExtraScrappingOptions } from '../api/crawler'; +import { tailwindClasses } from '../utils/tailwind-classes'; +import { countGPTToken } from '../shared/utils/openai'; +import { AsyncService } from 'civkit/async-service'; +import { ApplicationError, AssertionFailureError } from 'civkit/civ-rpc'; + +const pLinkedom = import('linkedom'); + +@singleton() +export class JSDomControl extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + linkedom!: Awaited; + + constructor( + protected globalLogger: GlobalLogger, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.linkedom = await pLinkedom; + this.emit('ready'); + } + + async narrowSnapshot(snapshot: PageSnapshot | undefined, options?: ExtraScrappingOptions) { + if (snapshot?.parsed && !options?.targetSelector && !options?.removeSelector && !options?.withIframe && !options?.withShadowDom) { + return snapshot; + } + if (!snapshot?.html) { + return snapshot; + } + + try { + // SideLoad contains native objects that cannot go through thread boundaries. + return await this.actualNarrowSnapshot(snapshot, { ...options, sideLoad: undefined }); + } catch (err: any) { + this.logger.warn(`Error narrowing snapshot`, { err }); + if (err instanceof ApplicationError) { + throw err; + } + + throw new AssertionFailureError(`Failed to process the page: ${err?.message}`); + } + } + + @Threaded() + async actualNarrowSnapshot(snapshot: PageSnapshot, options?: ExtraScrappingOptions): Promise { + const t0 = Date.now(); + let sourceHTML = snapshot.html; + if (options?.withShadowDom && snapshot.shadowExpanded) { + sourceHTML = snapshot.shadowExpanded; + } + let jsdom = this.linkedom.parseHTML(sourceHTML); + if (!jsdom.window.document.documentElement) { + jsdom = this.linkedom.parseHTML(`${sourceHTML}`); + } + const allNodes: Node[] = []; + jsdom.window.document.querySelectorAll('svg').forEach((x) => x.innerHTML = ''); + if (options?.withIframe) { + jsdom.window.document.querySelectorAll('iframe[src],frame[src]').forEach((x) => { + const src = x.getAttribute('src'); + const thisSnapshot = snapshot.childFrames?.find((f) => f.href === src); + if (options?.withIframe === 'quoted') { + const blockquoteElem = jsdom.window.document.createElement('blockquote'); + const preElem = jsdom.window.document.createElement('pre'); + preElem.innerHTML = thisSnapshot?.text || ''; + blockquoteElem.appendChild(preElem); + x.replaceWith(blockquoteElem); + } else if (thisSnapshot?.html) { + x.innerHTML = thisSnapshot.html; + x.querySelectorAll('script, style').forEach((s) => s.remove()); + if (src) { + x.querySelectorAll('[src]').forEach((el) => { + const imgSrc = el.getAttribute('src')!; + if (URL.canParse(imgSrc, src!)) { + el.setAttribute('src', new URL(imgSrc, src!).toString()); + } + }); + x.querySelectorAll('[href]').forEach((el) => { + const linkHref = el.getAttribute('href')!; + if (URL.canParse(linkHref, src!)) { + el.setAttribute('href', new URL(linkHref, src!).toString()); + } + }); + } + } + }); + } + + if (Array.isArray(options?.removeSelector)) { + for (const rl of options!.removeSelector) { + jsdom.window.document.querySelectorAll(rl).forEach((x) => x.remove()); + } + } else if (options?.removeSelector) { + jsdom.window.document.querySelectorAll(options.removeSelector).forEach((x) => x.remove()); + } + + let bewareTargetContentDoesNotExist = false; + if (Array.isArray(options?.targetSelector)) { + bewareTargetContentDoesNotExist = true; + for (const x of options!.targetSelector.map((x) => jsdom.window.document.querySelectorAll(x))) { + x.forEach((el) => { + if (!allNodes.includes(el)) { + allNodes.push(el); + } + }); + } + } else if (options?.targetSelector) { + bewareTargetContentDoesNotExist = true; + jsdom.window.document.querySelectorAll(options.targetSelector).forEach((el) => { + if (!allNodes.includes(el)) { + allNodes.push(el); + } + }); + } else { + allNodes.push(jsdom.window.document); + } + + if (!allNodes.length) { + + if (bewareTargetContentDoesNotExist) { + return undefined; + } + + return snapshot; + } + const textNodes: HTMLElement[] = []; + let rootDoc: Document; + if (allNodes.length === 1 && allNodes[0].nodeName === '#document' && (allNodes[0] as any).documentElement) { + rootDoc = allNodes[0] as any; + if (rootDoc.body?.innerText) { + textNodes.push(rootDoc.body); + } + } else { + rootDoc = this.linkedom.parseHTML('').window.document; + for (const n of allNodes) { + rootDoc.body.appendChild(n); + rootDoc.body.appendChild(rootDoc.createTextNode('\n\n')); + if ((n as HTMLElement).innerText) { + textNodes.push(n as HTMLElement); + } + } + } + const textChunks = textNodes.map((x) => { + const clone = x.cloneNode(true) as HTMLElement; + clone.querySelectorAll('script,style,link,svg').forEach((s) => s.remove()); + + return clone.innerText; + }); + + let parsed; + try { + parsed = new Readability(rootDoc.cloneNode(true) as any).parse(); + } catch (err: any) { + this.logger.warn(`Failed to parse selected element`, { err }); + } + + const imgSet = new Set(); + const rebuiltImgs: ImgBrief[] = []; + Array.from(rootDoc.querySelectorAll('img[src],img[data-src]')) + .map((x: any) => [x.getAttribute('src'), x.getAttribute('data-src'), x.getAttribute('alt')]) + .forEach(([u1, u2, alt]) => { + let absUrl: string | undefined; + if (u1) { + try { + const u1Txt = new URL(u1, snapshot.rebase || snapshot.href).toString(); + imgSet.add(u1Txt); + absUrl = u1Txt; + } catch (err) { + // void 0; + } + } + if (u2) { + try { + const u2Txt = new URL(u2, snapshot.rebase || snapshot.href).toString(); + imgSet.add(u2Txt); + absUrl = u2Txt; + } catch (err) { + // void 0; + } + } + if (absUrl) { + rebuiltImgs.push({ + src: absUrl, + alt + }); + } + }); + + const r = { + ...snapshot, + title: snapshot.title || jsdom.window.document.title, + description: snapshot.description || + (jsdom.window.document.head?.querySelector('meta[name="description"]')?.getAttribute('content') ?? ''), + parsed, + html: rootDoc.documentElement.outerHTML, + text: textChunks.join('\n'), + imgs: (snapshot.imgs || rebuiltImgs)?.filter((x) => imgSet.has(x.src)) || [], + } as PageSnapshot; + + const dt = Date.now() - t0; + if (dt > 1000) { + this.logger.warn(`Performance issue: Narrowing snapshot took ${dt}ms`, { url: snapshot.href, dt }); + } + + return r; + } + + @Threaded() + async inferSnapshot(snapshot: PageSnapshot) { + const t0 = Date.now(); + const extendedSnapshot = { ...snapshot } as ExtendedSnapshot; + try { + const jsdom = this.linkedom.parseHTML(snapshot.html); + + jsdom.window.document.querySelectorAll('svg').forEach((x) => x.innerHTML = ''); + const links = Array.from(jsdom.window.document.querySelectorAll('a[href]')) + .map((x: any) => [x.textContent.replace(/\s+/g, ' ').trim(), x.getAttribute('href'),]) + .map(([text, href]) => { + if (!href) { + return undefined; + } + try { + const parsed = new URL(href, snapshot.rebase || snapshot.href); + + return [text, parsed.toString()] as const; + } catch (err) { + return undefined; + } + }) + .filter(Boolean) as [string, string][]; + + extendedSnapshot.links = links; + + const imgs = Array.from(jsdom.window.document.querySelectorAll('img[src],img[data-src]')) + .map((x: any) => { + let linkPreferredSrc = x.getAttribute('src') || ''; + if (linkPreferredSrc.startsWith('data:')) { + const dataSrc = x.getAttribute('data-src') || ''; + if (dataSrc && !dataSrc.startsWith('data:')) { + linkPreferredSrc = dataSrc; + } + } + + return { + src: new URL(linkPreferredSrc, snapshot.rebase || snapshot.href).toString(), + width: parseInt(x.getAttribute('width') || '0'), + height: parseInt(x.getAttribute('height') || '0'), + alt: x.getAttribute('alt') || x.getAttribute('title'), + }; + }); + + extendedSnapshot.imgs = imgs as any; + } catch (_err) { + void 0; + } + + const dt = Date.now() - t0; + if (dt > 1000) { + this.logger.warn(`Performance issue: Inferring snapshot took ${dt}ms`, { url: snapshot.href, dt }); + } + + return extendedSnapshot; + } + + cleanRedundantEmptyLines(text: string) { + const lines = text.split(/\r?\n/g); + const mappedFlag = lines.map((line) => Boolean(line.trim())); + + return lines.filter((_line, i) => mappedFlag[i] || mappedFlag[i - 1]).join('\n'); + } + + @Threaded() + async cleanHTMLforLMs(sourceHTML: string, ...discardSelectors: string[]): Promise { + const t0 = Date.now(); + let jsdom = this.linkedom.parseHTML(sourceHTML); + if (!jsdom.window.document.documentElement) { + jsdom = this.linkedom.parseHTML(`${sourceHTML}`); + } + + for (const rl of discardSelectors) { + jsdom.window.document.querySelectorAll(rl).forEach((x) => x.remove()); + } + + jsdom.window.document.querySelectorAll('img[src],img[data-src]').forEach((x) => { + const src = x.getAttribute('src') || x.getAttribute('data-src'); + if (src?.startsWith('data:')) { + x.setAttribute('src', 'blob:opaque'); + } + x.removeAttribute('data-src'); + x.removeAttribute('srcset'); + }); + + jsdom.window.document.querySelectorAll('[class]').forEach((x) => { + const classes = x.getAttribute('class')?.split(/\s+/g) || []; + const newClasses = classes.filter((c) => !tailwindClasses.has(c)); + x.setAttribute('class', newClasses.join(' ')); + }); + jsdom.window.document.querySelectorAll('[style]').forEach((x) => { + const style = x.getAttribute('style')?.toLocaleLowerCase() || ''; + if (style.startsWith('display: none')) { + return; + } + x.removeAttribute('style'); + }); + const treeWalker = jsdom.window.document.createTreeWalker( + jsdom.window.document, // Start from the root document + 0x80 // Only show comment nodes + ); + + let currentNode; + while ((currentNode = treeWalker.nextNode())) { + currentNode.parentNode?.removeChild(currentNode); // Remove each comment node + } + + jsdom.window.document.querySelectorAll('*').forEach((x) => { + const attrs = x.getAttributeNames(); + for (const attr of attrs) { + if (attr.startsWith('data-') || attr.startsWith('aria-')) { + x.removeAttribute(attr); + } + } + }); + + const final = this.cleanRedundantEmptyLines(jsdom.window.document.documentElement.outerHTML); + + const dt = Date.now() - t0; + if (dt > 1000) { + this.logger.warn(`Performance issue: Cleaning HTML for LMs took ${dt}ms`, { dt }); + } + + return final; + } + + snippetToElement(snippet?: string, url?: string) { + const parsed = this.linkedom.parseHTML(snippet || ''); + + // Hack for turndown gfm table plugin. + parsed.window.document.querySelectorAll('table').forEach((x) => { + Object.defineProperty(x, 'rows', { value: Array.from(x.querySelectorAll('tr')), enumerable: true }); + }); + Object.defineProperty(parsed.window.document.documentElement, 'cloneNode', { + value: function () { return this; }, + }); + + return parsed.window.document.documentElement; + } + + runTurndown(turndownService: TurndownService, html: TurndownService.Node | string) { + const t0 = Date.now(); + + try { + return turndownService.turndown(html); + } finally { + const dt = Date.now() - t0; + if (dt > 1000) { + this.logger.warn(`Performance issue: Turndown took ${dt}ms`, { dt }); + } + } + } + + @Threaded() + async analyzeHTMLTextLite(sourceHTML: string) { + let jsdom = this.linkedom.parseHTML(sourceHTML); + if (!jsdom.window.document.documentElement) { + jsdom = this.linkedom.parseHTML(`${sourceHTML}`); + } + jsdom.window.document.querySelectorAll('script,style,link,svg').forEach((s) => s.remove()); + const text = jsdom.window.document.body.innerText || ''; + + return { + title: jsdom.window.document.title, + text, + tokens: countGPTToken(text.replaceAll(/[\s\r\n\t]+/g, ' ')), + }; + } +} + +const jsdomControl = container.resolve(JSDomControl); + +export default jsdomControl; diff --git a/src/services/lm.ts b/src/services/lm.ts new file mode 100644 index 0000000..8bb95c3 --- /dev/null +++ b/src/services/lm.ts @@ -0,0 +1,152 @@ +import { AsyncService } from 'civkit/async-service'; +import { singleton } from 'tsyringe'; + +import { PageSnapshot } from './puppeteer'; +import { GlobalLogger } from './logger'; +import _ from 'lodash'; +import { AssertionFailureError } from 'civkit'; +import { LLMManager } from '../shared/services/common-llm'; +import { JSDomControl } from './jsdom'; + +const tripleBackTick = '```'; + +@singleton() +export class LmControl extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + constructor( + protected globalLogger: GlobalLogger, + protected commonLLM: LLMManager, + protected jsdomControl: JSDomControl, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + async* geminiFromBrowserSnapshot(snapshot?: PageSnapshot & { + pageshotUrl?: string, + }) { + const pageshot = snapshot?.pageshotUrl || snapshot?.pageshot; + + if (!pageshot) { + throw new AssertionFailureError('Screenshot of the page is not available'); + } + + const html = await this.jsdomControl.cleanHTMLforLMs(snapshot.html, 'script,link,style,textarea,select>option,svg'); + + const it = this.commonLLM.iterRun('vertex-gemini-1.5-flash-002', { + prompt: [ + `HTML: \n${html}\n\nSCREENSHOT: \n`, + typeof pageshot === 'string' ? new URL(pageshot) : pageshot, + `Convert this webpage into a markdown source file that does not contain HTML tags, retaining the page language and visual structures.`, + ], + + options: { + system: 'You are ReaderLM-v7, a model that generates Markdown source files only. No HTML, notes and chit-chats allowed', + stream: true + } + }); + + const chunks: string[] = []; + for await (const txt of it) { + chunks.push(txt); + const output: PageSnapshot = { + ...snapshot, + parsed: { + ...snapshot?.parsed, + textContent: chunks.join(''), + } + }; + yield output; + } + + return; + } + + async* readerLMMarkdownFromSnapshot(snapshot?: PageSnapshot) { + if (!snapshot) { + throw new AssertionFailureError('Snapshot of the page is not available'); + } + + const html = await this.jsdomControl.cleanHTMLforLMs(snapshot.html, 'script,link,style,textarea,select>option,svg'); + + const it = this.commonLLM.iterRun('readerlm-v2', { + prompt: `Extract the main content from the given HTML and convert it to Markdown format.\n\n${tripleBackTick}html\n${html}\n${tripleBackTick}\n`, + + options: { + // system: 'You are an AI assistant developed by VENDOR_NAME', + stream: true, + modelSpecific: { + top_k: 1, + temperature: 0, + repetition_penalty: 1.13, + presence_penalty: 0.25, + frequency_penalty: 0.25, + max_tokens: 8192, + } + }, + maxTry: 1, + }); + + const chunks: string[] = []; + for await (const txt of it) { + chunks.push(txt); + const output: PageSnapshot = { + ...snapshot, + parsed: { + ...snapshot?.parsed, + textContent: chunks.join(''), + } + }; + yield output; + } + + return; + } + + async* readerLMFromSnapshot(schema?: string, instruction: string = 'Infer useful information from the HTML and present it in a structured JSON object.', snapshot?: PageSnapshot) { + if (!snapshot) { + throw new AssertionFailureError('Snapshot of the page is not available'); + } + + const html = await this.jsdomControl.cleanHTMLforLMs(snapshot.html, 'script,link,style,textarea,select>option,svg'); + + const it = this.commonLLM.iterRun('readerlm-v2', { + prompt: `${instruction}\n\n${tripleBackTick}html\n${html}\n${tripleBackTick}\n${schema ? `The JSON schema:\n${tripleBackTick}json\n${schema}\n${tripleBackTick}\n` : ''}`, + options: { + // system: 'You are an AI assistant developed by VENDOR_NAME', + stream: true, + modelSpecific: { + top_k: 1, + temperature: 0, + repetition_penalty: 1.13, + presence_penalty: 0.25, + frequency_penalty: 0.25, + max_tokens: 8192, + } + }, + maxTry: 1, + }); + + const chunks: string[] = []; + for await (const txt of it) { + chunks.push(txt); + const output: PageSnapshot = { + ...snapshot, + parsed: { + ...snapshot?.parsed, + textContent: chunks.join(''), + } + }; + yield output; + } + + return; + } +} diff --git a/src/services/logger.ts b/src/services/logger.ts new file mode 100644 index 0000000..a930e9b --- /dev/null +++ b/src/services/logger.ts @@ -0,0 +1,57 @@ +import { AbstractPinoLogger } from 'civkit/pino-logger'; +import { singleton, container } from 'tsyringe'; +import { threadId } from 'node:worker_threads'; +import { getTraceCtx } from 'civkit/async-context'; + + +const levelToSeverityMap: { [k: string]: string | undefined; } = { + trace: 'DEFAULT', + debug: 'DEBUG', + info: 'INFO', + warn: 'WARNING', + error: 'ERROR', + fatal: 'CRITICAL', +}; + +@singleton() +export class GlobalLogger extends AbstractPinoLogger { + loggerOptions = { + level: 'debug', + base: { + tid: threadId, + } + }; + + override init(): void { + if (process.env['NODE_ENV']?.startsWith('prod')) { + super.init(process.stdout); + } else { + const PinoPretty = require('pino-pretty').PinoPretty; + super.init(PinoPretty({ + singleLine: true, + colorize: true, + messageFormat(log: any, messageKey: any) { + return `${log['tid'] ? `[${log['tid']}]` : ''}[${log['service'] || 'ROOT'}] ${log[messageKey]}`; + }, + })); + } + + + this.emit('ready'); + } + + override log(...args: any[]) { + const [levelObj, ...rest] = args; + const severity = levelToSeverityMap[levelObj?.level]; + const traceCtx = getTraceCtx(); + const patched: any= { ...levelObj, severity }; + const traceId = traceCtx?.googleTraceId || traceCtx?.traceId; + if (traceId && process.env['GCLOUD_PROJECT']) { + patched['logging.googleapis.com/trace'] = `projects/${process.env['GCLOUD_PROJECT']}/traces/${traceId}`; + } + return super.log(patched, ...rest); + } +} + +const instance = container.resolve(GlobalLogger); +export default instance; diff --git a/src/services/minimal-stealth.js b/src/services/minimal-stealth.js new file mode 100644 index 0000000..2068945 --- /dev/null +++ b/src/services/minimal-stealth.js @@ -0,0 +1,599 @@ + + +export function minimalStealth() { + /** + * A set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces. + * + * Meant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser). + * + * Note: If for whatever reason you need to use this outside of `puppeteer-extra`: + * Just remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context. + * + * Alternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities. + * + */ + const utils = {}; + + // const toStringRedirects = new WeakMap(); + + utils.init = () => { + utils.preloadCache(); + // const handler = { + // apply: function (target, ctx) { + // // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""` + // if (ctx === Function.prototype.toString) { + // return utils.makeNativeString('toString'); + // } + + // const originalObj = toStringRedirects.get(ctx); + + // // `toString` targeted at our proxied Object detected + // if (originalObj) { + // const fallback = () => + // originalObj && originalObj.name + // ? utils.makeNativeString(originalObj.name) + // : utils.makeNativeString(ctx.name); + + // // Return the toString representation of our original object if possible + // return originalObj + '' || fallback(); + // } + + // if (typeof ctx === 'undefined' || ctx === null) { + // return target.call(ctx); + // } + + // // Check if the toString protype of the context is the same as the global prototype, + // // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case + // const hasSameProto = Object.getPrototypeOf( + // Function.prototype.toString + // ).isPrototypeOf(ctx.toString); // eslint-disable-line no-prototype-builtins + // if (!hasSameProto) { + // // Pass the call on to the local Function.prototype.toString instead + // return ctx.toString(); + // } + + // return target.call(ctx); + // } + // }; + + // const toStringProxy = new Proxy( + // Function.prototype.toString, + // utils.stripProxyFromErrors(handler) + // ); + // utils.replaceProperty(Function.prototype, 'toString', { + // value: toStringProxy + // }); + }; + + /** + * Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw. + * + * The presence of a JS Proxy can be revealed as it shows up in error stack traces. + * + * @param {object} handler - The JS Proxy handler to wrap + */ + utils.stripProxyFromErrors = (handler = {}) => { + const newHandler = { + setPrototypeOf: function (target, proto) { + if (proto === null) + throw new TypeError('Cannot convert object to primitive value'); + if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) { + throw new TypeError('Cyclic __proto__ value'); + } + return Reflect.setPrototypeOf(target, proto); + } + }; + // We wrap each trap in the handler in a try/catch and modify the error stack if they throw + const traps = Object.getOwnPropertyNames(handler); + traps.forEach(trap => { + newHandler[trap] = function () { + try { + // Forward the call to the defined proxy handler + return handler[trap].call(this, ...(arguments || [])); + } catch (err) { + // Stack traces differ per browser, we only support chromium based ones currently + if (!err || !err.stack || !err.stack.includes(`at `)) { + throw err; + } + + // When something throws within one of our traps the Proxy will show up in error stacks + // An earlier implementation of this code would simply strip lines with a blacklist, + // but it makes sense to be more surgical here and only remove lines related to our Proxy. + // We try to use a known "anchor" line for that and strip it with everything above it. + // If the anchor line cannot be found for some reason we fall back to our blacklist approach. + + const stripWithBlacklist = (stack, stripFirstLine = true) => { + const blacklist = [ + `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply + `at Object.${trap} `, // e.g. Object.get or Object.apply + `at Object.newHandler. [as ${trap}] ` // caused by this very wrapper :-) + ]; + return ( + err.stack + .split('\n') + // Always remove the first (file) line in the stack (guaranteed to be our proxy) + .filter((line, index) => !(index === 1 && stripFirstLine)) + // Check if the line starts with one of our blacklisted strings + .filter(line => !blacklist.some(bl => line.trim().startsWith(bl))) + .join('\n') + ); + }; + + const stripWithAnchor = (stack, anchor) => { + const stackArr = stack.split('\n'); + anchor = anchor || `at Object.newHandler. [as ${trap}] `; // Known first Proxy line in chromium + const anchorIndex = stackArr.findIndex(line => + line.trim().startsWith(anchor) + ); + if (anchorIndex === -1) { + return false; // 404, anchor not found + } + // Strip everything from the top until we reach the anchor line + // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`) + stackArr.splice(1, anchorIndex); + return stackArr.join('\n'); + }; + + // Special cases due to our nested toString proxies + err.stack = err.stack.replace( + 'at Object.toString (', + 'at Function.toString (' + ); + if ((err.stack || '').includes('at Function.toString (')) { + err.stack = stripWithBlacklist(err.stack, false); + throw err; + } + + // Try using the anchor method, fallback to blacklist if necessary + err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack); + + throw err; // Re-throw our now sanitized error + } + }; + }); + return newHandler; + }; + + /** + * Strip error lines from stack traces until (and including) a known line the stack. + * + * @param {object} err - The error to sanitize + * @param {string} anchor - The string the anchor line starts with + */ + utils.stripErrorWithAnchor = (err, anchor) => { + const stackArr = err.stack.split('\n'); + const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor)); + if (anchorIndex === -1) { + return err; // 404, anchor not found + } + // Strip everything from the top until we reach the anchor line (remove anchor line as well) + // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`) + stackArr.splice(1, anchorIndex); + err.stack = stackArr.join('\n'); + return err; + }; + + /** + * Replace the property of an object in a stealthy way. + * + * Note: You also want to work on the prototype of an object most often, + * as you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)). + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty + * + * @example + * replaceProperty(WebGLRenderingContext.prototype, 'getParameter', { value: "alice" }) + * // or + * replaceProperty(Object.getPrototypeOf(navigator), 'languages', { get: () => ['en-US', 'en'] }) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The property name to replace + * @param {object} descriptorOverrides - e.g. { value: "alice" } + */ + utils.replaceProperty = (obj, propName, descriptorOverrides = {}) => { + return Object.defineProperty(obj, propName, { + // Copy over the existing descriptors (writable, enumerable, configurable, etc) + ...(Object.getOwnPropertyDescriptor(obj, propName) || {}), + // Add our overrides (e.g. value, get()) + ...descriptorOverrides + }); + }; + + /** + * Preload a cache of function copies and data. + * + * For a determined enough observer it would be possible to overwrite and sniff usage of functions + * we use in our internal Proxies, to combat that we use a cached copy of those functions. + * + * Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before, + * by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups). + * + * This is evaluated once per execution context (e.g. window) + */ + utils.preloadCache = () => { + if (utils.cache) { + return; + } + utils.cache = { + // Used in our proxies + Reflect: { + get: Reflect.get.bind(Reflect), + apply: Reflect.apply.bind(Reflect) + }, + // Used in `makeNativeString` + nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }` + }; + }; + + /** + * Utility function to generate a cross-browser `toString` result representing native code. + * + * There's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings. + * To future-proof this we use an existing native toString result as the basis. + * + * The only advantage we have over the other team is that our JS runs first, hence we cache the result + * of the native toString result once, so they cannot spoof it afterwards and reveal that we're using it. + * + * @example + * makeNativeString('foobar') // => `function foobar() { [native code] }` + * + * @param {string} [name] - Optional function name + */ + utils.makeNativeString = (name = '') => { + return utils.cache.nativeToStringStr.replace('toString', name || ''); + }; + + /** + * Helper function to modify the `toString()` result of the provided object. + * + * Note: Use `utils.redirectToString` instead when possible. + * + * There's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object. + * If no string is provided we will generate a `[native code]` thing based on the name of the property object. + * + * @example + * patchToString(WebGLRenderingContext.prototype.getParameter, 'function getParameter() { [native code] }') + * + * @param {object} obj - The object for which to modify the `toString()` representation + * @param {string} str - Optional string used as a return value + */ + utils.patchToString = (obj, str = '') => { + // toStringRedirects.set(obj, str); + Object.defineProperty(obj, 'toString', { + value: ()=> str, + enumerable: false, + writable: true, + configurable: true, + }); + }; + + /** + * Make all nested functions of an object native. + * + * @param {object} obj + */ + utils.patchToStringNested = (obj = {}) => { + return utils.execRecursively(obj, ['function'], utils.patchToString); + }; + + /** + * Redirect toString requests from one object to another. + * + * @param {object} proxyObj - The object that toString will be called on + * @param {object} originalObj - The object which toString result we wan to return + */ + utils.redirectToString = (proxyObj, originalObj) => { + // toStringRedirects.set(proxyObj, originalObj); + Object.defineProperty(proxyObj, 'toString', { + value: ()=> originalObj.toString(), + enumerable: false, + writable: true, + configurable: true, + }); + }; + + + /** + * All-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps. + * + * Will stealthify these aspects (strip error stack traces, redirect toString, etc). + * Note: This is meant to modify native Browser APIs and works best with prototype objects. + * + * @example + * replaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace + * @param {object} handler - The JS Proxy handler to use + */ + utils.replaceWithProxy = (obj, propName, handler) => { + const originalObj = obj[propName]; + const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler)); + + utils.replaceProperty(obj, propName, { value: proxyObj }); + utils.redirectToString(proxyObj, originalObj); + + return true; + }; + /** + * All-in-one method to replace a getter with a JS Proxy using the provided Proxy handler with traps. + * + * @example + * replaceGetterWithProxy(Object.getPrototypeOf(navigator), 'vendor', proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace + * @param {object} handler - The JS Proxy handler to use + */ + utils.replaceGetterWithProxy = (obj, propName, handler) => { + const fn = Object.getOwnPropertyDescriptor(obj, propName).get; + const fnStr = fn.toString(); // special getter function string + const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler)); + + utils.replaceProperty(obj, propName, { get: proxyObj }); + utils.patchToString(proxyObj, fnStr); + + return true; + }; + + /** + * All-in-one method to replace a getter and/or setter. Functions get and set + * of handler have one more argument that contains the native function. + * + * @example + * replaceGetterSetter(HTMLIFrameElement.prototype, 'contentWindow', handler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace + * @param {object} handlerGetterSetter - The handler with get and/or set + * functions + * @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description + */ + utils.replaceGetterSetter = (obj, propName, handlerGetterSetter) => { + const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName); + const handler = { ...ownPropertyDescriptor }; + + if (handlerGetterSetter.get !== undefined) { + const nativeFn = ownPropertyDescriptor.get; + handler.get = function () { + return handlerGetterSetter.get.call(this, nativeFn.bind(this)); + }; + utils.redirectToString(handler.get, nativeFn); + } + + if (handlerGetterSetter.set !== undefined) { + const nativeFn = ownPropertyDescriptor.set; + handler.set = function (newValue) { + handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this)); + }; + utils.redirectToString(handler.set, nativeFn); + } + + Object.defineProperty(obj, propName, handler); + }; + + /** + * All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps. + * + * Will stealthify these aspects (strip error stack traces, redirect toString, etc). + * + * @example + * mockWithProxy(chrome.runtime, 'sendMessage', function sendMessage() {}, proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace or create + * @param {object} pseudoTarget - The JS Proxy target to use as a basis + * @param {object} handler - The JS Proxy handler to use + */ + utils.mockWithProxy = (obj, propName, pseudoTarget, handler) => { + const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler)); + + utils.replaceProperty(obj, propName, { value: proxyObj }); + utils.patchToString(proxyObj); + + return true; + }; + + /** + * All-in-one method to create a new JS Proxy with stealth tweaks. + * + * This is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property. + * + * Will stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc). + * + * @example + * createProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy + * + * @param {object} pseudoTarget - The JS Proxy target to use as a basis + * @param {object} handler - The JS Proxy handler to use + */ + utils.createProxy = (pseudoTarget, handler) => { + const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler)); + utils.patchToString(proxyObj); + + return proxyObj; + }; + + /** + * Helper function to split a full path to an Object into the first part and property. + * + * @example + * splitObjPath(`HTMLMediaElement.prototype.canPlayType`) + * // => {objName: "HTMLMediaElement.prototype", propName: "canPlayType"} + * + * @param {string} objPath - The full path to an object as dot notation string + */ + utils.splitObjPath = objPath => ({ + // Remove last dot entry (property) ==> `HTMLMediaElement.prototype` + objName: objPath.split('.').slice(0, -1).join('.'), + // Extract last dot entry ==> `canPlayType` + propName: objPath.split('.').slice(-1)[0] + }); + + /** + * Convenience method to replace a property with a JS Proxy using the provided objPath. + * + * Supports a full path (dot notation) to the object as string here, in case that makes it easier. + * + * @example + * replaceObjPathWithProxy('WebGLRenderingContext.prototype.getParameter', proxyHandler) + * + * @param {string} objPath - The full path to an object (dot notation string) to replace + * @param {object} handler - The JS Proxy handler to use + */ + utils.replaceObjPathWithProxy = (objPath, handler) => { + const { objName, propName } = utils.splitObjPath(objPath); + const obj = eval(objName); // eslint-disable-line no-eval + return utils.replaceWithProxy(obj, propName, handler); + }; + + /** + * Traverse nested properties of an object recursively and apply the given function on a whitelist of value types. + * + * @param {object} obj + * @param {array} typeFilter - e.g. `['function']` + * @param {Function} fn - e.g. `utils.patchToString` + */ + utils.execRecursively = (obj = {}, typeFilter = [], fn) => { + function recurse(obj) { + for (const key in obj) { + if (obj[key] === undefined) { + continue; + } + if (obj[key] && typeof obj[key] === 'object') { + recurse(obj[key]); + } else { + if (obj[key] && typeFilter.includes(typeof obj[key])) { + fn.call(this, obj[key]); + } + } + } + } + recurse(obj); + return obj; + }; + + /** + * Everything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one. + * That means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter. + * + * Unfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process. + * This utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings. + * + * We use this to pass down our utility functions as well as any other functions (to be able to split up code better). + * + * @see utils.materializeFns + * + * @param {object} fnObj - An object containing functions as properties + */ + utils.stringifyFns = (fnObj = { hello: () => 'world' }) => { + // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine + // https://github.com/feross/fromentries + function fromEntries(iterable) { + return [...iterable].reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {}); + } + return (Object.fromEntries || fromEntries)( + Object.entries(fnObj) + .filter(([key, value]) => typeof value === 'function') + .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval + ); + }; + + /** + * Utility function to reverse the process of `utils.stringifyFns`. + * Will materialize an object with stringified functions (supports classic and fat arrow functions). + * + * @param {object} fnStrObj - An object containing stringified functions as properties + */ + utils.materializeFns = (fnStrObj = { hello: "() => 'world'" }) => { + return Object.fromEntries( + Object.entries(fnStrObj).map(([key, value]) => { + if (value.startsWith('function')) { + // some trickery is needed to make oldschool functions work :-) + return [key, eval(`() => ${value}`)()]; // eslint-disable-line no-eval + } else { + // arrow functions just work + return [key, eval(value)]; // eslint-disable-line no-eval + } + }) + ); + }; + + // Proxy handler templates for re-usability + utils.makeHandler = () => ({ + // Used by simple `navigator` getter evasions + getterValue: value => ({ + apply(target, ctx, args) { + // Let's fetch the value first, to trigger and escalate potential errors + // Illegal invocations like `navigator.__proto__.vendor` will throw here + utils.cache.Reflect.apply(...arguments); + return value; + } + }) + }); + + /** + * Compare two arrays. + * + * @param {array} array1 - First array + * @param {array} array2 - Second array + */ + utils.arrayEquals = (array1, array2) => { + if (array1.length !== array2.length) { + return false; + } + for (let i = 0; i < array1.length; ++i) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; + }; + + /** + * Cache the method return according to its arguments. + * + * @param {Function} fn - A function that will be cached + */ + utils.memoize = fn => { + const cache = []; + return function (...args) { + if (!cache.some(c => utils.arrayEquals(c.key, args))) { + cache.push({ key: args, value: fn.apply(this, args) }); + } + return cache.find(c => utils.arrayEquals(c.key, args)).value; + }; + }; + + utils.init(); + + const getParameterProxyHandler = { + apply: function (target, ctx, args) { + const param = (args || [])[0] + const result = utils.cache.Reflect.apply(target, ctx, args) + // UNMASKED_VENDOR_WEBGL + if (param === 37445) { + return 'Intel Inc.' // default in headless: Google Inc. + } + // UNMASKED_RENDERER_WEBGL + if (param === 37446) { + return 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader + } + return result + } + } + + // There's more than one WebGL rendering context + // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility + // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter) + const addProxy = (obj, propName) => { + utils.replaceWithProxy(obj, propName, getParameterProxyHandler) + } + // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing: + addProxy(WebGLRenderingContext.prototype, 'getParameter') + addProxy(WebGL2RenderingContext.prototype, 'getParameter') + +} \ No newline at end of file diff --git a/src/services/misc.ts b/src/services/misc.ts new file mode 100644 index 0000000..82ea5b5 --- /dev/null +++ b/src/services/misc.ts @@ -0,0 +1,107 @@ +import { singleton } from 'tsyringe'; +import { AsyncService } from 'civkit/async-service'; +import { ParamValidationError } from 'civkit/civ-rpc'; +import { SecurityCompromiseError } from '../shared/lib/errors'; +import { isIP } from 'node:net'; +import { isIPInNonPublicRange } from '../utils/ip'; +import { GlobalLogger } from './logger'; +import { lookup } from 'node:dns/promises'; +import { Threaded } from './threaded'; + +const normalizeUrl = require('@esm2cjs/normalize-url').default; + +@singleton() +export class MiscService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + constructor( + protected globalLogger: GlobalLogger, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + @Threaded() + async assertNormalizedUrl(input: string) { + let result: URL; + try { + result = new URL( + normalizeUrl( + input, + { + stripWWW: false, + removeTrailingSlash: false, + removeSingleSlash: false, + sortQueryParameters: false, + } + ) + ); + } catch (err) { + throw new ParamValidationError({ + message: `${err}`, + path: 'url' + }); + } + + if (!['http:', 'https:', 'blob:'].includes(result.protocol)) { + throw new ParamValidationError({ + message: `Invalid protocol ${result.protocol}`, + path: 'url' + }); + } + + const normalizedHostname = result.hostname.startsWith('[') ? result.hostname.slice(1, -1) : result.hostname; + let ips: string[] = []; + const isIp = isIP(normalizedHostname); + if (isIp) { + ips.push(normalizedHostname); + } + if ( + (result.hostname === 'localhost') || + (isIp && isIPInNonPublicRange(normalizedHostname)) + ) { + this.logger.warn(`Suspicious action: Request to localhost or non-public IP: ${normalizedHostname}`, { href: result.href }); + throw new SecurityCompromiseError({ + message: `Suspicious action: Request to localhost or non-public IP: ${normalizedHostname}`, + path: 'url' + }); + } + if (!isIp && result.protocol !== 'blob:') { + const resolved = await lookup(result.hostname, { all: true }).catch((err) => { + if (err.code === 'ENOTFOUND') { + return Promise.reject(new ParamValidationError({ + message: `Domain '${result.hostname}' could not be resolved`, + path: 'url' + })); + } + + return; + }); + if (resolved) { + for (const x of resolved) { + if (isIPInNonPublicRange(x.address)) { + this.logger.warn(`Suspicious action: Domain resolved to non-public IP: ${result.hostname} => ${x.address}`, { href: result.href, ip: x.address }); + throw new SecurityCompromiseError({ + message: `Suspicious action: Domain resolved to non-public IP: ${x.address}`, + path: 'url' + }); + } + ips.push(x.address); + } + + } + } + + return { + url: result, + ips + }; + } + +} \ No newline at end of file diff --git a/src/services/pdf-extract.ts b/src/services/pdf-extract.ts new file mode 100644 index 0000000..e1bbfaa --- /dev/null +++ b/src/services/pdf-extract.ts @@ -0,0 +1,378 @@ +import { singleton } from 'tsyringe'; +import _ from 'lodash'; +import { TextItem } from 'pdfjs-dist/types/src/display/api'; +import { AssertionFailureError, AsyncService, HashManager } from 'civkit'; +import { GlobalLogger } from './logger'; +import { PDFContent } from '../db/pdf'; +import dayjs from 'dayjs'; +import { FirebaseStorageBucketControl } from '../shared/services/firebase-storage-bucket'; +import { randomUUID } from 'crypto'; +import type { PDFDocumentLoadingTask } from 'pdfjs-dist'; +import path from 'path'; +import { AsyncLocalContext } from './async-context'; +const utc = require('dayjs/plugin/utc'); // Import the UTC plugin +dayjs.extend(utc); // Extend dayjs with the UTC plugin +const timezone = require('dayjs/plugin/timezone'); +dayjs.extend(timezone); + +const pPdfjs = import('pdfjs-dist/legacy/build/pdf.mjs'); +const nodeCmapUrl = path.resolve(require.resolve('pdfjs-dist'), '../../cmaps') + '/'; + +const md5Hasher = new HashManager('md5', 'hex'); + +function stdDev(numbers: number[]) { + const mean = _.mean(numbers); + const squareDiffs = numbers.map((num) => Math.pow(num - mean, 2)); + const avgSquareDiff = _.mean(squareDiffs); + return Math.sqrt(avgSquareDiff); +} + +function isRotatedByAtLeast35Degrees(transform?: [number, number, number, number, number, number]): boolean { + if (!transform) { + return false; + } + const [a, b, c, d, _e, _f] = transform; + + // Calculate the rotation angles using arctan(b/a) and arctan(-c/d) + const angle1 = Math.atan2(b, a) * (180 / Math.PI); // from a, b + const angle2 = Math.atan2(-c, d) * (180 / Math.PI); // from c, d + + // Either angle1 or angle2 can be used to determine the rotation, they should be equivalent + const rotationAngle1 = Math.abs(angle1); + const rotationAngle2 = Math.abs(angle2); + + // Check if the absolute rotation angle is greater than or equal to 35 degrees + return rotationAngle1 >= 35 || rotationAngle2 >= 35; +} + +@singleton() +export class PDFExtractor extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + pdfjs!: Awaited; + + cacheRetentionMs = 1000 * 3600 * 24 * 7; + + constructor( + protected globalLogger: GlobalLogger, + protected firebaseObjectStorage: FirebaseStorageBucketControl, + protected asyncLocalContext: AsyncLocalContext, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.pdfjs = await pPdfjs; + + this.emit('ready'); + } + + isDataUrl(url: string) { + return url.startsWith('data:'); + } + + parseDataUrl(url: string) { + const protocol = url.slice(0, url.indexOf(':')); + const contentType = url.slice(url.indexOf(':') + 1, url.indexOf(';')); + const data = url.slice(url.indexOf(',') + 1); + if (protocol !== 'data' || !data) { + throw new Error('Invalid data URL'); + } + + if (contentType !== 'application/pdf') { + throw new Error('Invalid data URL type'); + } + + return { + type: contentType, + data: data + }; + } + + async extract(url: string | URL) { + let loadingTask: PDFDocumentLoadingTask; + + if (typeof url === 'string' && this.isDataUrl(url)) { + const { data } = this.parseDataUrl(url); + const binary = Uint8Array.from(Buffer.from(data, 'base64')); + loadingTask = this.pdfjs.getDocument({ + data: binary, + disableFontFace: true, + verbosity: 0, + cMapUrl: nodeCmapUrl, + }); + } else { + loadingTask = this.pdfjs.getDocument({ + url, + disableFontFace: true, + verbosity: 0, + cMapUrl: nodeCmapUrl, + }); + } + + + const doc = await loadingTask.promise; + const meta = await doc.getMetadata(); + + const textItems: TextItem[][] = []; + + for (const pg of _.range(0, doc.numPages)) { + const page = await doc.getPage(pg + 1); + const textContent = await page.getTextContent({ includeMarkedContent: true }); + textItems.push((textContent.items as TextItem[])); + } + + const articleCharHeights: number[] = []; + for (const textItem of textItems.flat()) { + if (textItem.height) { + articleCharHeights.push(...Array(textItem.str.length).fill(textItem.height)); + } + } + const articleAvgHeight = _.mean(articleCharHeights); + const articleStdDevHeight = stdDev(articleCharHeights); + // const articleMedianHeight = articleCharHeights.sort()[Math.floor(articleCharHeights.length / 2)]; + const mdOps: Array<{ + text: string; + op?: 'new' | 'append'; + mode: 'h1' | 'h2' | 'p' | 'appendix' | 'space'; + }> = []; + + const rawChunks: string[] = []; + + let op: 'append' | 'new' = 'new'; + let mode: 'h1' | 'h2' | 'p' | 'space' | 'appendix' = 'p'; + for (const pageTextItems of textItems) { + const charHeights = []; + for (const textItem of pageTextItems as TextItem[]) { + if (textItem.height) { + charHeights.push(...Array(textItem.str.length).fill(textItem.height)); + } + rawChunks.push(`${textItem.str}${textItem.hasEOL ? '\n' : ''}`); + } + + const avgHeight = _.mean(charHeights); + const stdDevHeight = stdDev(charHeights); + // const medianHeight = charHeights.sort()[Math.floor(charHeights.length / 2)]; + + for (const textItem of pageTextItems) { + if (textItem.height > articleAvgHeight + 3 * articleStdDevHeight) { + mode = 'h1'; + } else if (textItem.height > articleAvgHeight + 2 * articleStdDevHeight) { + mode = 'h2'; + } else if (textItem.height && textItem.height < avgHeight - stdDevHeight) { + mode = 'appendix'; + } else if (textItem.height) { + mode = 'p'; + } else { + mode = 'space'; + } + + if (isRotatedByAtLeast35Degrees(textItem.transform as any)) { + mode = 'appendix'; + } + + mdOps.push({ + op, + mode, + text: textItem.str + }); + + if (textItem.hasEOL && !textItem.str) { + op = 'new'; + } else { + op = 'append'; + } + } + } + + const mdChunks = []; + const appendixChunks = []; + mode = 'space'; + for (const x of mdOps) { + const previousMode: string = mode; + const changeToMdChunks = []; + + const isNewStart = x.mode !== 'space' && (x.op === 'new' || (previousMode === 'appendix' && x.mode !== previousMode)); + + if (isNewStart) { + switch (x.mode) { + case 'h1': { + changeToMdChunks.push(`\n\n# `); + mode = x.mode; + break; + } + + case 'h2': { + changeToMdChunks.push(`\n\n## `); + mode = x.mode; + break; + } + + case 'p': { + changeToMdChunks.push(`\n\n`); + mode = x.mode; + break; + } + + case 'appendix': { + mode = x.mode; + appendixChunks.push(`\n\n`); + break; + } + + default: { + break; + } + } + } else { + if (x.mode === 'appendix' && appendixChunks.length) { + const lastChunk = appendixChunks[appendixChunks.length - 1]; + if (!lastChunk.match(/(\s+|-)$/) && lastChunk.length !== 1) { + appendixChunks.push(' '); + } + } else if (mdChunks.length) { + const lastChunk = mdChunks[mdChunks.length - 1]; + if (!lastChunk.match(/(\s+|-)$/) && lastChunk.length !== 1) { + changeToMdChunks.push(' '); + } + } + } + + if (x.text) { + if (x.mode == 'appendix') { + if (appendixChunks.length || isNewStart) { + appendixChunks.push(x.text); + } else { + changeToMdChunks.push(x.text); + } + } else { + changeToMdChunks.push(x.text); + } + } + + if (isNewStart && x.mode !== 'appendix' && appendixChunks.length) { + const appendix = appendixChunks.join('').split(/\r?\n/).map((x) => x.trim()).filter(Boolean).map((x) => `> ${x}`).join('\n'); + changeToMdChunks.unshift(appendix); + changeToMdChunks.unshift(`\n\n`); + appendixChunks.length = 0; + } + + if (x.mode === 'space' && changeToMdChunks.length) { + changeToMdChunks.length = 1; + } + if (changeToMdChunks.length) { + mdChunks.push(...changeToMdChunks); + } + } + + if (mdChunks.length) { + mdChunks[0] = mdChunks[0].trimStart(); + } + + return { meta: meta.info as Record, content: mdChunks.join(''), text: rawChunks.join('') }; + } + + async cachedExtract(url: string, cacheTolerance: number = 1000 * 3600 * 24, alternativeUrl?: string) { + if (!url) { + return undefined; + } + let nameUrl = alternativeUrl || url; + const digest = md5Hasher.hash(nameUrl); + + if (this.isDataUrl(url)) { + nameUrl = `blob://pdf:${digest}`; + } + + const cache: PDFContent | undefined = nameUrl.startsWith('blob:') ? undefined : + (await PDFContent.fromFirestoreQuery(PDFContent.COLLECTION.where('urlDigest', '==', digest).orderBy('createdAt', 'desc').limit(1)))?.[0]; + + if (cache) { + const age = Date.now() - cache?.createdAt.valueOf(); + const stale = cache.createdAt.valueOf() < (Date.now() - cacheTolerance); + this.logger.info(`${stale ? 'Stale cache exists' : 'Cache hit'} for PDF ${nameUrl}, normalized digest: ${digest}, ${age}ms old, tolerance ${cacheTolerance}ms`, { + data: url, url: nameUrl, digest, age, stale, cacheTolerance + }); + + if (!stale) { + if (cache.content && cache.text) { + return { + meta: cache.meta, + content: cache.content, + text: cache.text + }; + } + + try { + const r = await this.firebaseObjectStorage.downloadFile(`pdfs/${cache._id}`); + let cached = JSON.parse(r.toString('utf-8')); + + return { + meta: cached.meta, + content: cached.content, + text: cached.text + }; + } catch (err) { + this.logger.warn(`Unable to load cached content for ${nameUrl}`, { err }); + + return undefined; + } + } + } + + let extracted; + + try { + extracted = await this.extract(url); + } catch (err: any) { + this.logger.warn(`Unable to extract from pdf ${nameUrl}`, { err, url, nameUrl }); + throw new AssertionFailureError(`Unable to process ${nameUrl} as pdf: ${err?.message}`); + } + + if (!this.asyncLocalContext.ctx.DNT && !nameUrl.startsWith('blob:')) { + const theID = randomUUID(); + await this.firebaseObjectStorage.saveFile(`pdfs/${theID}`, + Buffer.from(JSON.stringify(extracted), 'utf-8'), { contentType: 'application/json' }); + PDFContent.save( + PDFContent.from({ + _id: theID, + src: nameUrl, + meta: extracted?.meta || {}, + urlDigest: digest, + createdAt: new Date(), + expireAt: new Date(Date.now() + this.cacheRetentionMs) + }).degradeForFireStore() + ).catch((r) => { + this.logger.warn(`Unable to cache PDF content for ${nameUrl}`, { err: r }); + }); + } + + return extracted; + } + + parsePdfDate(pdfDate: string | undefined) { + if (!pdfDate) { + return undefined; + } + // Remove the 'D:' prefix + const cleanedDate = pdfDate.slice(2); + + // Define the format without the timezone part first + const dateTimePart = cleanedDate.slice(0, 14); + const timezonePart = cleanedDate.slice(14); + + // Construct the full date string in a standard format + const formattedDate = `${dateTimePart}${timezonePart.replace("'", "").replace("'", "")}`; + + // Parse the date with timezone + const parsedDate = dayjs(formattedDate, "YYYYMMDDHHmmssZ"); + + const date = parsedDate.toDate(); + + if (!date.valueOf()) { + return undefined; + } + + return date; + } +} diff --git a/src/services/pseudo-transfer.ts b/src/services/pseudo-transfer.ts new file mode 100644 index 0000000..45a2c76 --- /dev/null +++ b/src/services/pseudo-transfer.ts @@ -0,0 +1,65 @@ +import { marshalErrorLike } from 'civkit'; +import { AbstractPseudoTransfer, SYM_PSEUDO_TRANSFERABLE } from 'civkit/pseudo-transfer'; +import { container, singleton } from 'tsyringe'; + + +@singleton() +export class PseudoTransfer extends AbstractPseudoTransfer { + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + } + +} + +const instance = container.resolve(PseudoTransfer); + +Object.defineProperty(Error.prototype, SYM_PSEUDO_TRANSFERABLE, { + value: function () { + const prototype = this; + return { + copyOwnProperty: 'all', + marshall: (input: Error) => marshalErrorLike(input), + unMarshall: (input: object) => { + Object.setPrototypeOf(input, prototype); + return input; + }, + }; + }, + enumerable: false, +}); +instance.expectPseudoTransferableType(Error); +for (const x of [...Object.values(require('./errors')), ...Object.values(require('civkit/civ-rpc'))]) { + if (typeof x === 'function' && x.prototype instanceof Error) { + instance.expectPseudoTransferableType(x as any); + } +} + + +Object.defineProperty(URL.prototype, SYM_PSEUDO_TRANSFERABLE, { + value: function () { + return { + copyOwnProperty: 'none', + marshall: (input: URL) => ({ href: input.href }), + unMarshall: (input: { href: string; }) => new URL(input.href), + }; + }, + enumerable: false, +}); +instance.expectPseudoTransferableType(URL); + +Object.defineProperty(Buffer.prototype, SYM_PSEUDO_TRANSFERABLE, { + value: function () { + return { + copyOwnProperty: 'none', + unMarshall: (input: Uint8Array | Buffer) => Buffer.isBuffer(input) ? input : Buffer.from(input), + marshall: (input: Uint8Array | Buffer) => input, + }; + }, + enumerable: false, +}); +instance.expectPseudoTransferableType(Buffer); + + +export default instance; diff --git a/src/services/puppeteer.ts b/src/services/puppeteer.ts new file mode 100644 index 0000000..3b0eb5a --- /dev/null +++ b/src/services/puppeteer.ts @@ -0,0 +1,1335 @@ +import _ from 'lodash'; +import { isIP } from 'net'; +import { readFile } from 'fs/promises'; +import fs from 'fs'; +import { container, singleton } from 'tsyringe'; + +import type { Browser, CookieParam, GoToOptions, HTTPRequest, HTTPResponse, Page, Viewport } from 'puppeteer'; +import type { Cookie } from 'set-cookie-parser'; +import puppeteer, { TimeoutError } from 'puppeteer'; + +import { Defer, Deferred } from 'civkit/defer'; +import { AssertionFailureError, ParamValidationError } from 'civkit/civ-rpc'; +import { AsyncService } from 'civkit/async-service'; +import { FancyFile } from 'civkit/fancy-file'; +import { delay } from 'civkit/timeout'; + +import { SecurityCompromiseError, ServiceCrashedError, ServiceNodeResourceDrainError } from '../shared/lib/errors'; +import { CurlControl } from './curl'; +import { BlackHoleDetector } from './blackhole-detector'; +import { AsyncLocalContext } from './async-context'; +import { GlobalLogger } from './logger'; +import { minimalStealth } from './minimal-stealth'; +const tldExtract = require('tld-extract'); + +const READABILITY_JS = fs.readFileSync(require.resolve('@mozilla/readability/Readability.js'), 'utf-8'); + + +export interface ImgBrief { + src: string; + loaded?: boolean; + width?: number; + height?: number; + naturalWidth?: number; + naturalHeight?: number; + alt?: string; +} + +export interface ReadabilityParsed { + title: string; + content: string; + textContent: string; + length: number; + excerpt: string; + byline: string; + dir: string; + siteName: string; + lang: string; + publishedTime: string; +} + +export interface PageSnapshot { + title: string; + description?: string; + href: string; + rebase?: string; + html: string; + htmlSignificantlyModifiedByJs?: boolean; + shadowExpanded?: string; + text: string; + status?: number; + statusText?: string; + parsed?: Partial | null; + screenshot?: Buffer; + pageshot?: Buffer; + imgs?: ImgBrief[]; + pdfs?: string[]; + maxElemDepth?: number; + elemCount?: number; + childFrames?: PageSnapshot[]; + isIntermediate?: boolean; + isFromCache?: boolean; + lastMutationIdle?: number; + lastContentResourceLoaded?: number; + lastMediaResourceLoaded?: number; +} + +export interface ExtendedSnapshot extends PageSnapshot { + links: [string, string][]; + imgs: ImgBrief[]; +} + +export interface ScrappingOptions { + proxyUrl?: string; + cookies?: Cookie[]; + favorScreenshot?: boolean; + waitForSelector?: string | string[]; + minIntervalMs?: number; + overrideUserAgent?: string; + timeoutMs?: number; + locale?: string; + referer?: string; + extraHeaders?: Record; + injectFrameScripts?: string[]; + injectPageScripts?: string[]; + viewport?: Viewport; + proxyResources?: boolean; + + sideLoad?: { + impersonate: { + [url: string]: { + status: number; + headers: { [k: string]: string | string[]; }; + contentType?: string; + body?: FancyFile; + }; + }; + proxyOrigin: { [origin: string]: string; }; + }; + +} + +const SIMULATE_SCROLL = ` +(function () { + function createIntersectionObserverEntry(target, isIntersecting, timestamp) { + const targetRect = target.getBoundingClientRect(); + const record = { + target, + isIntersecting, + time: timestamp, + // If intersecting, intersectionRect matches boundingClientRect + // If not intersecting, intersectionRect is empty (0x0) + intersectionRect: isIntersecting + ? targetRect + : new DOMRectReadOnly(0, 0, 0, 0), + // Current bounding client rect of the target + boundingClientRect: targetRect, + // Intersection ratio is either 0 (not intersecting) or 1 (fully intersecting) + intersectionRatio: isIntersecting ? 1 : 0, + // Root bounds (viewport in our case) + rootBounds: new DOMRectReadOnly( + 0, + 0, + window.innerWidth, + window.innerHeight + ) + }; + Object.setPrototypeOf(record, window.IntersectionObserverEntry.prototype); + return record; + } + function cloneIntersectionObserverEntry(entry) { + const record = { + target: entry.target, + isIntersecting: entry.isIntersecting, + time: entry.time, + intersectionRect: entry.intersectionRect, + boundingClientRect: entry.boundingClientRect, + intersectionRatio: entry.intersectionRatio, + rootBounds: entry.rootBounds + }; + Object.setPrototypeOf(record, window.IntersectionObserverEntry.prototype); + return record; + } + const orig = window.IntersectionObserver; + const kCallback = Symbol('callback'); + const kLastEntryMap = Symbol('lastEntryMap'); + const liveObservers = new Map(); + class MangledIntersectionObserver extends orig { + constructor(callback, options) { + super((entries, observer) => { + const lastEntryMap = observer[kLastEntryMap]; + const lastEntry = entries[entries.length - 1]; + lastEntryMap.set(lastEntry.target, lastEntry); + return callback(entries, observer); + }, options); + this[kCallback] = callback; + this[kLastEntryMap] = new WeakMap(); + liveObservers.set(this, new Set()); + } + disconnect() { + liveObservers.get(this)?.clear(); + liveObservers.delete(this); + return super.disconnect(); + } + observe(target) { + const observer = liveObservers.get(this); + observer?.add(target); + return super.observe(target); + } + unobserve(target) { + const observer = liveObservers.get(this); + observer?.delete(target); + return super.unobserve(target); + } + } + Object.defineProperty(MangledIntersectionObserver, 'name', { value: 'IntersectionObserver', writable: false }); + window.IntersectionObserver = MangledIntersectionObserver; + function simulateScroll() { + for (const [observer, targets] of liveObservers.entries()) { + const t0 = performance.now(); + for (const target of targets) { + const entry = createIntersectionObserverEntry(target, true, t0); + observer[kCallback]([entry], observer); + setTimeout(() => { + const t1 = performance.now(); + const lastEntry = observer[kLastEntryMap].get(target); + if (!lastEntry) { + return; + } + const entry2 = { ...cloneIntersectionObserverEntry(lastEntry), time: t1 }; + observer[kCallback]([entry2], observer); + }); + } + } + } + window.simulateScroll = simulateScroll; +})(); +`; + +const MUTATION_IDLE_WATCH = ` +(function () { + let timeout; + const sendMsg = ()=> { + document.dispatchEvent(new CustomEvent('mutationIdle')); + }; + + const cb = () => { + if (timeout) { + clearTimeout(timeout); + timeout = setTimeout(sendMsg, 200); + } + }; + const mutationObserver = new MutationObserver(cb); + + document.addEventListener('DOMContentLoaded', () => { + mutationObserver.observe(document.documentElement, { + childList: true, + subtree: true, + }); + timeout = setTimeout(sendMsg, 200); + }, { once: true }) +})(); +`; + +const SCRIPT_TO_INJECT_INTO_FRAME = ` +${READABILITY_JS} +${SIMULATE_SCROLL} +${MUTATION_IDLE_WATCH} +(${minimalStealth.toString()})(); + +(function(){ +function briefImgs(elem) { + const imageTags = Array.from((elem || document).querySelectorAll('img[src],img[data-src]')); + + return imageTags.map((x)=> { + let linkPreferredSrc = x.src; + if (linkPreferredSrc.startsWith('data:')) { + if (typeof x.dataset?.src === 'string' && !x.dataset.src.startsWith('data:')) { + linkPreferredSrc = x.dataset.src; + } + } + + return { + src: new URL(linkPreferredSrc, document.baseURI).toString(), + loaded: x.complete, + width: x.width, + height: x.height, + naturalWidth: x.naturalWidth, + naturalHeight: x.naturalHeight, + alt: x.alt || x.title, + }; + }); +} +function getMaxDepthAndElemCountUsingTreeWalker(root=document.documentElement) { + let maxDepth = 0; + let currentDepth = 0; + let elementCount = 0; + + const treeWalker = document.createTreeWalker( + root, + NodeFilter.SHOW_ELEMENT, + (node) => { + const nodeName = node.nodeName?.toLowerCase(); + return (nodeName === 'svg') ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; + }, + false + ); + + while (true) { + maxDepth = Math.max(maxDepth, currentDepth); + elementCount++; // Increment the count for the current node + + if (treeWalker.firstChild()) { + currentDepth++; + } else { + while (!treeWalker.nextSibling() && currentDepth > 0) { + treeWalker.parentNode(); + currentDepth--; + } + + if (currentDepth <= 0) { + break; + } + } + } + + return { + maxDepth: maxDepth + 1, + elementCount: elementCount + }; +} + +function cloneAndExpandShadowRoots(rootElement = document.documentElement) { + // Create a shallow clone of the root element + const clone = rootElement.cloneNode(false); + // Function to process an element and its shadow root + function processShadowRoot(original, cloned) { + if (original.shadowRoot && original.shadowRoot.mode === 'open') { + shadowDomPresents = true; + const shadowContent = document.createDocumentFragment(); + + // Clone shadow root content normally + original.shadowRoot.childNodes.forEach(childNode => { + const clonedNode = childNode.cloneNode(true); + shadowContent.appendChild(clonedNode); + }); + + // Handle slots + const slots = shadowContent.querySelectorAll('slot'); + slots.forEach(slot => { + const slotName = slot.getAttribute('name') || ''; + const assignedElements = original.querySelectorAll( + slotName ? \`[slot="\${slotName}"]\` : ':not([slot])' + ); + + if (assignedElements.length > 0) { + const slotContent = document.createDocumentFragment(); + assignedElements.forEach(el => { + const clonedEl = el.cloneNode(true); + slotContent.appendChild(clonedEl); + }); + slot.parentNode.replaceChild(slotContent, slot); + } else if (!slotName) { + // Keep default slot content + // No need to do anything as it's already cloned + } + }); + + cloned.appendChild(shadowContent); + } + } + + // Use a TreeWalker on the original root to clone the entire structure + const treeWalker = document.createTreeWalker( + rootElement, + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT + ); + + const elementMap = new Map([[rootElement, clone]]); + + let currentNode; + while (currentNode = treeWalker.nextNode()) { + const parentClone = elementMap.get(currentNode.parentNode); + const clonedNode = currentNode.cloneNode(false); + parentClone.appendChild(clonedNode); + + if (currentNode.nodeType === Node.ELEMENT_NODE) { + elementMap.set(currentNode, clonedNode); + processShadowRoot(currentNode, clonedNode); + } + } + + return clone; +} + +function shadowDomPresent(rootElement = document.documentElement) { + const elems = rootElement.querySelectorAll('*'); + for (const x of elems) { + if (x.shadowRoot && x.shadowRoot.mode === 'open') { + return true; + } + } + return false; +} + +let lastMutationIdle = 0; +let initialAnalytics; +document.addEventListener('mutationIdle', ()=> lastMutationIdle = Date.now()); + +function giveSnapshot(stopActiveSnapshot, overrideDomAnalysis) { + if (stopActiveSnapshot) { + window.haltSnapshot = true; + } + let parsed; + try { + parsed = new Readability(document.cloneNode(true)).parse(); + } catch (err) { + void 0; + } + const domAnalysis = overrideDomAnalysis || getMaxDepthAndElemCountUsingTreeWalker(document.documentElement); + initialAnalytics ??= domAnalysis; + + const thisElemCount = domAnalysis.elementCount; + const initialElemCount = initialAnalytics.elementCount; + Math.abs(thisElemCount - initialElemCount) / (initialElemCount + Number.EPSILON) + const r = { + title: document.title, + description: document.head?.querySelector('meta[name="description"]')?.getAttribute('content') ?? '', + href: document.location.href, + html: document.documentElement?.outerHTML, + htmlSignificantlyModifiedByJs: Boolean(Math.abs(thisElemCount - initialElemCount) / (initialElemCount + Number.EPSILON) > 0.05), + text: document.body?.innerText, + shadowExpanded: shadowDomPresent() ? cloneAndExpandShadowRoots()?.outerHTML : undefined, + parsed: parsed, + imgs: [], + maxElemDepth: domAnalysis.maxDepth, + elemCount: domAnalysis.elementCount, + lastMutationIdle, + }; + if (document.baseURI !== r.href) { + r.rebase = document.baseURI; + } + r.imgs = briefImgs(); + + return r; +} +function waitForSelector(selectorText) { + return new Promise((resolve) => { + const existing = document.querySelector(selectorText); + if (existing) { + resolve(existing); + return; + } + const observer = new MutationObserver(() => { + const elem = document.querySelector(selectorText); + if (elem) { + resolve(document.querySelector(selectorText)); + observer.disconnect(); + } + }); + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + }); +} +window.getMaxDepthAndElemCountUsingTreeWalker = getMaxDepthAndElemCountUsingTreeWalker; +window.waitForSelector = waitForSelector; +window.giveSnapshot = giveSnapshot; +window.briefImgs = briefImgs; +})(); +`; + +const documentResourceTypes = new Set([ + 'document', 'script', 'xhr', 'fetch', 'prefetch', 'eventsource', 'websocket', 'preflight' +]); +const mediaResourceTypes = new Set([ + 'stylesheet', 'image', 'font', 'media' +]); + + +class PageReqCtrlKit { + reqSet: Set = new Set(); + blockers: Deferred[] = []; + lastResourceLoadedAt: number = 0; + lastContentResourceLoadedAt: number = 0; + lastMediaResourceLoadedAt: number = 0; + + constructor( + public concurrency: number, + ) { + if (isNaN(concurrency) || concurrency < 1) { + throw new AssertionFailureError(`Invalid concurrency: ${concurrency}`); + } + } + + onNewRequest(req: HTTPRequest) { + this.reqSet.add(req); + if (this.reqSet.size <= this.concurrency) { + return Promise.resolve(); + } + const deferred = Defer(); + this.blockers.push(deferred); + + return deferred.promise; + } + + onFinishRequest(req: HTTPRequest) { + this.reqSet.delete(req); + const deferred = this.blockers.shift(); + deferred?.resolve(); + const now = Date.now(); + this.lastResourceLoadedAt = now; + // Beware req being undefined + // https://pptr.dev/api/puppeteer.pageevent#:~:text=For%20certain%20requests%2C%20might%20contain%20undefined. + const typ = req?.resourceType(); + if (!typ) { + return; + } + if (documentResourceTypes.has(typ)) { + this.lastContentResourceLoadedAt = now; + } + if (mediaResourceTypes.has(typ)) { + this.lastMediaResourceLoadedAt = now; + } + } +} + +@singleton() +export class PuppeteerControl extends AsyncService { + + _sn = 0; + browser!: Browser; + logger = this.globalLogger.child({ service: this.constructor.name }); + + __loadedPage: Page[] = []; + + finalizerMap = new WeakMap>(); + snMap = new WeakMap(); + livePages = new Set(); + pagePhase = new WeakMap(); + lastPageCratedAt: number = 0; + ua: string = ''; + effectiveUA: string = ''; + + concurrentRequestsPerPage: number = 32; + pageReqCtrl = new WeakMap(); + + lastReqSentAt: number = 0; + + circuitBreakerHosts: Set = new Set(); + + lifeCycleTrack = new WeakMap(); + + constructor( + protected globalLogger: GlobalLogger, + protected asyncLocalContext: AsyncLocalContext, + protected curlControl: CurlControl, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + this.setMaxListeners(Infinity); + + let crippledTimes = 0; + this.on('crippled', () => { + crippledTimes += 1; + this.__loadedPage.length = 0; + this.livePages.clear(); + if (crippledTimes > 5) { + process.nextTick(() => { + this.emit('error', new Error('Browser crashed too many times, quitting...')); + // process.exit(1); + }); + } + }); + } + + override async init() { + await this.dependencyReady(); + if (process.env.NODE_ENV?.includes('dry-run')) { + this.emit('ready'); + return; + } + + if (this.browser) { + if (this.browser.connected) { + await this.browser.close(); + } else { + this.browser.process()?.kill('SIGKILL'); + } + } + this.browser = await puppeteer.launch({ + timeout: 10_000, + headless: !Boolean(process.env.DEBUG_BROWSER), + executablePath: process.env.OVERRIDE_CHROME_EXECUTABLE_PATH, + args: [ + '--disable-dev-shm-usage', + '--disable-blink-features=AutomationControlled' + ] + }).catch((err: any) => { + this.logger.error(`Unknown firebase issue, just die fast.`, { err }); + process.nextTick(() => { + this.emit('error', err); + // process.exit(1); + }); + return Promise.reject(err); + }); + this.browser.once('disconnected', () => { + this.logger.warn(`Browser disconnected`); + if (this.browser) { + this.emit('crippled'); + } + process.nextTick(() => this.serviceReady()); + }); + this.ua = await this.browser.userAgent(); + this.logger.info(`Browser launched: ${this.browser.process()?.pid}, ${this.ua}`); + this.effectiveUA = this.ua.replace(/Headless/i, '').replace('Mozilla/5.0 (X11; Linux x86_64)', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'); + this.curlControl.impersonateChrome(this.effectiveUA); + + await this.newPage('beware_deadlock').then((r) => this.__loadedPage.push(r)); + + this.emit('ready'); + } + + protected getRpsControlKit(page: Page) { + let kit = this.pageReqCtrl.get(page); + if (!kit) { + kit = new PageReqCtrlKit(this.concurrentRequestsPerPage); + this.pageReqCtrl.set(page, kit); + } + + return kit; + } + + async newPage(bewareDeadLock: any = false) { + if (!bewareDeadLock) { + await this.serviceReady(); + } + const sn = this._sn++; + let page; + try { + const dedicatedContext = await this.browser.createBrowserContext(); + page = await dedicatedContext.newPage(); + } catch (err: any) { + this.logger.warn(`Failed to create page ${sn}`, { err }); + this.browser.process()?.kill('SIGKILL'); + throw new ServiceNodeResourceDrainError(`This specific worker node failed to open a new page, try again.`); + } + const preparations = []; + + preparations.push(page.setUserAgent(this.effectiveUA)); + // preparations.push(page.setUserAgent(`Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)`)); + // preparations.push(page.setUserAgent(`Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0; +https://openai.com/gptbot)`)); + preparations.push(page.setBypassCSP(true)); + preparations.push(page.setViewport({ width: 1024, height: 1024 })); + preparations.push(page.exposeFunction('reportSnapshot', (snapshot: PageSnapshot) => { + if (snapshot.href === 'about:blank') { + return; + } + page.emit('snapshot', snapshot); + })); + preparations.push(page.exposeFunction('setViewport', (viewport: Viewport | null) => { + page.setViewport(viewport).catch(() => undefined); + })); + preparations.push(page.evaluateOnNewDocument(SCRIPT_TO_INJECT_INTO_FRAME)); + preparations.push(page.setRequestInterception(true)); + + await Promise.all(preparations); + + await page.goto('about:blank', { waitUntil: 'domcontentloaded' }); + + const domainSet = new Set(); + let reqCounter = 0; + let t0: number | undefined; + let halt = false; + + page.on('request', async (req) => { + reqCounter++; + if (halt) { + return req.abort('blockedbyclient', 1000); + } + const requestUrl = req.url(); + if (!requestUrl.startsWith('http:') && !requestUrl.startsWith('https:') && !requestUrl.startsWith('chrome-extension:') && requestUrl !== 'about:blank') { + return req.abort('blockedbyclient', 1000); + } + t0 ??= Date.now(); + + const parsedUrl = new URL(requestUrl); + if (isIP(parsedUrl.hostname)) { + domainSet.add(parsedUrl.hostname); + } else { + try { + const tldParsed = tldExtract(requestUrl); + domainSet.add(tldParsed.domain); + } catch (_err) { + domainSet.add(parsedUrl.hostname); + } + } + + if (this.circuitBreakerHosts.has(parsedUrl.hostname.toLowerCase())) { + page.emit('abuse', { url: requestUrl, page, sn, reason: `Abusive request: ${requestUrl}` }); + return req.abort('blockedbyclient', 1000); + } + + if ( + parsedUrl.hostname === 'localhost' || + parsedUrl.hostname.startsWith('127.') + ) { + page.emit('abuse', { url: requestUrl, page, sn, reason: `Suspicious action: Request to localhost: ${requestUrl}` }); + + return req.abort('blockedbyclient', 1000); + } + + const dt = Math.ceil((Date.now() - t0) / 1000); + const rps = reqCounter / dt; + // console.log(`rps: ${rps}`); + const pagePhase = this.pagePhase.get(page); + if (pagePhase === 'background') { + if (rps > 10 || reqCounter > 1000) { + halt = true; + + return req.abort('blockedbyclient', 1000); + } + } + if (reqCounter > 1000) { + if (rps > 60 || reqCounter > 2000) { + page.emit('abuse', { url: requestUrl, page, sn, reason: `DDoS attack suspected: Too many requests` }); + halt = true; + + return req.abort('blockedbyclient', 1000); + } + } + + if (domainSet.size > 200) { + page.emit('abuse', { url: requestUrl, page, sn, reason: `DDoS attack suspected: Too many domains` }); + halt = true; + + return req.abort('blockedbyclient', 1000); + } + + if (requestUrl.startsWith('http')) { + const kit = this.getRpsControlKit(page); + await kit.onNewRequest(req); + } + + if (req.isInterceptResolutionHandled()) { + return; + }; + + const continueArgs = req.continueRequestOverrides + ? [req.continueRequestOverrides(), 0] as const + : []; + + return req.continue(continueArgs[0], continueArgs[1]); + }); + const reqFinishHandler = (req: HTTPRequest) => { + const kit = this.getRpsControlKit(page); + kit.onFinishRequest(req); + }; + page.on('requestfinished', reqFinishHandler); + page.on('requestfailed', reqFinishHandler); + page.on('requestservedfromcache', reqFinishHandler); + + await page.evaluateOnNewDocument(` +(function () { + if (window.self === window.top) { + let lastAnalytics; + let lastReportedAt = 0; + const handlePageLoad = () => { + const now = Date.now(); + const dt = now - lastReportedAt; + const previousAnalytics = lastAnalytics; + const thisAnalytics = getMaxDepthAndElemCountUsingTreeWalker(); + let dElem = 0; + + if (window.haltSnapshot) { + return; + } + + const thisElemCount = thisAnalytics.elementCount; + if (previousAnalytics) { + const previousElemCount = previousAnalytics.elementCount; + + const delta = Math.abs(thisElemCount - previousElemCount); + dElem = delta /(previousElemCount + Number.EPSILON); + } + + if (dt < 1200 && dElem < 0.05) { + return; + } + + lastAnalytics = thisAnalytics; + lastReportedAt = now; + + const r = giveSnapshot(false, lastAnalytics); + window.reportSnapshot(r); + }; + document.addEventListener('readystatechange', ()=> { + if (document.readyState === 'interactive') { + handlePageLoad(); + } + }); + document.addEventListener('load', handlePageLoad); + window.addEventListener('load', handlePageLoad); + document.addEventListener('DOMContentLoaded', handlePageLoad); + document.addEventListener('mutationIdle', handlePageLoad); + } + document.addEventListener('DOMContentLoaded', ()=> window.simulateScroll(), { once: true }); +})(); +`); + + this.snMap.set(page, sn); + this.logger.debug(`Page ${sn} created.`); + this.lastPageCratedAt = Date.now(); + this.livePages.add(page); + this.pagePhase.set(page, 'idle'); + + return page; + } + + async getNextPage() { + let thePage: Page | undefined; + if (this.__loadedPage.length) { + thePage = this.__loadedPage.shift(); + if (this.__loadedPage.length <= 1) { + process.nextTick(() => { + this.newPage() + .then((r) => this.__loadedPage.push(r)) + .catch((err) => { + this.logger.warn(`Failed to load new page ahead of time`, { err }); + }); + }); + } + } + + if (!thePage) { + thePage = await this.newPage(); + } + + const timer = setTimeout(() => { + this.logger.warn(`Page is not allowed to live past 5 minutes, ditching page ${this.snMap.get(thePage!)}...`); + this.ditchPage(thePage!); + }, 300 * 1000); + + this.finalizerMap.set(thePage, timer); + + return thePage; + } + + async ditchPage(page: Page) { + if (this.finalizerMap.has(page)) { + clearTimeout(this.finalizerMap.get(page)!); + this.finalizerMap.delete(page); + } + if (page.isClosed()) { + return; + } + const sn = this.snMap.get(page); + this.logger.debug(`Closing page ${sn}`); + await Promise.race([ + (async () => { + const ctx = page.browserContext(); + try { + await page.close(); + } finally { + await ctx.close(); + } + })(), + delay(5000) + ]).catch((err) => { + this.logger.error(`Failed to destroy page ${sn}`, { err }); + }); + this.livePages.delete(page); + this.pagePhase.delete(page); + } + + async *scrap(parsedUrl: URL, options: ScrappingOptions = {}): AsyncGenerator { + // parsedUrl.search = ''; + const url = parsedUrl.toString(); + let snapshot: PageSnapshot | undefined; + let screenshot: Buffer | undefined; + let pageshot: Buffer | undefined; + const pdfUrls: string[] = []; + let navigationResponse: HTTPResponse | undefined; + const page = await this.getNextPage(); + this.lifeCycleTrack.set(page, this.asyncLocalContext.ctx); + this.pagePhase.set(page, 'active'); + page.on('response', (resp) => { + this.blackHoleDetector.itWorked(); + const req = resp.request(); + if (req.frame() === page.mainFrame() && req.isNavigationRequest()) { + navigationResponse = resp; + } + if (!resp.ok()) { + return; + } + const headers = resp.headers(); + const url = resp.url(); + const contentType = headers['content-type']; + if (contentType?.toLowerCase().includes('application/pdf')) { + pdfUrls.push(url); + } + }); + page.on('request', async (req) => { + if (req.isInterceptResolutionHandled()) { + return; + }; + const reqUrlParsed = new URL(req.url()); + if (!reqUrlParsed.protocol.startsWith('http')) { + const overrides = req.continueRequestOverrides(); + + return req.continue(overrides, 0); + } + const typ = req.resourceType(); + if (typ === 'media') { + // Non-cooperative answer to block all media requests. + return req.abort('blockedbyclient'); + } + if (!options.proxyResources) { + const isDocRequest = ['document', 'xhr', 'fetch', 'websocket', 'prefetch', 'eventsource', 'ping'].includes(typ); + if (!isDocRequest) { + if (options.extraHeaders) { + const overrides = req.continueRequestOverrides(); + const continueArgs = [{ + ...overrides, + headers: { + ...req.headers(), + ...overrides?.headers, + ...options.extraHeaders, + } + }, 1] as const; + + return req.continue(continueArgs[0], continueArgs[1]); + } + const overrides = req.continueRequestOverrides(); + + return req.continue(overrides, 0); + } + } + const sideload = options.sideLoad; + + const impersonate = sideload?.impersonate[reqUrlParsed.href]; + if (impersonate) { + let body; + if (impersonate.body) { + body = await readFile(await impersonate.body.filePath); + if (req.isInterceptResolutionHandled()) { + return; + } + } + return req.respond({ + status: impersonate.status, + headers: impersonate.headers, + contentType: impersonate.contentType, + body: body ? Uint8Array.from(body) : undefined, + }, 999); + } + + const proxy = options.proxyUrl || sideload?.proxyOrigin?.[reqUrlParsed.origin]; + const ctx = this.lifeCycleTrack.get(page); + if (proxy && ctx) { + return await this.asyncLocalContext.bridge(ctx, async () => { + try { + const curled = await this.curlControl.sideLoad(reqUrlParsed, { + ...options, + method: req.method(), + body: req.postData(), + extraHeaders: { + ...req.headers(), + ...options.extraHeaders, + }, + proxyUrl: proxy + }); + if (req.isInterceptResolutionHandled()) { + return; + }; + + if (curled.chain.length === 1) { + if (!curled.file) { + return req.respond({ + status: curled.status, + headers: _.omit(curled.headers, 'result'), + contentType: curled.contentType, + }, 3); + } + const body = await readFile(await curled.file.filePath); + if (req.isInterceptResolutionHandled()) { + return; + }; + return req.respond({ + status: curled.status, + headers: _.omit(curled.headers, 'result'), + contentType: curled.contentType, + body: Uint8Array.from(body), + }, 3); + } + options.sideLoad ??= curled.sideLoadOpts; + _.merge(options.sideLoad, curled.sideLoadOpts); + const firstReq = curled.chain[0]; + + return req.respond({ + status: firstReq.result!.code, + headers: _.omit(firstReq, 'result'), + }, 3); + } catch (err: any) { + this.logger.warn(`Failed to sideload browser request ${reqUrlParsed.origin}`, { href: reqUrlParsed.href, err, proxy }); + } + if (req.isInterceptResolutionHandled()) { + return; + }; + const overrides = req.continueRequestOverrides(); + const continueArgs = [{ + ...overrides, + headers: { + ...req.headers(), + ...overrides?.headers, + ...options.extraHeaders, + } + }, 1] as const; + + return req.continue(continueArgs[0], continueArgs[1]); + }); + } + + if (req.isInterceptResolutionHandled()) { + return; + }; + const overrides = req.continueRequestOverrides(); + const continueArgs = [{ + ...overrides, + headers: { + ...req.headers(), + ...overrides?.headers, + ...options.extraHeaders, + } + }, 1] as const; + + return req.continue(continueArgs[0], continueArgs[1]); + }); + let pageScriptEvaluations: Promise[] = []; + let frameScriptEvaluations: Promise[] = []; + if (options.injectPageScripts?.length) { + page.on('framenavigated', (frame) => { + if (frame !== page.mainFrame()) { + return; + } + + pageScriptEvaluations.push( + Promise.allSettled(options.injectPageScripts!.map((x) => frame.evaluate(x).catch((err) => { + this.logger.warn(`Error in evaluation of page scripts`, { err }); + }))) + ); + }); + } + if (options.injectFrameScripts?.length) { + page.on('framenavigated', (frame) => { + frameScriptEvaluations.push( + Promise.allSettled(options.injectFrameScripts!.map((x) => frame.evaluate(x).catch((err) => { + this.logger.warn(`Error in evaluation of frame scripts`, { err }); + }))) + ); + }); + } + const sn = this.snMap.get(page); + this.logger.info(`Page ${sn}: Scraping ${url}`, { url }); + if (options.locale) { + // Add headers via request interception to walk around this bug + // https://github.com/puppeteer/puppeteer/issues/10235 + // await page.setExtraHTTPHeaders({ + // 'Accept-Language': options.locale + // }); + + await page.evaluateOnNewDocument(() => { + Object.defineProperty(navigator, "language", { + get: function () { + return options.locale; + } + }); + Object.defineProperty(navigator, "languages", { + get: function () { + return [options.locale]; + } + }); + }); + } + + if (options.cookies) { + const mapped = options.cookies.map((x) => { + const draft: CookieParam = { + name: x.name, + value: encodeURIComponent(x.value), + secure: x.secure, + domain: x.domain, + path: x.path, + expires: x.expires ? Math.floor(x.expires.valueOf() / 1000) : undefined, + sameSite: x.sameSite as any, + }; + if (!draft.expires && x.maxAge) { + draft.expires = Math.floor(Date.now() / 1000) + x.maxAge; + } + if (!draft.domain) { + draft.url = parsedUrl.toString(); + } + + return draft; + }); + try { + await page.setCookie(...mapped); + } catch (err: any) { + this.logger.warn(`Page ${sn}: Failed to set cookies`, { err }); + throw new ParamValidationError({ + path: 'cookies', + message: `Failed to set cookies: ${err?.message}` + }); + } + } + if (options.overrideUserAgent) { + await page.setUserAgent(options.overrideUserAgent); + } + if (options.viewport) { + await page.setViewport(options.viewport); + } + + let nextSnapshotDeferred = Defer(); + const crippleListener = () => nextSnapshotDeferred.reject(new ServiceCrashedError({ message: `Browser crashed, try again` })); + this.once('crippled', crippleListener); + nextSnapshotDeferred.promise.finally(() => { + this.off('crippled', crippleListener); + }); + let successfullyDone; + const hdl = (s: any) => { + if (snapshot === s) { + return; + } + snapshot = s; + if (snapshot) { + const kit = this.pageReqCtrl.get(page); + snapshot.lastContentResourceLoaded = kit?.lastContentResourceLoadedAt; + snapshot.lastMediaResourceLoaded = kit?.lastMediaResourceLoadedAt; + } + if (s?.maxElemDepth && s.maxElemDepth > 256) { + return; + } + if (s?.elemCount && s.elemCount > 10_000) { + return; + } + nextSnapshotDeferred.resolve(s); + nextSnapshotDeferred = Defer(); + this.once('crippled', crippleListener); + nextSnapshotDeferred.promise.finally(() => { + this.off('crippled', crippleListener); + }); + }; + page.on('snapshot', hdl); + page.once('abuse', (event: any) => { + this.emit('abuse', { ...event, url: parsedUrl }); + if (snapshot?.href && parsedUrl.href !== snapshot.href) { + this.emit('abuse', { ...event, url: snapshot.href }); + } + + nextSnapshotDeferred.reject( + new SecurityCompromiseError(`Abuse detected: ${event.reason}`) + ); + }); + + const timeout = options.timeoutMs || 30_000; + const goToOptions: GoToOptions = { + waitUntil: ['load', 'domcontentloaded', 'networkidle0'], + timeout, + }; + + if (options.referer) { + goToOptions.referer = options.referer; + } + + let waitForPromise: Promise | undefined; + let finalizationPromise: Promise | undefined; + const doFinalization = async () => { + if (waitForPromise) { + // SuccessfullyDone is meant for the finish of the page. + // It doesn't matter if you are expecting something and it didn't show up. + await waitForPromise.catch(() => void 0); + } + successfullyDone ??= true; + try { + const pSubFrameSnapshots = this.snapshotChildFrames(page); + snapshot = await page.evaluate('giveSnapshot(true)') as PageSnapshot; + screenshot = (await this.takeScreenShot(page)) || screenshot; + pageshot = (await this.takeScreenShot(page, { fullPage: true })) || pageshot; + if (snapshot) { + snapshot.childFrames = await pSubFrameSnapshots; + } + } catch (err: any) { + this.logger.warn(`Page ${sn}: Failed to finalize ${url}`, { err }); + } + if (!snapshot?.html) { + return; + } + + this.logger.info(`Page ${sn}: Snapshot of ${url} done`, { url, title: snapshot?.title, href: snapshot?.href }); + this.emit( + 'crawled', + { + ...snapshot, + status: navigationResponse?.status(), + statusText: navigationResponse?.statusText(), + pdfs: _.uniq(pdfUrls), screenshot, pageshot, + }, + { ...options, url: parsedUrl } + ); + }; + const delayPromise = delay(timeout); + const gotoPromise = page.goto(url, goToOptions) + .catch((err) => { + if (err instanceof TimeoutError) { + this.logger.warn(`Page ${sn}: Browsing of ${url} timed out`, { err }); + return new AssertionFailureError({ + message: `Failed to goto ${url}: ${err}`, + cause: err, + }); + } + if (err?.message?.startsWith('net::ERR_ABORTED')) { + if (pdfUrls.length) { + // Not throw for pdf mode. + return; + } + } + + this.logger.warn(`Page ${sn}: Browsing of ${url} failed`, { err }); + return new AssertionFailureError({ + message: `Failed to goto ${url}: ${err}`, + cause: err, + }); + }).then(async (stuff) => { + // This check is necessary because without snapshot, the condition of the page is unclear + // Calling evaluate directly may stall the process. + if (!snapshot) { + if (stuff instanceof Error) { + throw stuff; + } + } + await Promise.race([Promise.allSettled([...pageScriptEvaluations, ...frameScriptEvaluations]), delayPromise]) + .catch(() => void 0); + return stuff; + }); + if (options.waitForSelector) { + const t0 = Date.now(); + waitForPromise = nextSnapshotDeferred.promise.then(() => { + const t1 = Date.now(); + const elapsed = t1 - t0; + const remaining = timeout - elapsed; + const thisTimeout = remaining > 100 ? remaining : 100; + const p = (Array.isArray(options.waitForSelector) ? + Promise.all(options.waitForSelector.map((x) => page.waitForSelector(x, { timeout: thisTimeout }))) : + page.waitForSelector(options.waitForSelector!, { timeout: thisTimeout })) + .then(() => { + successfullyDone = true; + }) + .catch((err) => { + waitForPromise = undefined; + this.logger.warn(`Page ${sn}: Failed to wait for selector ${options.waitForSelector}`, { err }); + }); + return p as any; + }); + finalizationPromise = Promise.allSettled([waitForPromise, gotoPromise]).then(doFinalization); + } else { + finalizationPromise = gotoPromise.then(doFinalization); + } + + try { + let lastHTML = snapshot?.html; + while (true) { + const ckpt = [nextSnapshotDeferred.promise, waitForPromise ?? gotoPromise]; + + if (options.minIntervalMs) { + ckpt.push(delay(options.minIntervalMs)); + } + let error; + await Promise.race(ckpt).catch((err) => error = err); + if (successfullyDone && !error) { + if (!snapshot && !screenshot) { + throw new AssertionFailureError(`Could not extract any meaningful content from the page`); + } + yield { + ...snapshot, + status: navigationResponse?.status(), + statusText: navigationResponse?.statusText(), + pdfs: _.uniq(pdfUrls), screenshot, pageshot + } as PageSnapshot; + break; + } + if (options.favorScreenshot && snapshot?.title && snapshot?.html !== lastHTML) { + screenshot = (await this.takeScreenShot(page)) || screenshot; + pageshot = (await this.takeScreenShot(page, { fullPage: true })) || pageshot; + lastHTML = snapshot.html; + } + if (snapshot || screenshot) { + yield { + ...snapshot, + status: navigationResponse?.status(), + statusText: navigationResponse?.statusText(), + pdfs: _.uniq(pdfUrls), screenshot, pageshot, + isIntermediate: true, + } as PageSnapshot; + } + if (error) { + throw error; + } + if (successfullyDone) { + break; + } + } + await finalizationPromise; + yield { + ...snapshot, + status: navigationResponse?.status(), + statusText: navigationResponse?.statusText(), + pdfs: _.uniq(pdfUrls), screenshot, pageshot + } as PageSnapshot; + } finally { + this.pagePhase.set(page, 'background'); + Promise.allSettled([gotoPromise, waitForPromise, finalizationPromise]).finally(() => { + page.off('snapshot', hdl); + this.ditchPage(page); + }); + nextSnapshotDeferred.resolve(); + } + } + + protected async takeScreenShot(page: Page, opts?: Parameters[0]): Promise { + const r = await page.screenshot(opts).catch((err) => { + this.logger.warn(`Failed to take screenshot`, { err }); + }); + + if (r) { + return Buffer.from(r); + } + + return undefined; + } + + async snapshotChildFrames(page: Page): Promise { + const childFrames = page.mainFrame().childFrames(); + const r = await Promise.all(childFrames.map(async (x) => { + const thisUrl = x.url(); + if (!thisUrl || thisUrl === 'about:blank') { + return undefined; + } + try { + await x.evaluate(SCRIPT_TO_INJECT_INTO_FRAME); + + return await x.evaluate(`giveSnapshot()`); + } catch (err) { + this.logger.warn(`Failed to snapshot child frame ${thisUrl}`, { err }); + return undefined; + } + })) as PageSnapshot[]; + + return r.filter(Boolean); + } + +} + +const puppeteerControl = container.resolve(PuppeteerControl); + +export default puppeteerControl; diff --git a/src/services/registry.ts b/src/services/registry.ts new file mode 100644 index 0000000..e4257a1 --- /dev/null +++ b/src/services/registry.ts @@ -0,0 +1,60 @@ +import { propertyInjectorFactory } from 'civkit/property-injector'; +import { KoaRPCRegistry } from 'civkit/civ-rpc/koa'; +import { container, singleton } from 'tsyringe'; +import { IntegrityEnvelope } from 'civkit/civ-rpc'; +import bodyParser from '@koa/bodyparser'; + +import { GlobalLogger } from './logger'; +import { TempFileManager } from './temp-file'; +import { AsyncLocalContext } from './async-context'; +import { BlackHoleDetector } from './blackhole-detector'; +export { Context } from 'koa'; + +export const InjectProperty = propertyInjectorFactory(container); + +@singleton() +export class RPCRegistry extends KoaRPCRegistry { + + title = 'Jina Reader API'; + container = container; + logger = this.globalLogger.child({ service: this.constructor.name }); + static override envelope = IntegrityEnvelope; + override _BODY_PARSER_LIMIT = '102mb'; + override _RESPONSE_STREAM_MODE = 'koa' as const; + + override koaMiddlewares = [ + this.__CORSAllowAllMiddleware.bind(this), + bodyParser({ + encoding: 'utf-8', + enableTypes: ['json', 'form'], + jsonLimit: this._BODY_PARSER_LIMIT, + xmlLimit: this._BODY_PARSER_LIMIT, + formLimit: this._BODY_PARSER_LIMIT, + }), + this.__multiParse.bind(this), + this.__binaryParse.bind(this), + ]; + + constructor( + protected globalLogger: GlobalLogger, + protected ctxMgr: AsyncLocalContext, + protected tempFileManager: TempFileManager, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + + this.on('run', () => this.blackHoleDetector.incomingRequest()); + this.on('ran', () => this.blackHoleDetector.doneWithRequest()); + this.on('fail', () => this.blackHoleDetector.doneWithRequest()); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + } + +} + +const instance = container.resolve(RPCRegistry); +export default instance; +export const { Method, RPCMethod, RPCReflect, Param, Ctx, } = instance.decorators(); diff --git a/src/services/robots-text.ts b/src/services/robots-text.ts new file mode 100644 index 0000000..365af3c --- /dev/null +++ b/src/services/robots-text.ts @@ -0,0 +1,127 @@ +import { singleton } from 'tsyringe'; +import { URL } from 'url'; +import { AssertionFailureError, DownstreamServiceFailureError, ResourcePolicyDenyError } from 'civkit/civ-rpc'; +import { AsyncService } from 'civkit/async-service'; +import { HashManager } from 'civkit/hash'; +import { marshalErrorLike } from 'civkit/lang'; + +import { GlobalLogger } from './logger'; +import { FirebaseStorageBucketControl } from '../shared/services/firebase-storage-bucket'; +import { Threaded } from '../services/threaded'; + + +export const md5Hasher = new HashManager('md5', 'hex'); + +@singleton() +export class RobotsTxtService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + constructor( + protected globalLogger: GlobalLogger, + protected firebaseStorageBucketControl: FirebaseStorageBucketControl, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + } + + async getCachedRobotTxt(origin: string) { + const digest = md5Hasher.hash(origin.toLowerCase()); + const cacheLoc = `robots-txt/${digest}`; + let buff; + buff = await this.firebaseStorageBucketControl.downloadFile(cacheLoc).catch(() => undefined); + if (buff) { + return buff.toString(); + } + + const r = await fetch(new URL('robots.txt', origin).href, { signal: AbortSignal.timeout(5000) }); + if (!r.ok) { + throw new DownstreamServiceFailureError(`Failed to fetch robots.txt from ${origin}: ${r.status} ${r.statusText}`); + } + buff = Buffer.from(await r.arrayBuffer()); + + this.firebaseStorageBucketControl.saveFile(cacheLoc, buff, { + contentType: 'text/plain' + }).catch((err) => { + this.logger.warn(`Failed to save robots.txt to cache: ${err}`, { err: marshalErrorLike(err) }); + }); + + return buff.toString(); + } + + @Threaded() + async assertAccessAllowed(url: URL, inputMyUa = '*') { + let robotTxt: string = ''; + try { + robotTxt = await this.getCachedRobotTxt(url.origin); + } catch (err) { + if (err instanceof DownstreamServiceFailureError) { + // Remote server is reachable but cannot provide a robot.txt; this is treated as public access + return true; + } + throw new AssertionFailureError(`Failed to load robots.txt from ${url.origin}: ${err}`); + } + const myUa = inputMyUa.toLowerCase(); + const lines = robotTxt.split(/\r?\n/g); + + let currentUa = myUa || '*'; + let uaLine = 'User-Agent: *'; + const pathNormalized = `${url.pathname}?`; + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('#') || !trimmed) { + continue; + } + const [k, ...rest] = trimmed.split(':'); + const key = k.trim().toLowerCase(); + const value = rest.join(':').trim(); + + if (key === 'user-agent') { + currentUa = value.toLowerCase(); + if (value === '*') { + currentUa = myUa; + } + uaLine = line; + continue; + } + + if (currentUa !== myUa) { + continue; + } + + if (key === 'disallow') { + if (!value) { + return true; + } + if (value.includes('*')) { + const [head, tail] = value.split('*'); + if (url.pathname.startsWith(head) && url.pathname.endsWith(tail)) { + throw new ResourcePolicyDenyError(`Access to ${url.href} is disallowed by site robots.txt: For ${uaLine}, ${line}`); + } + } else if (pathNormalized.startsWith(value)) { + throw new ResourcePolicyDenyError(`Access to ${url.href} is disallowed by site robots.txt: For ${uaLine}, ${line}`); + } + + continue; + } + + if (key === 'allow') { + if (!value) { + return true; + } + if (pathNormalized.startsWith(value)) { + return true; + } + continue; + } + } + + return true; + } + +} diff --git a/src/services/serp/compat.ts b/src/services/serp/compat.ts new file mode 100644 index 0000000..1543c34 --- /dev/null +++ b/src/services/serp/compat.ts @@ -0,0 +1,12 @@ +export interface WebSearchEntry { + link: string; + title: string; + source?: string; + date?: string; + snippet?: string; + imageUrl?: string; + siteLinks?: { + link: string; title: string; snippet?: string; + }[]; + variant?: 'web' | 'images' | 'news'; +} \ No newline at end of file diff --git a/src/services/serp/google.ts b/src/services/serp/google.ts new file mode 100644 index 0000000..23ba669 --- /dev/null +++ b/src/services/serp/google.ts @@ -0,0 +1,308 @@ +import { singleton } from 'tsyringe'; +import { AsyncService } from 'civkit/async-service'; +import { GlobalLogger } from '../logger'; +import { JSDomControl } from '../jsdom'; +import { isMainThread } from 'worker_threads'; +import _ from 'lodash'; +import { WebSearchEntry } from './compat'; +import { ScrappingOptions, SERPSpecializedPuppeteerControl } from './puppeteer'; +import { CurlControl } from '../curl'; +import { readFile } from 'fs/promises'; +import { ApplicationError } from 'civkit/civ-rpc'; +import { ServiceBadApproachError, ServiceBadAttemptError } from '../errors'; +import { parseJSONText } from 'civkit/vectorize'; +import { retryWith } from 'civkit/decorators'; +import { ProxyProviderService } from '../../shared/services/proxy-provider'; + +@singleton() +export class GoogleSERP extends AsyncService { + logger = this.globalLogger.child({ service: this.constructor.name }); + googleDomain = process.env.OVERRIDE_GOOGLE_DOMAIN || 'www.google.com'; + + constructor( + protected globalLogger: GlobalLogger, + protected puppeteerControl: SERPSpecializedPuppeteerControl, + protected jsDomControl: JSDomControl, + protected curlControl: CurlControl, + protected proxyProvider: ProxyProviderService, + ) { + const filteredDeps = isMainThread ? arguments : _.without(arguments, puppeteerControl); + super(...filteredDeps); + } + + override async init() { + await this.dependencyReady(); + + this.emit('ready'); + } + + @retryWith((err) => { + if (err instanceof ServiceBadApproachError) { + return false; + } + if (err instanceof ServiceBadAttemptError) { + // Keep trying + return true; + } + if (err instanceof ApplicationError) { + // Quit with this error + return false; + } + return undefined; + }, 3) + async sideLoadWithAllocatedProxy(url: URL, opts?: ScrappingOptions) { + if (opts?.allocProxy === 'none') { + return this.curlControl.sideLoad(url, opts); + } + + const proxy = await this.proxyProvider.alloc( + process.env.PREFERRED_PROXY_COUNTRY || 'auto' + ); + this.logger.debug(`Proxy allocated`, { proxy: proxy.href }); + const r = await this.curlControl.sideLoad(url, { + ...opts, + proxyUrl: proxy.href, + }); + + if (r.status === 429) { + throw new ServiceBadAttemptError('Google returned a 429 error. This may happen due to various reasons, including rate limiting or other issues.'); + } + + if (opts && opts.allocProxy) { + opts.proxyUrl ??= proxy.href; + } + + return { ...r, proxy }; + } + + digestQuery(query: { [k: string]: any; }) { + const url = new URL(`https://${this.googleDomain}/search`); + const clone = { ...query }; + const num = clone.num || 10; + if (clone.page) { + const page = parseInt(clone.page); + delete clone.page; + clone.start = (page - 1) * num; + if (clone.start === 0) { + delete clone.start; + } + } + if (clone.location) { + delete clone.location; + } + + for (const [k, v] of Object.entries(clone)) { + if (v === undefined || v === null) { + continue; + } + url.searchParams.set(k, `${v}`); + } + + return url; + } + + async webSearch(query: { [k: string]: any; }, opts?: ScrappingOptions) { + const url = this.digestQuery(query); + + const sideLoaded = await this.sideLoadWithAllocatedProxy(url, opts); + if (opts && sideLoaded.sideLoadOpts) { + opts.sideLoad = sideLoaded.sideLoadOpts; + } + + const snapshot = await this.puppeteerControl.controlledScrap(url, getWebSearchResults, opts); + + return snapshot; + } + + async newsSearch(query: { [k: string]: any; }, opts?: ScrappingOptions) { + const url = this.digestQuery(query); + + url.searchParams.set('tbm', 'nws'); + + const sideLoaded = await this.sideLoadWithAllocatedProxy(url, opts); + if (opts && sideLoaded.sideLoadOpts) { + opts.sideLoad = sideLoaded.sideLoadOpts; + } + + const snapshot = await this.puppeteerControl.controlledScrap(url, getNewsSearchResults, opts); + + return snapshot; + } + + async imageSearch(query: { [k: string]: any; }, opts?: ScrappingOptions) { + const url = this.digestQuery(query); + + url.searchParams.set('tbm', 'isch'); + url.searchParams.set('asearch', 'isch'); + url.searchParams.set('async', `_fmt:json,p:1,ijn:${query.start ? Math.floor(query.start / (query.num || 10)) : 0}`); + + const sideLoaded = await this.sideLoadWithAllocatedProxy(url, opts); + + if (sideLoaded.status !== 200 || !sideLoaded.file) { + throw new ServiceBadAttemptError('Google returned an error page. This may happen due to various reasons, including rate limiting or other issues.'); + } + + const jsonTxt = (await readFile((await sideLoaded.file.filePath))).toString(); + const rJSON = parseJSONText(jsonTxt.slice(jsonTxt.indexOf('{"ischj":'))); + + return _.get(rJSON, 'ischj.metadata').map((x: any) => { + + return { + link: _.get(x, 'result.referrer_url'), + title: _.get(x, 'result.page_title'), + snippet: _.get(x, 'text_in_grid.snippet'), + source: _.get(x, 'result.site_title'), + imageWidth: _.get(x, 'original_image.width'), + imageHeight: _.get(x, 'original_image.height'), + imageUrl: _.get(x, 'original_image.url'), + variant: 'images', + }; + }) as WebSearchEntry[]; + } +} + +async function getWebSearchResults() { + if (location.pathname.startsWith('/sorry') || location.pathname.startsWith('/error')) { + throw new Error('Google returned an error page. This may happen due to various reasons, including rate limiting or other issues.'); + } + + // @ts-ignore + await Promise.race([window.waitForSelector('div[data-async-context^="query"]'), window.waitForSelector('#botstuff .mnr-c')]); + + const wrapper1 = document.querySelector('div[data-async-context^="query"]'); + + if (!wrapper1) { + return undefined; + } + + const query = decodeURIComponent(wrapper1.getAttribute('data-async-context')?.split('query:')[1] || ''); + + if (!query) { + return undefined; + } + + const candidates = Array.from(wrapper1.querySelectorAll('div[lang],div[data-surl]')); + + return candidates.map((x, pos) => { + const primaryLink = x.querySelector('a:not([href="#"])'); + if (!primaryLink) { + return undefined; + } + const url = primaryLink.getAttribute('href'); + + if (primaryLink.querySelector('div[role="heading"]')) { + // const spans = primaryLink.querySelectorAll('span'); + // const title = spans[0]?.textContent; + // const source = spans[1]?.textContent; + // const date = spans[spans.length - 1].textContent; + + // return { + // link: url, + // title, + // source, + // date, + // variant: 'video' + // }; + return undefined; + } + + const title = primaryLink.querySelector('h3')?.textContent; + const source = Array.from(primaryLink.querySelectorAll('span')).find((x) => x.textContent)?.textContent; + const cite = primaryLink.querySelector('cite[role=text]')?.textContent; + let date = cite?.split('·')[1]?.trim(); + const snippets = Array.from(x.querySelectorAll('div[data-sncf*="1"] span')); + let snippet = snippets[snippets.length - 1]?.textContent; + if (!snippet) { + snippet = x.querySelector('div.IsZvec')?.textContent?.trim() || null; + } + date ??= snippets[snippets.length - 2]?.textContent?.trim(); + const imageUrl = x.querySelector('div[data-sncf*="1"] img[src]:not(img[src^="data"])')?.getAttribute('src'); + let siteLinks = Array.from(x.querySelectorAll('div[data-sncf*="3"] a[href]')).map((l) => { + return { + link: l.getAttribute('href'), + title: l.textContent, + }; + }); + const perhapsParent = x.parentElement?.closest('div[data-hveid]'); + if (!siteLinks?.length && perhapsParent) { + const candidates = Array.from(perhapsParent.querySelectorAll('td h3')); + if (candidates.length) { + siteLinks = candidates.map((l) => { + const link = l.querySelector('a'); + if (!link) { + return undefined; + } + const snippet = l.nextElementSibling?.textContent; + return { + link: link.getAttribute('href'), + title: link.textContent, + snippet, + }; + }).filter(Boolean) as any; + } + } + + return { + link: url, + title, + source, + date, + snippet: snippet ?? undefined, + imageUrl: imageUrl?.startsWith('data:') ? undefined : imageUrl, + siteLinks: siteLinks.length ? siteLinks : undefined, + variant: 'web', + }; + }).filter(Boolean) as WebSearchEntry[]; +} + +async function getNewsSearchResults() { + if (location.pathname.startsWith('/sorry') || location.pathname.startsWith('/error')) { + throw new Error('Google returned an error page. This may happen due to various reasons, including rate limiting or other issues.'); + } + + // @ts-ignore + await Promise.race([window.waitForSelector('div[data-async-context^="query"]'), window.waitForSelector('#botstuff .mnr-c')]); + + const wrapper1 = document.querySelector('div[data-async-context^="query"]'); + + if (!wrapper1) { + return undefined; + } + + const query = decodeURIComponent(wrapper1.getAttribute('data-async-context')?.split('query:')[1] || ''); + + if (!query) { + return undefined; + } + + const candidates = Array.from(wrapper1.querySelectorAll('div[data-news-doc-id]')); + + return candidates.map((x) => { + const primaryLink = x.querySelector('a:not([href="#"])'); + if (!primaryLink) { + return undefined; + } + const url = primaryLink.getAttribute('href'); + const titleElem = primaryLink.querySelector('div[role="heading"]'); + + if (!titleElem) { + return undefined; + } + + const title = titleElem.textContent?.trim(); + const source = titleElem.previousElementSibling?.textContent?.trim(); + const snippet = titleElem.nextElementSibling?.textContent?.trim(); + + const innerSpans = Array.from(titleElem.parentElement?.querySelectorAll('span') || []); + const date = innerSpans[innerSpans.length - 1]?.textContent?.trim(); + + return { + link: url, + title, + source, + date, + snippet, + variant: 'news', + }; + }).filter(Boolean) as WebSearchEntry[]; +} \ No newline at end of file diff --git a/src/services/serp/internal.ts b/src/services/serp/internal.ts new file mode 100644 index 0000000..1303369 --- /dev/null +++ b/src/services/serp/internal.ts @@ -0,0 +1,77 @@ + +import { singleton } from 'tsyringe'; +import { GlobalLogger } from '../logger'; +import { SecretExposer } from '../../shared/services/secrets'; +import { AsyncLocalContext } from '../async-context'; +import { SerperSearchQueryParams } from '../../shared/3rd-party/serper-search'; +import { BlackHoleDetector } from '../blackhole-detector'; +import { AsyncService } from 'civkit/async-service'; +import { JinaSerpApiHTTP } from '../../shared/3rd-party/internal-serp'; +import { WebSearchEntry } from './compat'; + +@singleton() +export class InternalJinaSerpService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + client!: JinaSerpApiHTTP; + + constructor( + protected globalLogger: GlobalLogger, + protected secretExposer: SecretExposer, + protected threadLocal: AsyncLocalContext, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + + this.client = new JinaSerpApiHTTP(this.secretExposer.JINA_SERP_API_KEY); + } + + + async doSearch(variant: 'web' | 'images' | 'news', query: SerperSearchQueryParams) { + this.logger.debug(`Doing external search`, query); + let results; + switch (variant) { + // case 'images': { + // const r = await this.client.imageSearch(query); + + // results = r.parsed.images; + // break; + // } + // case 'news': { + // const r = await this.client.newsSearch(query); + + // results = r.parsed.news; + // break; + // } + case 'web': + default: { + const r = await this.client.webSearch(query); + + results = r.parsed.results?.map((x) => ({ ...x, link: x.url })); + break; + } + } + + this.blackHoleDetector.itWorked(); + + return results as WebSearchEntry[]; + } + + + async webSearch(query: SerperSearchQueryParams) { + return this.doSearch('web', query); + } + async imageSearch(query: SerperSearchQueryParams) { + return this.doSearch('images', query); + } + async newsSearch(query: SerperSearchQueryParams) { + return this.doSearch('news', query); + } + +} diff --git a/src/services/serp/puppeteer.ts b/src/services/serp/puppeteer.ts new file mode 100644 index 0000000..40a0932 --- /dev/null +++ b/src/services/serp/puppeteer.ts @@ -0,0 +1,694 @@ +import _ from 'lodash'; +import { readFile } from 'fs/promises'; +import { container, singleton } from 'tsyringe'; + +import type { Browser, CookieParam, GoToOptions, Page, Viewport } from 'puppeteer'; +import type { Cookie } from 'set-cookie-parser'; +import puppeteer, { TimeoutError } from 'puppeteer'; + +import { Defer } from 'civkit/defer'; +import { AssertionFailureError, ParamValidationError } from 'civkit/civ-rpc'; +import { AsyncService } from 'civkit/async-service'; +import { FancyFile } from 'civkit/fancy-file'; +import { delay } from 'civkit/timeout'; + +import { SecurityCompromiseError, ServiceCrashedError, ServiceNodeResourceDrainError } from '../../shared/lib/errors'; +import { CurlControl } from '../curl'; +import { AsyncLocalContext } from '../async-context'; +import { GlobalLogger } from '../logger'; +import { minimalStealth } from '../minimal-stealth'; +import { BlackHoleDetector } from '../blackhole-detector'; + + +export interface ScrappingOptions { + proxyUrl?: string; + cookies?: Cookie[]; + overrideUserAgent?: string; + timeoutMs?: number; + locale?: string; + referer?: string; + extraHeaders?: Record; + viewport?: Viewport; + proxyResources?: boolean; + allocProxy?: string; + + sideLoad?: { + impersonate: { + [url: string]: { + status: number; + headers: { [k: string]: string | string[]; }; + contentType?: string; + body?: FancyFile; + }; + }; + proxyOrigin: { [origin: string]: string; }; + }; + +} + +const SIMULATE_SCROLL = ` +(function () { + function createIntersectionObserverEntry(target, isIntersecting, timestamp) { + const targetRect = target.getBoundingClientRect(); + const record = { + target, + isIntersecting, + time: timestamp, + // If intersecting, intersectionRect matches boundingClientRect + // If not intersecting, intersectionRect is empty (0x0) + intersectionRect: isIntersecting + ? targetRect + : new DOMRectReadOnly(0, 0, 0, 0), + // Current bounding client rect of the target + boundingClientRect: targetRect, + // Intersection ratio is either 0 (not intersecting) or 1 (fully intersecting) + intersectionRatio: isIntersecting ? 1 : 0, + // Root bounds (viewport in our case) + rootBounds: new DOMRectReadOnly( + 0, + 0, + window.innerWidth, + window.innerHeight + ) + }; + Object.setPrototypeOf(record, window.IntersectionObserverEntry.prototype); + return record; + } + function cloneIntersectionObserverEntry(entry) { + const record = { + target: entry.target, + isIntersecting: entry.isIntersecting, + time: entry.time, + intersectionRect: entry.intersectionRect, + boundingClientRect: entry.boundingClientRect, + intersectionRatio: entry.intersectionRatio, + rootBounds: entry.rootBounds + }; + Object.setPrototypeOf(record, window.IntersectionObserverEntry.prototype); + return record; + } + const orig = window.IntersectionObserver; + const kCallback = Symbol('callback'); + const kLastEntryMap = Symbol('lastEntryMap'); + const liveObservers = new Map(); + class MangledIntersectionObserver extends orig { + constructor(callback, options) { + super((entries, observer) => { + const lastEntryMap = observer[kLastEntryMap]; + const lastEntry = entries[entries.length - 1]; + lastEntryMap.set(lastEntry.target, lastEntry); + return callback(entries, observer); + }, options); + this[kCallback] = callback; + this[kLastEntryMap] = new WeakMap(); + liveObservers.set(this, new Set()); + } + disconnect() { + liveObservers.get(this)?.clear(); + liveObservers.delete(this); + return super.disconnect(); + } + observe(target) { + const observer = liveObservers.get(this); + observer?.add(target); + return super.observe(target); + } + unobserve(target) { + const observer = liveObservers.get(this); + observer?.delete(target); + return super.unobserve(target); + } + } + Object.defineProperty(MangledIntersectionObserver, 'name', { value: 'IntersectionObserver', writable: false }); + window.IntersectionObserver = MangledIntersectionObserver; + function simulateScroll() { + for (const [observer, targets] of liveObservers.entries()) { + const t0 = performance.now(); + for (const target of targets) { + const entry = createIntersectionObserverEntry(target, true, t0); + observer[kCallback]([entry], observer); + setTimeout(() => { + const t1 = performance.now(); + const lastEntry = observer[kLastEntryMap].get(target); + if (!lastEntry) { + return; + } + const entry2 = { ...cloneIntersectionObserverEntry(lastEntry), time: t1 }; + observer[kCallback]([entry2], observer); + }); + } + } + } + window.simulateScroll = simulateScroll; +})(); +`; + +const MUTATION_IDLE_WATCH = ` +(function () { + let timeout; + const sendMsg = ()=> { + document.dispatchEvent(new CustomEvent('mutationIdle')); + }; + + const cb = () => { + if (timeout) { + clearTimeout(timeout); + timeout = setTimeout(sendMsg, 200); + } + }; + const mutationObserver = new MutationObserver(cb); + + document.addEventListener('DOMContentLoaded', () => { + mutationObserver.observe(document.documentElement, { + childList: true, + subtree: true, + }); + timeout = setTimeout(sendMsg, 200); + }, { once: true }) +})(); +`; + +const SCRIPT_TO_INJECT_INTO_FRAME = ` +${SIMULATE_SCROLL} +${MUTATION_IDLE_WATCH} +(${minimalStealth.toString()})(); + +(function(){ + +let lastMutationIdle = 0; +let initialAnalytics; +document.addEventListener('mutationIdle', ()=> lastMutationIdle = Date.now()); + +function waitForSelector(selectorText) { + return new Promise((resolve) => { + const existing = document.querySelector(selectorText); + if (existing) { + resolve(existing); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + const observer = new MutationObserver(() => { + const elem = document.querySelector(selectorText); + if (elem) { + resolve(document.querySelector(selectorText)); + observer.disconnect(); + } + }); + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + }); + return; + } + const observer = new MutationObserver(() => { + const elem = document.querySelector(selectorText); + if (elem) { + resolve(document.querySelector(selectorText)); + observer.disconnect(); + } + }); + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + }); +} +window.waitForSelector = waitForSelector; +})(); +`; + +@singleton() +export class SERPSpecializedPuppeteerControl extends AsyncService { + + _sn = 0; + browser!: Browser; + logger = this.globalLogger.child({ service: this.constructor.name }); + + __loadedPage: Page[] = []; + + finalizerMap = new WeakMap>(); + snMap = new WeakMap(); + livePages = new Set(); + lastPageCratedAt: number = 0; + ua: string = ''; + effectiveUA: string = ''; + + protected _REPORT_FUNCTION_NAME = 'bingo'; + + lifeCycleTrack = new WeakMap(); + + constructor( + protected globalLogger: GlobalLogger, + protected asyncLocalContext: AsyncLocalContext, + protected curlControl: CurlControl, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + this.setMaxListeners(Infinity); + + let crippledTimes = 0; + this.on('crippled', () => { + crippledTimes += 1; + this.__loadedPage.length = 0; + this.livePages.clear(); + if (crippledTimes > 5) { + process.nextTick(() => { + this.emit('error', new Error('Browser crashed too many times, quitting...')); + // process.exit(1); + }); + } + }); + } + + override async init() { + await this.dependencyReady(); + if (process.env.NODE_ENV?.includes('dry-run')) { + this.emit('ready'); + return; + } + + if (this.browser) { + if (this.browser.connected) { + await this.browser.close(); + } else { + this.browser.process()?.kill('SIGKILL'); + } + } + this.browser = await puppeteer.launch({ + timeout: 10_000, + headless: !Boolean(process.env.DEBUG_BROWSER), + executablePath: process.env.OVERRIDE_CHROME_EXECUTABLE_PATH, + args: [ + '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled' + ] + }).catch((err: any) => { + this.logger.error(`Unknown firebase issue, just die fast.`, { err }); + process.nextTick(() => { + this.emit('error', err); + // process.exit(1); + }); + return Promise.reject(err); + }); + this.browser.once('disconnected', () => { + this.logger.warn(`Browser disconnected`); + if (this.browser) { + this.emit('crippled'); + } + process.nextTick(() => this.serviceReady()); + }); + this.ua = await this.browser.userAgent(); + this.logger.info(`Browser launched: ${this.browser.process()?.pid}, ${this.ua}`); + this.effectiveUA = this.ua.replace(/Headless/i, '').replace('Mozilla/5.0 (X11; Linux x86_64)', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'); + this.curlControl.impersonateChrome(this.effectiveUA); + + await this.newPage('beware_deadlock').then((r) => this.__loadedPage.push(r)); + + this.emit('ready'); + } + + async newPage(bewareDeadLock: any = false) { + if (!bewareDeadLock) { + await this.serviceReady(); + } + const sn = this._sn++; + let page; + try { + const dedicatedContext = await this.browser.createBrowserContext(); + page = await dedicatedContext.newPage(); + } catch (err: any) { + this.logger.warn(`Failed to create page ${sn}`, { err }); + this.browser.process()?.kill('SIGKILL'); + throw new ServiceNodeResourceDrainError(`This specific worker node failed to open a new page, try again.`); + } + const preparations = []; + + preparations.push(page.setUserAgent(this.effectiveUA)); + // preparations.push(page.setUserAgent(`Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)`)); + // preparations.push(page.setUserAgent(`Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0; +https://openai.com/gptbot)`)); + preparations.push(page.setBypassCSP(true)); + preparations.push(page.setViewport({ width: 1024, height: 1024 })); + preparations.push(page.exposeFunction(this._REPORT_FUNCTION_NAME, (thing: T) => { + page.emit(this._REPORT_FUNCTION_NAME, thing); + })); + preparations.push(page.exposeFunction('setViewport', (viewport: Viewport | null) => { + page.setViewport(viewport).catch(() => undefined); + })); + preparations.push(page.evaluateOnNewDocument(SCRIPT_TO_INJECT_INTO_FRAME)); + + await Promise.all(preparations); + + this.snMap.set(page, sn); + this.logger.debug(`Page ${sn} created.`); + this.lastPageCratedAt = Date.now(); + this.livePages.add(page); + + return page; + } + + async getNextPage() { + let thePage: Page | undefined; + if (this.__loadedPage.length) { + thePage = this.__loadedPage.shift(); + if (this.__loadedPage.length <= 1) { + process.nextTick(() => { + this.newPage() + .then((r) => this.__loadedPage.push(r)) + .catch((err) => { + this.logger.warn(`Failed to load new page ahead of time`, { err }); + }); + }); + } + } + + if (!thePage) { + thePage = await this.newPage(); + } + + const timer = setTimeout(() => { + this.logger.warn(`Page is not allowed to live past 5 minutes, ditching page ${this.snMap.get(thePage!)}...`); + this.ditchPage(thePage!); + }, 300 * 1000); + + this.finalizerMap.set(thePage, timer); + + return thePage; + } + + async ditchPage(page: Page) { + if (this.finalizerMap.has(page)) { + clearTimeout(this.finalizerMap.get(page)!); + this.finalizerMap.delete(page); + } + if (page.isClosed()) { + return; + } + const sn = this.snMap.get(page); + this.logger.debug(`Closing page ${sn}`); + await Promise.race([ + (async () => { + const ctx = page.browserContext(); + try { + await page.close(); + } finally { + await ctx.close(); + } + })(), + delay(5000) + ]).catch((err) => { + this.logger.error(`Failed to destroy page ${sn}`, { err }); + }); + this.livePages.delete(page); + } + + async controlledScrap(parsedUrl: URL, func: (this: void) => Promise, options: ScrappingOptions = {}): Promise { + // parsedUrl.search = ''; + const url = parsedUrl.toString(); + const page = await this.getNextPage(); + this.lifeCycleTrack.set(page, this.asyncLocalContext.ctx); + page.on('response', (_resp) => { + this.blackHoleDetector.itWorked(); + }); + page.on('request', async (req) => { + if (req.isInterceptResolutionHandled()) { + return; + }; + const reqUrlParsed = new URL(req.url()); + if (!reqUrlParsed.protocol.startsWith('http')) { + const overrides = req.continueRequestOverrides(); + + return req.continue(overrides, 0); + } + const typ = req.resourceType(); + if (typ === 'media' || typ === 'font' || typ === 'image' || typ === 'stylesheet') { + // Non-cooperative answer to block all media requests. + return req.abort('blockedbyclient'); + } + if (!options.proxyResources) { + const isDocRequest = ['document', 'xhr', 'fetch', 'websocket', 'prefetch', 'eventsource', 'ping'].includes(typ); + if (!isDocRequest) { + if (options.extraHeaders) { + const overrides = req.continueRequestOverrides(); + const continueArgs = [{ + ...overrides, + headers: { + ...req.headers(), + ...overrides?.headers, + ...options.extraHeaders, + } + }, 1] as const; + + return req.continue(continueArgs[0], continueArgs[1]); + } + const overrides = req.continueRequestOverrides(); + + return req.continue(overrides, 0); + } + } + const sideload = options.sideLoad; + + const impersonate = sideload?.impersonate[reqUrlParsed.href]; + if (impersonate) { + let body; + if (impersonate.body) { + body = await readFile(await impersonate.body.filePath); + if (req.isInterceptResolutionHandled()) { + return; + } + } + return req.respond({ + status: impersonate.status, + headers: impersonate.headers, + contentType: impersonate.contentType, + body: body ? Uint8Array.from(body) : undefined, + }, 999); + } + + const proxy = options.proxyUrl || sideload?.proxyOrigin?.[reqUrlParsed.origin]; + const ctx = this.lifeCycleTrack.get(page); + if (proxy && ctx) { + return await this.asyncLocalContext.bridge(ctx, async () => { + try { + const curled = await this.curlControl.sideLoad(reqUrlParsed, { + ...options, + method: req.method(), + body: req.postData(), + extraHeaders: { + ...req.headers(), + ...options.extraHeaders, + }, + proxyUrl: proxy + }); + if (req.isInterceptResolutionHandled()) { + return; + }; + + if (curled.chain.length === 1) { + if (!curled.file) { + return req.respond({ + status: curled.status, + headers: _.omit(curled.headers, 'result'), + contentType: curled.contentType, + }, 3); + } + const body = await readFile(await curled.file.filePath); + if (req.isInterceptResolutionHandled()) { + return; + }; + return req.respond({ + status: curled.status, + headers: _.omit(curled.headers, 'result'), + contentType: curled.contentType, + body: Uint8Array.from(body), + }, 3); + } + options.sideLoad ??= curled.sideLoadOpts; + _.merge(options.sideLoad, curled.sideLoadOpts); + const firstReq = curled.chain[0]; + + return req.respond({ + status: firstReq.result!.code, + headers: _.omit(firstReq, 'result'), + }, 3); + } catch (err: any) { + this.logger.warn(`Failed to sideload browser request ${reqUrlParsed.origin}`, { href: reqUrlParsed.href, err, proxy }); + } + if (req.isInterceptResolutionHandled()) { + return; + }; + const overrides = req.continueRequestOverrides(); + const continueArgs = [{ + ...overrides, + headers: { + ...req.headers(), + ...overrides?.headers, + ...options.extraHeaders, + } + }, 1] as const; + + return req.continue(continueArgs[0], continueArgs[1]); + }); + } + + if (req.isInterceptResolutionHandled()) { + return; + }; + const overrides = req.continueRequestOverrides(); + const continueArgs = [{ + ...overrides, + headers: { + ...req.headers(), + ...overrides?.headers, + ...options.extraHeaders, + } + }, 1] as const; + + return req.continue(continueArgs[0], continueArgs[1]); + }); + await page.setRequestInterception(true); + + const sn = this.snMap.get(page); + this.logger.info(`Page ${sn}: Scraping ${url}`, { url }); + + await page.evaluateOnNewDocument(`(function () { +if (window.top !== window.self) { + return; +} +const func = ${func.toString()}; + +func().then((result) => { + window.${this._REPORT_FUNCTION_NAME}({data: result}); +}).catch((err) => { + window.${this._REPORT_FUNCTION_NAME}({err: err}); +}); + +})();`); + + if (options.locale) { + // Add headers via request interception to walk around this bug + // https://github.com/puppeteer/puppeteer/issues/10235 + // await page.setExtraHTTPHeaders({ + // 'Accept-Language': options.locale + // }); + + await page.evaluateOnNewDocument(() => { + Object.defineProperty(navigator, "language", { + get: function () { + return options.locale; + } + }); + Object.defineProperty(navigator, "languages", { + get: function () { + return [options.locale]; + } + }); + }); + } + + if (options.cookies) { + const mapped = options.cookies.map((x) => { + const draft: CookieParam = { + name: x.name, + value: encodeURIComponent(x.value), + secure: x.secure, + domain: x.domain, + path: x.path, + expires: x.expires ? Math.floor(x.expires.valueOf() / 1000) : undefined, + sameSite: x.sameSite as any, + }; + if (!draft.expires && x.maxAge) { + draft.expires = Math.floor(Date.now() / 1000) + x.maxAge; + } + if (!draft.domain) { + draft.url = parsedUrl.toString(); + } + + return draft; + }); + try { + await page.setCookie(...mapped); + } catch (err: any) { + this.logger.warn(`Page ${sn}: Failed to set cookies`, { err }); + throw new ParamValidationError({ + path: 'cookies', + message: `Failed to set cookies: ${err?.message}` + }); + } + } + if (options.overrideUserAgent) { + await page.setUserAgent(options.overrideUserAgent); + } + if (options.viewport) { + await page.setViewport(options.viewport); + } + + const resultDeferred = Defer(); + const crippleListener = () => resultDeferred.reject(new ServiceCrashedError({ message: `Browser crashed, try again` })); + this.once('crippled', crippleListener); + resultDeferred.promise.finally(() => { + this.off('crippled', crippleListener); + }); + const hdl = (s: { + err?: any; + data?: T; + }) => { + if (s.err) { + resultDeferred.reject(s.err); + } + resultDeferred.resolve(s.data); + }; + page.on(this._REPORT_FUNCTION_NAME, hdl as any); + page.once('abuse', (event: any) => { + this.emit('abuse', { ...event, url: parsedUrl }); + + resultDeferred.reject( + new SecurityCompromiseError(`Abuse detected: ${event.reason}`) + ); + }); + + const timeout = options.timeoutMs || 30_000; + const goToOptions: GoToOptions = { + waitUntil: ['load', 'domcontentloaded', 'networkidle0'], + timeout, + }; + + if (options.referer) { + goToOptions.referer = options.referer; + } + + + const gotoPromise = page.goto(url, goToOptions) + .catch((err) => { + if (err instanceof TimeoutError) { + this.logger.warn(`Page ${sn}: Browsing of ${url} timed out`, { err }); + return new AssertionFailureError({ + message: `Failed to goto ${url}: ${err}`, + cause: err, + }); + } + + this.logger.warn(`Page ${sn}: Browsing of ${url} aborted`, { err }); + return undefined; + }).then(async (r) => { + await delay(5000); + resultDeferred.reject(new TimeoutError(`Control function did not respond in time`)); + return r; + }); + + try { + await Promise.race([resultDeferred.promise, gotoPromise]); + + return resultDeferred.promise; + } finally { + page.off(this._REPORT_FUNCTION_NAME, hdl as any); + this.ditchPage(page); + resultDeferred.resolve(); + } + } + +} + +const puppeteerControl = container.resolve(SERPSpecializedPuppeteerControl); + +export default puppeteerControl; diff --git a/src/services/serp/serper.ts b/src/services/serp/serper.ts new file mode 100644 index 0000000..51317ea --- /dev/null +++ b/src/services/serp/serper.ts @@ -0,0 +1,165 @@ + +import { singleton } from 'tsyringe'; +import { GlobalLogger } from '../logger'; +import { SecretExposer } from '../../shared/services/secrets'; +import { AsyncLocalContext } from '../async-context'; +import { SerperBingHTTP, SerperGoogleHTTP, SerperImageSearchResponse, SerperNewsSearchResponse, SerperSearchQueryParams, SerperWebSearchResponse } from '../../shared/3rd-party/serper-search'; +import { BlackHoleDetector } from '../blackhole-detector'; +import { Context } from '../registry'; +import { AsyncService } from 'civkit/async-service'; +import { AutoCastable, Prop, RPC_CALL_ENVIRONMENT } from 'civkit/civ-rpc'; + +@singleton() +export class SerperGoogleSearchService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + client!: SerperGoogleHTTP; + + constructor( + protected globalLogger: GlobalLogger, + protected secretExposer: SecretExposer, + protected threadLocal: AsyncLocalContext, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + + this.client = new SerperGoogleHTTP(this.secretExposer.SERPER_SEARCH_API_KEY); + } + + + doSearch(variant: 'web', query: SerperSearchQueryParams): Promise; + doSearch(variant: 'images', query: SerperSearchQueryParams): Promise; + doSearch(variant: 'news', query: SerperSearchQueryParams): Promise; + async doSearch(variant: 'web' | 'images' | 'news', query: SerperSearchQueryParams) { + this.logger.debug(`Doing external search`, query); + let results; + switch (variant) { + case 'images': { + const r = await this.client.imageSearch(query); + + results = r.parsed.images; + break; + } + case 'news': { + const r = await this.client.newsSearch(query); + + results = r.parsed.news; + break; + } + case 'web': + default: { + const r = await this.client.webSearch(query); + + results = r.parsed.organic; + break; + } + } + + this.blackHoleDetector.itWorked(); + + return results; + } + + + async webSearch(query: SerperSearchQueryParams) { + return this.doSearch('web', query); + } + async imageSearch(query: SerperSearchQueryParams) { + return this.doSearch('images', query); + } + async newsSearch(query: SerperSearchQueryParams) { + return this.doSearch('news', query); + } + +} + +@singleton() +export class SerperBingSearchService extends SerperGoogleSearchService { + override client!: SerperBingHTTP; + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + + this.client = new SerperBingHTTP(this.secretExposer.SERPER_SEARCH_API_KEY); + } +} + +export class GoogleSearchExplicitOperatorsDto extends AutoCastable { + @Prop({ + arrayOf: String, + desc: `Returns web pages with a specific file extension. Example: to find the Honda GX120 Owner’s manual in PDF, type “Honda GX120 ownners manual ext:pdf”.` + }) + ext?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages created in the specified file type. Example: to find a web page created in PDF format about the evaluation of age-related cognitive changes, type “evaluation of age cognitive changes filetype:pdf”.` + }) + filetype?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns webpages containing the specified term in the title of the page. Example: to find pages about SEO conferences making sure the results contain 2023 in the title, type “seo conference intitle:2023”.` + }) + intitle?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages written in the specified language. The language code must be in the ISO 639-1 two-letter code format. Example: to find information on visas only in Spanish, type “visas lang:es”.` + }) + loc?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages coming only from a specific web site. Example: to find information about Goggles only on Brave pages, type “goggles site:brave.com”.` + }) + site?: string | string[]; + + addTo(searchTerm: string) { + const chunks = []; + for (const [key, value] of Object.entries(this)) { + if (value) { + const values = Array.isArray(value) ? value : [value]; + const textValue = values.map((v) => `${key}:${v}`).join(' OR '); + if (textValue) { + chunks.push(textValue); + } + } + } + const opPart = chunks.length > 1 ? chunks.map((x) => `(${x})`).join(' AND ') : chunks; + + if (opPart.length) { + return [searchTerm, opPart].join(' '); + } + + return searchTerm; + } + + static override from(input: any) { + const instance = super.from(input) as GoogleSearchExplicitOperatorsDto; + const ctx = Reflect.get(input, RPC_CALL_ENVIRONMENT) as Context | undefined; + + const params = ['ext', 'filetype', 'intitle', 'loc', 'site']; + + for (const p of params) { + const customValue = ctx?.get(`x-${p}`) || ctx?.get(`${p}`); + if (!customValue) { + continue; + } + + const filtered = customValue.split(', ').filter(Boolean); + if (filtered.length) { + Reflect.set(instance, p, filtered); + } + } + + return instance; + } +} diff --git a/src/services/serper-search.ts b/src/services/serper-search.ts new file mode 100644 index 0000000..59698de --- /dev/null +++ b/src/services/serper-search.ts @@ -0,0 +1,212 @@ +import { AsyncService, AutoCastable, DownstreamServiceFailureError, Prop, RPC_CALL_ENVIRONMENT, delay, marshalErrorLike } from 'civkit'; +import { singleton } from 'tsyringe'; +import { GlobalLogger } from './logger'; +import { SecretExposer } from '../shared/services/secrets'; +import { AsyncLocalContext } from './async-context'; +import { SerperBingHTTP, SerperGoogleHTTP, SerperImageSearchResponse, SerperNewsSearchResponse, SerperSearchQueryParams, SerperWebSearchResponse } from '../shared/3rd-party/serper-search'; +import { BlackHoleDetector } from './blackhole-detector'; +import { Context } from './registry'; +import { ServiceBadAttemptError } from '../shared'; + +@singleton() +export class SerperSearchService extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + serperGoogleSearchHTTP!: SerperGoogleHTTP; + serperBingSearchHTTP!: SerperBingHTTP; + + constructor( + protected globalLogger: GlobalLogger, + protected secretExposer: SecretExposer, + protected threadLocal: AsyncLocalContext, + protected blackHoleDetector: BlackHoleDetector, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + + this.serperGoogleSearchHTTP = new SerperGoogleHTTP(this.secretExposer.SERPER_SEARCH_API_KEY); + this.serperBingSearchHTTP = new SerperBingHTTP(this.secretExposer.SERPER_SEARCH_API_KEY); + } + + *iterClient() { + const preferBingSearch = this.threadLocal.get('bing-preferred'); + if (preferBingSearch) { + yield this.serperBingSearchHTTP; + } + while (true) { + yield this.serperGoogleSearchHTTP; + } + } + + doSearch(variant: 'web', query: SerperSearchQueryParams): Promise; + doSearch(variant: 'images', query: SerperSearchQueryParams): Promise; + doSearch(variant: 'news', query: SerperSearchQueryParams): Promise; + async doSearch(variant: 'web' | 'images' | 'news', query: SerperSearchQueryParams) { + const clientIt = this.iterClient(); + let client = clientIt.next().value; + if (!client) { + throw new Error(`Error iterating serper client`); + } + + let maxTries = 3; + + while (maxTries--) { + const t0 = Date.now(); + try { + this.logger.debug(`Doing external search`, query); + let r; + switch (variant) { + case 'images': { + r = await client.imageSearch(query); + const nextClient = clientIt.next().value; + if (nextClient && nextClient !== client) { + const results = r.parsed.images; + if (!results.length) { + client = nextClient; + throw new ServiceBadAttemptError('No results found'); + } + } + + break; + } + case 'news': { + r = await client.newsSearch(query); + const nextClient = clientIt.next().value; + if (nextClient && nextClient !== client) { + const results = r.parsed.news; + if (!results.length) { + client = nextClient; + throw new ServiceBadAttemptError('No results found'); + } + } + + break; + } + case 'web': + default: { + r = await client.webSearch(query); + const nextClient = clientIt.next().value; + if (nextClient && nextClient !== client) { + const results = r.parsed.organic; + if (!results.length) { + client = nextClient; + throw new ServiceBadAttemptError('No results found'); + } + } + + break; + } + } + const dt = Date.now() - t0; + this.blackHoleDetector.itWorked(); + this.logger.debug(`External search took ${dt}ms`, { searchDt: dt, variant }); + + return r.parsed; + } catch (err: any) { + const dt = Date.now() - t0; + this.logger.error(`${variant} search failed: ${err?.message}`, { searchDt: dt, err: marshalErrorLike(err) }); + if (err?.status === 429) { + await delay(500 + 1000 * Math.random()); + continue; + } + if (err instanceof ServiceBadAttemptError) { + continue; + } + + throw new DownstreamServiceFailureError({ message: `Search(${variant}) failed` }); + } + } + + throw new DownstreamServiceFailureError({ message: `Search(${variant}) failed` }); + } + + + async webSearch(query: SerperSearchQueryParams) { + return this.doSearch('web', query); + } + async imageSearch(query: SerperSearchQueryParams) { + return this.doSearch('images', query); + } + async newsSearch(query: SerperSearchQueryParams) { + return this.doSearch('news', query); + } + +} + +export class GoogleSearchExplicitOperatorsDto extends AutoCastable { + @Prop({ + arrayOf: String, + desc: `Returns web pages with a specific file extension. Example: to find the Honda GX120 Owner’s manual in PDF, type “Honda GX120 ownners manual ext:pdf”.` + }) + ext?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages created in the specified file type. Example: to find a web page created in PDF format about the evaluation of age-related cognitive changes, type “evaluation of age cognitive changes filetype:pdf”.` + }) + filetype?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns webpages containing the specified term in the title of the page. Example: to find pages about SEO conferences making sure the results contain 2023 in the title, type “seo conference intitle:2023”.` + }) + intitle?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages written in the specified language. The language code must be in the ISO 639-1 two-letter code format. Example: to find information on visas only in Spanish, type “visas lang:es”.` + }) + loc?: string | string[]; + + @Prop({ + arrayOf: String, + desc: `Returns web pages coming only from a specific web site. Example: to find information about Goggles only on Brave pages, type “goggles site:brave.com”.` + }) + site?: string | string[]; + + addTo(searchTerm: string) { + const chunks = []; + for (const [key, value] of Object.entries(this)) { + if (value) { + const values = Array.isArray(value) ? value : [value]; + const textValue = values.map((v) => `${key}:${v}`).join(' OR '); + if (textValue) { + chunks.push(textValue); + } + } + } + const opPart = chunks.length > 1 ? chunks.map((x) => `(${x})`).join(' AND ') : chunks; + + if (opPart.length) { + return [searchTerm, opPart].join(' '); + } + + return searchTerm; + } + + static override from(input: any) { + const instance = super.from(input) as GoogleSearchExplicitOperatorsDto; + const ctx = Reflect.get(input, RPC_CALL_ENVIRONMENT) as Context | undefined; + + const params = ['ext', 'filetype', 'intitle', 'loc', 'site']; + + for (const p of params) { + const customValue = ctx?.get(`x-${p}`) || ctx?.get(`${p}`); + if (!customValue) { + continue; + } + + const filtered = customValue.split(', ').filter(Boolean); + if (filtered.length) { + Reflect.set(instance, p, filtered); + } + } + + return instance; + } +} diff --git a/src/services/snapshot-formatter.ts b/src/services/snapshot-formatter.ts new file mode 100644 index 0000000..3064a88 --- /dev/null +++ b/src/services/snapshot-formatter.ts @@ -0,0 +1,865 @@ +import { randomUUID } from 'crypto'; +import { container, singleton } from 'tsyringe'; +import { AssertionFailureError, AsyncService, DataStreamBrokenError, FancyFile, HashManager, marshalErrorLike } from 'civkit'; +import TurndownService, { Filter, Rule } from 'turndown'; +import { GlobalLogger } from './logger'; +import { PageSnapshot } from './puppeteer'; +import { FirebaseStorageBucketControl } from '../shared/services/firebase-storage-bucket'; +import { AsyncContext } from '../shared/services/async-context'; +import { Threaded } from '../services/threaded'; +import { JSDomControl } from './jsdom'; +import { AltTextService } from './alt-text'; +import { PDFExtractor } from './pdf-extract'; +import { cleanAttribute } from '../utils/misc'; +import _ from 'lodash'; +import { STATUS_CODES } from 'http'; +import type { CrawlerOptions } from '../dto/crawler-options'; +import { readFile } from '../utils/encoding'; +import { pathToFileURL } from 'url'; +import { countGPTToken } from '../shared/utils/openai'; + + +export interface FormattedPage { + title?: string; + description?: string; + url?: string; + content?: string; + publishedTime?: string; + html?: string; + text?: string; + screenshotUrl?: string; + screenshot?: Buffer; + pageshotUrl?: string; + pageshot?: Buffer; + links?: { [k: string]: string; } | [string, string][]; + images?: { [k: string]: string; } | [string, string][]; + warning?: string; + usage?: { + total_tokens?: number; + totalTokens?: number; + tokens?: number; + }; + + textRepresentation?: string; + + [Symbol.dispose]?: () => void; +} + +export const md5Hasher = new HashManager('md5', 'hex'); + +const gfmPlugin = require('turndown-plugin-gfm'); +const highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/; + +export function highlightedCodeBlock(turndownService: TurndownService) { + turndownService.addRule('highlightedCodeBlock', { + filter: (node) => { + return ( + node.nodeName === 'DIV' && + node.firstChild?.nodeName === 'PRE' && + highlightRegExp.test(node.className) + ); + }, + replacement: (_content, node, options) => { + const className = (node as any).className || ''; + const language = (className.match(highlightRegExp) || [null, ''])[1]; + + return ( + '\n\n' + options.fence + language + '\n' + + node.firstChild!.textContent + + '\n' + options.fence + '\n\n' + ); + } + }); +} + +@singleton() +export class SnapshotFormatter extends AsyncService { + + logger = this.globalLogger.child({ service: this.constructor.name }); + + gfmPlugin = [gfmPlugin.tables, highlightedCodeBlock, gfmPlugin.strikethrough, gfmPlugin.taskListItems]; + gfmNoTable = [highlightedCodeBlock, gfmPlugin.strikethrough, gfmPlugin.taskListItems]; + + constructor( + protected globalLogger: GlobalLogger, + protected jsdomControl: JSDomControl, + protected altTextService: AltTextService, + protected pdfExtractor: PDFExtractor, + protected threadLocal: AsyncContext, + protected firebaseObjectStorage: FirebaseStorageBucketControl, + ) { + super(...arguments); + } + + override async init() { + await this.dependencyReady(); + this.emit('ready'); + } + + + @Threaded() + async formatSnapshot(mode: string | 'markdown' | 'html' | 'text' | 'screenshot' | 'pageshot', snapshot: PageSnapshot & { + screenshotUrl?: string; + pageshotUrl?: string; + }, nominalUrl?: URL, urlValidMs = 3600 * 1000 * 4) { + const t0 = Date.now(); + const f = { + ...(await this.getGeneralSnapshotMixins(snapshot)), + }; + let modeOK = false; + + if (mode.includes('screenshot')) { + modeOK = true; + if (snapshot.screenshot && !snapshot.screenshotUrl) { + const fid = `instant-screenshots/${randomUUID()}`; + await this.firebaseObjectStorage.saveFile(fid, snapshot.screenshot, { + metadata: { + contentType: 'image/png', + } + }); + snapshot.screenshotUrl = await this.firebaseObjectStorage.signDownloadUrl(fid, Date.now() + urlValidMs); + } + Object.assign(f, { + screenshotUrl: snapshot.screenshotUrl, + }); + + Object.defineProperty(f, 'textRepresentation', { value: `${f.screenshotUrl}\n`, enumerable: false, configurable: true }); + } + if (mode.includes('pageshot')) { + modeOK = true; + if (snapshot.pageshot && !snapshot.pageshotUrl) { + const fid = `instant-screenshots/${randomUUID()}`; + await this.firebaseObjectStorage.saveFile(fid, snapshot.pageshot, { + metadata: { + contentType: 'image/png', + } + }); + snapshot.pageshotUrl = await this.firebaseObjectStorage.signDownloadUrl(fid, Date.now() + urlValidMs); + } + Object.assign(f, { + html: snapshot.html, + pageshotUrl: snapshot.pageshotUrl, + }); + Object.defineProperty(f, 'textRepresentation', { value: `${f.pageshotUrl}\n`, enumerable: false, configurable: true }); + } + if (mode.includes('html')) { + modeOK = true; + Object.assign(f, { + html: snapshot.html, + }); + + Object.defineProperty(f, 'textRepresentation', { value: snapshot.html, enumerable: false, configurable: true }); + } + + let pdfMode = false; + // in case of Google Web Cache content + if (snapshot.pdfs?.length && (!snapshot.title || snapshot.title.startsWith('cache:'))) { + const pdf = await this.pdfExtractor.cachedExtract(snapshot.pdfs[0], + this.threadLocal.get('cacheTolerance'), + snapshot.pdfs[0].startsWith('http') ? undefined : snapshot.href, + ); + if (pdf) { + pdfMode = true; + snapshot.title = pdf.meta?.Title; + snapshot.text = pdf.text || snapshot.text; + snapshot.parsed = { + content: pdf.content, + textContent: pdf.content, + length: pdf.content?.length, + byline: pdf.meta?.Author, + lang: pdf.meta?.Language || undefined, + title: pdf.meta?.Title, + publishedTime: this.pdfExtractor.parsePdfDate(pdf.meta?.ModDate || pdf.meta?.CreationDate)?.toISOString(), + }; + } + } + + if (mode.includes('text')) { + modeOK = true; + Object.assign(f, { + text: snapshot.text, + }); + Object.defineProperty(f, 'textRepresentation', { value: snapshot.text, enumerable: false, configurable: true }); + } + + if (mode.includes('lm')) { + modeOK = true; + f.content = snapshot.parsed?.textContent; + } + + if (modeOK && (mode.includes('lm') || + (!mode.includes('markdown') && !mode.includes('content'))) + ) { + const dt = Date.now() - t0; + this.logger.debug(`Formatting took ${dt}ms`, { mode, url: nominalUrl?.toString(), dt }); + + const formatted: FormattedPage = { + title: (snapshot.parsed?.title || snapshot.title || '').trim(), + description: (snapshot.description || '').trim(), + url: nominalUrl?.toString() || snapshot.href?.trim(), + publishedTime: snapshot.parsed?.publishedTime || undefined, + }; + + Object.assign(f, formatted); + + return f; + } + + const imgDataUrlToObjectUrl = !Boolean(this.threadLocal.get('keepImgDataUrl')); + + let contentText = ''; + const imageSummary = {} as { [k: string]: string; }; + const imageIdxTrack = new Map(); + const uid = this.threadLocal.get('uid'); + + do { + if (pdfMode) { + contentText = (snapshot.parsed?.content || snapshot.text || '').trim(); + break; + } + + if ( + snapshot.maxElemDepth! > 256 || + (!uid && snapshot.elemCount! > 10_000) || + snapshot.elemCount! > 80_000 + ) { + this.logger.warn('Degrading to text to protect the server', { url: snapshot.href, elemDepth: snapshot.maxElemDepth, elemCount: snapshot.elemCount }); + contentText = (snapshot.text || '').trimEnd(); + break; + } + + const noGFMOpts = this.threadLocal.get('noGfm'); + const imageRetention = this.threadLocal.get('retainImages') as CrawlerOptions['retainImages']; + let imgIdx = 0; + const urlToAltMap: { [k: string]: string | undefined; } = {}; + const customRules: { [k: string]: Rule; } = { + 'img-retention': { + filter: 'img', + replacement: (_content: string, node: HTMLElement) => { + if (imageRetention === 'none') { + return ''; + } + const alt = cleanAttribute(node.getAttribute('alt')); + + if (imageRetention === 'alt') { + return alt ? `(Image ${++imgIdx}: ${alt})` : ''; + } + const originalSrc = (node.getAttribute('src') || '').trim(); + let linkPreferredSrc = originalSrc; + const maybeSrcSet: string = (node.getAttribute('srcset') || '').trim(); + if (!linkPreferredSrc && maybeSrcSet) { + linkPreferredSrc = maybeSrcSet.split(',').map((x) => x.trim()).filter(Boolean)[0]; + } + if (!linkPreferredSrc || linkPreferredSrc.startsWith('data:')) { + const dataSrc = (node.getAttribute('data-src') || '').trim(); + if (dataSrc && !dataSrc.startsWith('data:')) { + linkPreferredSrc = dataSrc; + } + } + + let src; + try { + src = new URL(linkPreferredSrc, snapshot.rebase || nominalUrl).toString(); + } catch (_err) { + void 0; + } + if (!src) { + return ''; + } + + const keySrc = (originalSrc.startsWith('data:') ? this.dataUrlToBlobUrl(originalSrc, snapshot.rebase) : src).trim(); + const mapped = urlToAltMap[keySrc]; + const imgSerial = ++imgIdx; + const idxArr = imageIdxTrack.has(keySrc) ? imageIdxTrack.get(keySrc)! : []; + idxArr.push(imgSerial); + imageIdxTrack.set(keySrc, idxArr); + + if (mapped) { + imageSummary[keySrc] = mapped || alt; + + if (imageRetention === 'alt_p') { + return `(Image ${imgSerial}: ${mapped || alt})`; + } + + if (imgDataUrlToObjectUrl) { + return `![Image ${imgSerial}: ${mapped || alt}](${keySrc})`; + } + + return `![Image ${imgSerial}: ${mapped || alt}](${src})`; + } else if (imageRetention === 'alt_p') { + return alt ? `(Image ${imgSerial}: ${alt})` : ''; + } + + imageSummary[keySrc] = alt || ''; + + if (imgDataUrlToObjectUrl) { + return alt ? `![Image ${imgSerial}: ${alt}](${keySrc})` : `![Image ${imgSerial}](${keySrc})`; + } + + return alt ? `![Image ${imgSerial}: ${alt}](${src})` : `![Image ${imgSerial}](${src})`; + } + } as Rule + }; + const optsMixin = { + url: snapshot.rebase || nominalUrl, + customRules, + customKeep: noGFMOpts === 'table' ? 'table' : undefined, + imgDataUrlToObjectUrl, + } as const; + + const jsDomElementOfHTML = this.jsdomControl.snippetToElement(snapshot.html, snapshot.href); + let toBeTurnedToMd = jsDomElementOfHTML; + let turnDownService = this.getTurndown({ ...optsMixin }); + if (!mode.includes('markdown') && snapshot.parsed?.content) { + const jsDomElementOfParsed = this.jsdomControl.snippetToElement(snapshot.parsed.content, snapshot.href); + const par1 = this.jsdomControl.runTurndown(turnDownService, jsDomElementOfHTML); + imgIdx = 0; + const par2 = snapshot.parsed.content ? this.jsdomControl.runTurndown(turnDownService, jsDomElementOfParsed) : ''; + + // If Readability did its job + if (par2.length >= 0.3 * par1.length) { + turnDownService = this.getTurndown({ noRules: true, ...optsMixin }); + imgIdx = 0; + if (snapshot.parsed.content) { + toBeTurnedToMd = jsDomElementOfParsed; + } + } + } + + if (!noGFMOpts) { + turnDownService = turnDownService.use(noGFMOpts === 'table' ? this.gfmNoTable : this.gfmPlugin); + } + + // _p is the special suffix for withGeneratedAlt + if (snapshot.imgs?.length && imageRetention?.endsWith('_p')) { + const tasks = _.uniqBy((snapshot.imgs || []), 'src').map(async (x) => { + const r = await this.altTextService.getAltText(x).catch((err: any) => { + this.logger.warn(`Failed to get alt text for ${x.src}`, { err: marshalErrorLike(err) }); + return undefined; + }); + if (r && x.src) { + // note x.src here is already rebased to absolute url by browser/upstream. + const keySrc = (x.src.startsWith('data:') ? this.dataUrlToBlobUrl(x.src, snapshot.rebase) : x.src).trim(); + urlToAltMap[keySrc] = r; + } + }); + + await Promise.all(tasks); + } + + if (toBeTurnedToMd) { + try { + contentText = this.jsdomControl.runTurndown(turnDownService, toBeTurnedToMd).trim(); + imgIdx = 0; + } catch (err) { + this.logger.warn(`Turndown failed to run, retrying without plugins`, { err }); + const vanillaTurnDownService = this.getTurndown({ ...optsMixin }); + try { + contentText = this.jsdomControl.runTurndown(vanillaTurnDownService, toBeTurnedToMd).trim(); + imgIdx = 0; + } catch (err2) { + this.logger.warn(`Turndown failed to run, giving up`, { err: err2 }); + } + } + } + + if ( + this.isPoorlyTransformed(contentText, toBeTurnedToMd) + && toBeTurnedToMd !== jsDomElementOfHTML + ) { + toBeTurnedToMd = jsDomElementOfHTML; + try { + contentText = this.jsdomControl.runTurndown(turnDownService, jsDomElementOfHTML).trim(); + imgIdx = 0; + } catch (err) { + this.logger.warn(`Turndown failed to run, retrying without plugins`, { err }); + const vanillaTurnDownService = this.getTurndown({ ...optsMixin }); + try { + contentText = this.jsdomControl.runTurndown(vanillaTurnDownService, jsDomElementOfHTML).trim(); + imgIdx = 0; + } catch (err2) { + this.logger.warn(`Turndown failed to run, giving up`, { err: err2 }); + } + } + } + if (mode === 'content' && this.isPoorlyTransformed(contentText, toBeTurnedToMd)) { + contentText = (snapshot.text || '').trimEnd(); + } + } while (false); + + const formatted: FormattedPage = { + title: (snapshot.parsed?.title || snapshot.title || '').trim(), + description: (snapshot.description || '').trim(), + url: nominalUrl?.toString() || snapshot.href?.trim(), + content: contentText, + publishedTime: snapshot.parsed?.publishedTime || undefined, + }; + + if (snapshot.status) { + const code = snapshot.status; + const n = code - 200; + if (n < 0 || n >= 200) { + const text = snapshot.statusText || STATUS_CODES[code]; + formatted.warning ??= ''; + const msg = `Target URL returned error ${code}${text ? `: ${text}` : ''}`; + formatted.warning = `${formatted.warning}${formatted.warning ? '\n' : ''}${msg}`; + } + } + + if (this.threadLocal.get('withImagesSummary')) { + formatted.images = + _(imageSummary) + .toPairs() + .map( + ([url, alt], i) => { + const idxTrack = imageIdxTrack.get(url); + const tag = idxTrack?.length ? `Image ${_.uniq(idxTrack).join(',')}` : `Hidden Image ${i + 1}`; + + return [`${tag}${alt ? `: ${alt}` : ''}`, url]; + } + ).fromPairs() + .value(); + } + if (this.threadLocal.get('withLinksSummary')) { + const links = (await this.jsdomControl.inferSnapshot(snapshot)).links; + + if (this.threadLocal.get('withLinksSummary') === 'all') { + formatted.links = links; + } else { + formatted.links = _(links).filter(([_label, href]) => !href.startsWith('file:') && !href.startsWith('javascript:')).uniqBy(1).fromPairs().value(); + } + } + + if (countGPTToken(formatted.content) < 200) { + formatted.warning ??= ''; + if (snapshot.isIntermediate) { + const msg = 'This page maybe not yet fully loaded, consider explicitly specify a timeout.'; + formatted.warning = `${formatted.warning}${formatted.warning ? '\n' : ''}${msg}`; + } + if (snapshot.childFrames?.length && !this.threadLocal.get('withIframe')) { + const msg = 'This page contains iframe that are currently hidden, consider enabling iframe processing.'; + formatted.warning = `${formatted.warning}${formatted.warning ? '\n' : ''}${msg}`; + } + if (snapshot.shadowExpanded && !this.threadLocal.get('withShadowDom')) { + const msg = 'This page contains shadow DOM that are currently hidden, consider enabling shadow DOM processing.'; + formatted.warning = `${formatted.warning}${formatted.warning ? '\n' : ''}${msg}`; + } + if (snapshot.html.includes('captcha') || snapshot.html.includes('cf-turnstile-response')) { + const msg = 'This page maybe requiring CAPTCHA, please make sure you are authorized to access this page.'; + formatted.warning = `${formatted.warning}${formatted.warning ? '\n' : ''}${msg}`; + } + if (snapshot.isFromCache) { + const msg = 'This is a cached snapshot of the original page, consider retry with caching opt-out.'; + formatted.warning = `${formatted.warning}${formatted.warning ? '\n' : ''}${msg}`; + } + } + + Object.assign(f, formatted); + + const textRepresentation = (function (this: typeof formatted) { + const mixins = []; + if (this.publishedTime) { + mixins.push(`Published Time: ${this.publishedTime}`); + } + const suffixMixins = []; + if (this.images) { + const imageSummaryChunks = ['Images:']; + for (const [k, v] of Object.entries(this.images)) { + imageSummaryChunks.push(`- ![${k}](${v})`); + } + if (imageSummaryChunks.length === 1) { + imageSummaryChunks.push('This page does not seem to contain any images.'); + } + suffixMixins.push(imageSummaryChunks.join('\n')); + } + if (this.links) { + const linkSummaryChunks = ['Links/Buttons:']; + if (Array.isArray(this.links)) { + for (const [k, v] of this.links) { + linkSummaryChunks.push(`- [${k}](${v})`); + } + } else { + for (const [k, v] of Object.entries(this.links)) { + linkSummaryChunks.push(`- [${k}](${v})`); + } + } + if (linkSummaryChunks.length === 1) { + linkSummaryChunks.push('This page does not seem to contain any buttons/links.'); + } + suffixMixins.push(linkSummaryChunks.join('\n')); + } + + if (this.warning) { + mixins.push(this.warning.split('\n').map((v) => `Warning: ${v}`).join('\n')); + } + + if (mode.includes('markdown')) { + return `${mixins.length ? `${mixins.join('\n\n')}\n\n` : ''}${this.content} +${suffixMixins.length ? `\n${suffixMixins.join('\n\n')}\n` : ''}`; + } + + return `Title: ${this.title} + +URL Source: ${this.url} +${mixins.length ? `\n${mixins.join('\n\n')}\n` : ''} +Markdown Content: +${this.content} +${suffixMixins.length ? `\n${suffixMixins.join('\n\n')}\n` : ''}`; + }).call(formatted); + + Object.defineProperty(f, 'textRepresentation', { value: textRepresentation, enumerable: false }); + + const dt = Date.now() - t0; + this.logger.debug(`Formatting took ${dt}ms`, { mode, url: nominalUrl?.toString(), dt }); + + return f as FormattedPage; + } + + dataUrlToBlobUrl(dataUrl: string, baseUrl: string = 'http://localhost/') { + const refUrl = new URL(baseUrl); + const mappedUrl = new URL(`blob:${refUrl.origin || 'localhost'}/${md5Hasher.hash(dataUrl)}`); + + return mappedUrl.href; + } + + async getGeneralSnapshotMixins(snapshot: PageSnapshot) { + let inferred; + const mixin: any = {}; + if (this.threadLocal.get('withImagesSummary')) { + inferred ??= await this.jsdomControl.inferSnapshot(snapshot); + const imageSummary = {} as { [k: string]: string; }; + const imageIdxTrack = new Map(); + + let imgIdx = 0; + + for (const img of inferred.imgs) { + const imgSerial = ++imgIdx; + const keySrc = (img.src.startsWith('data:') ? this.dataUrlToBlobUrl(img.src, snapshot.rebase) : img.src).trim(); + const idxArr = imageIdxTrack.has(keySrc) ? imageIdxTrack.get(keySrc)! : []; + idxArr.push(imgSerial); + imageIdxTrack.set(keySrc, idxArr); + imageSummary[keySrc] = img.alt || ''; + } + + mixin.images = + _(imageSummary) + .toPairs() + .map( + ([url, alt], i) => { + const idxTrack = imageIdxTrack.get(url); + const tag = idxTrack?.length ? `Image ${_.uniq(idxTrack).join(',')}` : `Hidden Image ${i + 1}`; + + return [`${tag}${alt ? `: ${alt}` : ''}`, url]; + } + ).fromPairs() + .value(); + } + if (this.threadLocal.get('withLinksSummary')) { + inferred ??= await this.jsdomControl.inferSnapshot(snapshot); + if (this.threadLocal.get('withLinksSummary') === 'all') { + mixin.links = inferred.links; + } else { + mixin.links = _(inferred.links).filter(([_label, href]) => !href.startsWith('file:') && !href.startsWith('javascript:')).uniqBy(1).fromPairs().value(); + } + } + if (snapshot.status) { + const code = snapshot.status; + const n = code - 200; + if (n < 0 || n >= 200) { + const text = snapshot.statusText || STATUS_CODES[code]; + mixin.warning ??= ''; + const msg = `Target URL returned error ${code}${text ? `: ${text}` : ''}`; + mixin.warning = `${mixin.warning}${mixin.warning ? '\n' : ''}${msg}`; + } + } + + return mixin; + } + + getTurndown(options?: { + noRules?: boolean | string, + url?: string | URL; + imgDataUrlToObjectUrl?: boolean; + removeImages?: boolean | 'src'; + customRules?: { [k: string]: Rule; }; + customKeep?: Filter; + }) { + const turndownOpts = this.threadLocal.get('turndownOpts'); + const turnDownService = new TurndownService({ + ...turndownOpts, + codeBlockStyle: 'fenced', + preformattedCode: true, + } as any); + if (options?.customKeep) { + turnDownService.keep(options.customKeep); + } + if (!options?.noRules) { + turnDownService.addRule('remove-irrelevant', { + filter: ['meta', 'style', 'script', 'noscript', 'link', 'textarea', 'select'], + replacement: () => '' + }); + turnDownService.addRule('truncate-svg', { + filter: 'svg' as any, + replacement: () => '' + }); + turnDownService.addRule('title-as-h1', { + filter: ['title'], + replacement: (innerText) => `${innerText}\n===============\n` + }); + } + + if (options?.imgDataUrlToObjectUrl) { + turnDownService.addRule('data-url-to-pseudo-object-url', { + filter: (node) => Boolean(node.tagName === 'IMG' && node.getAttribute('src')?.startsWith('data:')), + replacement: (_content, node: any) => { + const src = (node.getAttribute('src') || '').trim(); + const alt = cleanAttribute(node.getAttribute('alt')) || ''; + + const blobUrl = this.dataUrlToBlobUrl(src, options.url?.toString()); + + return `![${alt}](${blobUrl})`; + } + }); + } + + if (options?.customRules) { + for (const [k, v] of Object.entries(options.customRules)) { + turnDownService.addRule(k, v); + } + } + + turnDownService.addRule('improved-heading', { + filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + replacement: (content, node, options) => { + const hLevel = Number(node.nodeName.charAt(1)); + if (options.headingStyle === 'setext' && hLevel < 3) { + const underline = _.repeat((hLevel === 1 ? '=' : '-'), Math.min(128, content.length)); + return ( + '\n\n' + content + '\n' + underline + '\n\n' + ); + } else { + return '\n\n' + _.repeat('#', hLevel) + ' ' + content + '\n\n'; + } + } + }); + + turnDownService.addRule('improved-paragraph', { + filter: 'p', + replacement: (innerText) => { + const trimmed = innerText.trim(); + if (!trimmed) { + return ''; + } + + return `${trimmed.replace(/\n{3,}/g, '\n\n')}\n\n`; + } + }); + + let realLinkStyle: 'inlined' | 'collapsed' | 'shortcut' | 'referenced' | 'discarded' = 'inlined'; + if (turndownOpts?.linkStyle === 'referenced' || turndownOpts?.linkReferenceStyle) { + realLinkStyle = 'referenced'; + if (turndownOpts?.linkReferenceStyle === 'collapsed') { + realLinkStyle = 'collapsed'; + } else if (turndownOpts?.linkReferenceStyle === 'shortcut') { + realLinkStyle = 'shortcut'; + } else if (turndownOpts?.linkReferenceStyle === 'discarded') { + realLinkStyle = 'discarded'; + } + } else if (turndownOpts?.linkStyle === 'discarded') { + realLinkStyle = 'discarded'; + } + + turnDownService.addRule('improved-link', { + filter: function (node, _options) { + return Boolean( + node.nodeName === 'A' && + node.getAttribute('href') + ); + }, + + replacement: function (this: { references: string[]; }, content, node: any) { + var href = node.getAttribute('href'); + let title = cleanAttribute(node.getAttribute('title')); + if (title) title = ` "${title.replace(/"/g, '\\"')}"`; + let replacement; + let reference; + const fixedContent = content.replace(/\s+/g, ' ').trim(); + let fixedHref = href; + if (options?.url) { + try { + fixedHref = new URL(fixedHref, options.url).toString(); + } catch (_err) { + void 0; + } + } + + switch (realLinkStyle) { + case 'inlined': + replacement = `[${fixedContent}](${fixedHref}${title || ''})`; + reference = undefined; + break; + case 'collapsed': + replacement = `[${fixedContent}][]`; + reference = `[${fixedContent}]: ${fixedHref}${title}`; + break; + case 'shortcut': + replacement = `[${fixedContent}]`; + reference = `[${fixedContent}]: ${fixedHref}${title}`; + break; + case 'discarded': + replacement = content; + reference = undefined; + break; + default: + const id = this.references.length + 1; + replacement = `[${fixedContent}][${id}]`; + reference = `[${id}]${fixedHref}${title}`; + } + + if (reference) { + this.references.push(reference); + } + + return replacement; + }, + + // @ts-ignore + references: [], + + append: function (this: { references: string[]; }) { + let references = ''; + if (this.references.length) { + references = `\n\n${this.references.join('\n')}\n\n`; + this.references = []; // Reset references + } + return references; + } + }); + turnDownService.addRule('improved-code', { + filter: function (node: any) { + let hasSiblings = node.previousSibling || node.nextSibling; + let isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings; + + return node.nodeName === 'CODE' && !isCodeBlock; + }, + + replacement: function (inputContent: any) { + if (!inputContent) return ''; + let content = inputContent; + + let delimiter = '`'; + let matches = content.match(/`+/gm) || []; + while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'; + if (content.includes('\n')) { + delimiter = '```'; + } + + let extraSpace = delimiter === '```' ? '\n' : /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : ''; + + return delimiter + extraSpace + content + (delimiter === '```' && !content.endsWith(extraSpace) ? extraSpace : '') + delimiter; + } + }); + + return turnDownService; + } + + + isPoorlyTransformed(content?: string, node?: Element) { + if (!content) { + return true; + } + + if (content.startsWith('<') && content.endsWith('>')) { + return true; + } + + if (!this.threadLocal.get('noGfm') && content.includes('')) { + if (node?.textContent && content.length > node.textContent.length * 0.8) { + return true; + } + + const tableElms = node?.querySelectorAll('table') || []; + const deepTableElms = node?.querySelectorAll('table table'); + if (node && tableElms.length) { + const wrappingTables = _.without(tableElms, ...Array.from(deepTableElms || [])); + const tableTextsLength = _.sum(wrappingTables.map((x) => (x.innerHTML?.length || 0))); + + if (tableTextsLength / (content.length) > 0.6) { + return true; + } + } + + const tbodyElms = node?.querySelectorAll('tbody') || []; + const deepTbodyElms = node?.querySelectorAll('tbody tbody'); + if ((deepTbodyElms?.length || 0) / tbodyElms.length > 0.6) { + return true; + } + } + + return false; + } + + async createSnapshotFromFile(url: URL, file: FancyFile, overrideContentType?: string, overrideFileName?: string) { + if (overrideContentType === 'application/octet-stream') { + overrideContentType = undefined; + } + + const contentType: string = (overrideContentType || await file.mimeType).toLowerCase(); + const fileName = overrideFileName || `${url.origin}${url.pathname}`; + const snapshot: PageSnapshot = { + title: '', + href: url.href, + html: '', + text: '' + }; + + if (contentType.startsWith('image/')) { + snapshot.html = `${fileName}`; + snapshot.title = fileName; + snapshot.imgs = [{ src: url.href }]; + + return snapshot; + } + try { + const encoding: string | undefined = contentType.includes('charset=') ? contentType.split('charset=')[1]?.trim().toLowerCase() : 'utf-8'; + if (contentType.startsWith('text/html')) { + if ((await file.size) > 1024 * 1024 * 32) { + throw new AssertionFailureError(`Failed to access ${url}: file too large`); + } + snapshot.html = await readFile(await file.filePath, encoding); + let innerCharset; + const peek = snapshot.html.slice(0, 1024); + innerCharset ??= peek.match(/]+text\/html;\s*?charset=([^>"]+)/i)?.[1]?.toLowerCase(); + innerCharset ??= peek.match(/]+charset="([^>"]+)\"/i)?.[1]?.toLowerCase(); + if (innerCharset && innerCharset !== encoding) { + snapshot.html = await readFile(await file.filePath, innerCharset); + } + + return snapshot; + } + if (contentType.startsWith('text/') || contentType.startsWith('application/json')) { + if ((await file.size) > 1024 * 1024 * 32) { + throw new AssertionFailureError(`Failed to access ${url}: file too large`); + } + snapshot.text = await readFile(await file.filePath, encoding); + snapshot.html = `
${snapshot.text}
`; + + return snapshot; + } + if (contentType.startsWith('application/pdf')) { + snapshot.pdfs = [pathToFileURL(await file.filePath).href]; + + return snapshot; + } + } catch (err: any) { + this.logger.warn(`Failed to read from file: ${url}`, { err, url }); + throw new DataStreamBrokenError(`Failed to access ${url}: ${err?.message}`); + } + + throw new AssertionFailureError(`Failed to access ${url}: unexpected type ${contentType}`); + } +} + +const snapshotFormatter = container.resolve(SnapshotFormatter); + +export default snapshotFormatter; diff --git a/src/services/temp-file.ts b/src/services/temp-file.ts new file mode 100644 index 0000000..74f10ba --- /dev/null +++ b/src/services/temp-file.ts @@ -0,0 +1,23 @@ +import { AbstractTempFileManger } from 'civkit/temp'; +import { rm } from 'fs/promises'; +import { singleton } from 'tsyringe'; +import { Finalizer } from './finalizer'; + +@singleton() +export class TempFileManager extends AbstractTempFileManger { + + rootDir = ''; + + override async init() { + await this.dependencyReady(); + await super.init(); + this.emit('ready'); + } + + @Finalizer() + override async standDown() { + await super.standDown(); + + await rm(this.rootDir, { recursive: true, force: true }); + } +} diff --git a/src/services/threaded.ts b/src/services/threaded.ts new file mode 100644 index 0000000..9107ecd --- /dev/null +++ b/src/services/threaded.ts @@ -0,0 +1,66 @@ +import 'reflect-metadata'; + +import { singleton, container } from 'tsyringe'; +import { AbstractThreadedServiceRegistry } from 'civkit/threaded'; +import _ from 'lodash'; + +import { GlobalLogger } from './logger'; +import { AsyncLocalContext } from './async-context'; +import { PseudoTransfer } from './pseudo-transfer'; +import { cpus } from 'os'; +import { isMainThread } from 'worker_threads'; + +@singleton() +export class ThreadedServiceRegistry extends AbstractThreadedServiceRegistry { + container = container; + + logger = this.globalLogger.child({ service: this.constructor.name }); + + constructor( + protected globalLogger: GlobalLogger, + public asyncContext: AsyncLocalContext, + public pseudoTransfer: PseudoTransfer, + ) { + super(...arguments); + } + + setMaxWorkersByCpu() { + const cpuStat = cpus(); + + const evenCpuCycles = cpuStat.filter((_cpu, i) => i % 2 === 0).reduce((acc, cpu) => acc + cpu.times.user + cpu.times.sys, 0); + const oddCpuCycles = cpuStat.filter((_cpu, i) => i % 2 === 1).reduce((acc, cpu) => acc + cpu.times.user + cpu.times.sys, 0); + + const isLikelyHyperThreaded = (oddCpuCycles / evenCpuCycles) < 0.5; + + this.maxWorkers = isLikelyHyperThreaded ? cpuStat.length / 2 : cpuStat.length; + } + + override async init() { + await this.dependencyReady(); + await super.init(); + + if (isMainThread) { + this.setMaxWorkersByCpu(); + await Promise.all( + _.range(0, 2).map( + (_n) => + new Promise( + (resolve, reject) => { + this.createWorker() + .once('message', resolve) + .once('error', reject); + } + ) + ) + ); + } + + this.emit('ready'); + } + +} + + +const instance = container.resolve(ThreadedServiceRegistry); +export default instance; +export const { Method, Param, Ctx, RPCReflect, Threaded } = instance.decorators(); diff --git a/src/shared b/src/shared new file mode 100644 index 0000000..2cfcd24 --- /dev/null +++ b/src/shared @@ -0,0 +1 @@ +../thinapps-shared/backend \ No newline at end of file diff --git a/src/stand-alone/crawl.ts b/src/stand-alone/crawl.ts new file mode 100644 index 0000000..4a6e84c --- /dev/null +++ b/src/stand-alone/crawl.ts @@ -0,0 +1,169 @@ +import 'reflect-metadata'; +import { container, singleton } from 'tsyringe'; + +import { KoaServer } from 'civkit/civ-rpc/koa'; +import http2 from 'http2'; +import http from 'http'; +import { CrawlerHost } from '../api/crawler'; +import { FsWalk, WalkOutEntity } from 'civkit/fswalk'; +import path from 'path'; +import fs from 'fs'; +import { mimeOfExt } from 'civkit/mime'; +import { Context, Next } from 'koa'; +import { RPCRegistry } from '../services/registry'; +import { AsyncResource } from 'async_hooks'; +import { runOnce } from 'civkit/decorators'; +import { randomUUID } from 'crypto'; +import { ThreadedServiceRegistry } from '../services/threaded'; +import { GlobalLogger } from '../services/logger'; +import { AsyncLocalContext } from '../services/async-context'; +import finalizer, { Finalizer } from '../services/finalizer'; +import koaCompress from 'koa-compress'; + +@singleton() +export class CrawlStandAloneServer extends KoaServer { + logger = this.globalLogger.child({ service: this.constructor.name }); + + httpAlternativeServer?: typeof this['httpServer']; + assets = new Map(); + + constructor( + protected globalLogger: GlobalLogger, + protected registry: RPCRegistry, + protected crawlerHost: CrawlerHost, + protected threadLocal: AsyncLocalContext, + protected threads: ThreadedServiceRegistry, + ) { + super(...arguments); + } + + h2c() { + this.httpAlternativeServer = this.httpServer; + const fn = this.koaApp.callback(); + this.httpServer = http2.createServer((req, res) => { + const ar = new AsyncResource('HTTP2ServerRequest'); + ar.runInAsyncScope(fn, this.koaApp, req, res); + }); + // useResourceBasedDefaultTracker(); + + return this; + } + + override async init() { + await this.walkForAssets(); + await super.init(); + } + + async walkForAssets() { + const files = await FsWalk.walkOut(path.resolve(__dirname, '..', '..', 'public')); + + for (const file of files) { + if (file.type !== 'file') { + continue; + } + this.assets.set(file.relativePath.toString(), file); + } + } + + override listen(port: number) { + const r = super.listen(port); + if (this.httpAlternativeServer) { + const altPort = port + 1; + this.httpAlternativeServer.listen(altPort, () => { + this.logger.info(`Alternative ${this.httpAlternativeServer!.constructor.name} listening on port ${altPort}`); + }); + } + + return r; + } + + makeAssetsServingController() { + return (ctx: Context, next: Next) => { + const requestPath = ctx.path; + const file = requestPath.slice(1); + if (!file) { + return next(); + } + + const asset = this.assets.get(file); + if (asset?.type !== 'file') { + return next(); + } + + ctx.body = fs.createReadStream(asset.path); + ctx.type = mimeOfExt(path.extname(asset.path.toString())) || 'application/octet-stream'; + ctx.set('Content-Length', asset.stats.size.toString()); + + return; + }; + } + + registerRoutes(): void { + this.koaApp.use(koaCompress({ + filter(type) { + if (type.startsWith('text/')) { + return true; + } + + if (type.includes('application/json') || type.includes('+json') || type.includes('+xml')) { + return true; + } + + if (type.includes('application/x-ndjson')) { + return true; + } + + return false; + } + })); + this.koaApp.use(this.makeAssetsServingController()); + this.koaApp.use(this.registry.makeShimController()); + } + + // Using h2c server has an implication that multiple requests may share the same connection and x-cloud-trace-context + // TraceId is expected to be request-bound and unique. So these two has to be distinguished. + @runOnce() + override insertAsyncHookMiddleware() { + const asyncHookMiddleware = async (ctx: Context, next: () => Promise) => { + const googleTraceId = ctx.get('x-cloud-trace-context').split('/')?.[0]; + this.threadLocal.setup({ + traceId: randomUUID(), + traceT0: new Date(), + googleTraceId, + }); + + return next(); + }; + + this.koaApp.use(asyncHookMiddleware); + } + + @Finalizer() + override async standDown() { + const tasks: Promise[] = []; + if (this.httpAlternativeServer?.listening) { + (this.httpAlternativeServer as http.Server).closeIdleConnections?.(); + this.httpAlternativeServer.close(); + tasks.push(new Promise((resolve, reject) => { + this.httpAlternativeServer!.close((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + })); + } + tasks.push(super.standDown()); + await Promise.all(tasks); + } + +} +const instance = container.resolve(CrawlStandAloneServer); + +export default instance; + +if (process.env.NODE_ENV?.includes('dry-run')) { + instance.serviceReady().then(() => finalizer.terminate()); +} else { + instance.serviceReady().then((s) => s.h2c().listen(parseInt(process.env.PORT || '') || 3000)); +} \ No newline at end of file diff --git a/src/stand-alone/search.ts b/src/stand-alone/search.ts new file mode 100644 index 0000000..673a3cb --- /dev/null +++ b/src/stand-alone/search.ts @@ -0,0 +1,178 @@ +import 'reflect-metadata'; +import { container, singleton } from 'tsyringe'; + +import { KoaServer } from 'civkit/civ-rpc/koa'; +import http2 from 'http2'; +import http from 'http'; +import { SearcherHost } from '../api/searcher'; +import { FsWalk, WalkOutEntity } from 'civkit/fswalk'; +import path from 'path'; +import fs from 'fs'; +import { mimeOfExt } from 'civkit/mime'; +import { Context, Next } from 'koa'; +import { RPCRegistry } from '../services/registry'; +import { AsyncResource } from 'async_hooks'; +import { runOnce } from 'civkit/decorators'; +import { randomUUID } from 'crypto'; +import { ThreadedServiceRegistry } from '../services/threaded'; +import { GlobalLogger } from '../services/logger'; +import { AsyncLocalContext } from '../services/async-context'; +import finalizer, { Finalizer } from '../services/finalizer'; +import koaCompress from 'koa-compress'; + +@singleton() +export class SearchStandAloneServer extends KoaServer { + logger = this.globalLogger.child({ service: this.constructor.name }); + + httpAlternativeServer?: typeof this['httpServer']; + assets = new Map(); + + constructor( + protected globalLogger: GlobalLogger, + protected registry: RPCRegistry, + protected searcherHost: SearcherHost, + protected threadLocal: AsyncLocalContext, + protected threads: ThreadedServiceRegistry, + ) { + super(...arguments); + } + + h2c() { + this.httpAlternativeServer = this.httpServer; + const fn = this.koaApp.callback(); + this.httpServer = http2.createServer((req, res) => { + const ar = new AsyncResource('HTTP2ServerRequest'); + ar.runInAsyncScope(fn, this.koaApp, req, res); + }); + // useResourceBasedDefaultTracker(); + + return this; + } + + override async init() { + await this.walkForAssets(); + await this.dependencyReady(); + + for (const [k, v] of this.registry.conf.entries()) { + if (v.tags?.includes('crawl')) { + this.registry.conf.delete(k); + } + } + + await super.init(); + } + + async walkForAssets() { + const files = await FsWalk.walkOut(path.resolve(__dirname, '..', '..', 'public')); + + for (const file of files) { + if (file.type !== 'file') { + continue; + } + this.assets.set(file.relativePath.toString(), file); + } + } + + override listen(port: number) { + const r = super.listen(port); + if (this.httpAlternativeServer) { + const altPort = port + 1; + this.httpAlternativeServer.listen(altPort, () => { + this.logger.info(`Alternative ${this.httpAlternativeServer!.constructor.name} listening on port ${altPort}`); + }); + } + + return r; + } + + makeAssetsServingController() { + return (ctx: Context, next: Next) => { + const requestPath = ctx.path; + const file = requestPath.slice(1); + if (!file) { + return next(); + } + + const asset = this.assets.get(file); + if (asset?.type !== 'file') { + return next(); + } + + ctx.body = fs.createReadStream(asset.path); + ctx.type = mimeOfExt(path.extname(asset.path.toString())) || 'application/octet-stream'; + ctx.set('Content-Length', asset.stats.size.toString()); + + return; + }; + } + + registerRoutes(): void { + this.koaApp.use(koaCompress({ + filter(type) { + if (type.startsWith('text/')) { + return true; + } + + if (type.includes('application/json') || type.includes('+json') || type.includes('+xml')) { + return true; + } + + if (type.includes('application/x-ndjson')) { + return true; + } + + return false; + } + })); + this.koaApp.use(this.makeAssetsServingController()); + this.koaApp.use(this.registry.makeShimController()); + } + + + // Using h2c server has an implication that multiple requests may share the same connection and x-cloud-trace-context + // TraceId is expected to be request-bound and unique. So these two has to be distinguished. + @runOnce() + override insertAsyncHookMiddleware() { + const asyncHookMiddleware = async (ctx: Context, next: () => Promise) => { + const googleTraceId = ctx.get('x-cloud-trace-context').split('/')?.[0]; + this.threadLocal.setup({ + traceId: randomUUID(), + traceT0: new Date(), + googleTraceId, + }); + + return next(); + }; + + this.koaApp.use(asyncHookMiddleware); + } + + @Finalizer() + override async standDown() { + const tasks: Promise[] = []; + if (this.httpAlternativeServer?.listening) { + (this.httpAlternativeServer as http.Server).closeIdleConnections?.(); + this.httpAlternativeServer.close(); + tasks.push(new Promise((resolve, reject) => { + this.httpAlternativeServer!.close((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + })); + } + tasks.push(super.standDown()); + await Promise.all(tasks); + } + +} +const instance = container.resolve(SearchStandAloneServer); + +export default instance; + +if (process.env.NODE_ENV?.includes('dry-run')) { + instance.serviceReady().then(() => finalizer.terminate()); +} else { + instance.serviceReady().then((s) => s.h2c().listen(parseInt(process.env.PORT || '') || 3000)); +} diff --git a/src/stand-alone/serp.ts b/src/stand-alone/serp.ts new file mode 100644 index 0000000..76c91fc --- /dev/null +++ b/src/stand-alone/serp.ts @@ -0,0 +1,180 @@ +import 'reflect-metadata'; +import { container, singleton } from 'tsyringe'; + +import { KoaServer } from 'civkit/civ-rpc/koa'; +import http2 from 'http2'; +import http from 'http'; +import { FsWalk, WalkOutEntity } from 'civkit/fswalk'; +import path from 'path'; +import fs from 'fs'; +import { mimeOfExt } from 'civkit/mime'; +import { Context, Next } from 'koa'; +import { RPCRegistry } from '../services/registry'; +import { AsyncResource } from 'async_hooks'; +import { runOnce } from 'civkit/decorators'; +import { randomUUID } from 'crypto'; +import { ThreadedServiceRegistry } from '../services/threaded'; +import { GlobalLogger } from '../services/logger'; +import { AsyncLocalContext } from '../services/async-context'; +import finalizer, { Finalizer } from '../services/finalizer'; +import { SerpHost } from '../api/serp'; +import koaCompress from 'koa-compress'; +import { getAuditionMiddleware } from '../shared/utils/audition'; + +@singleton() +export class SERPStandAloneServer extends KoaServer { + logger = this.globalLogger.child({ service: this.constructor.name }); + + httpAlternativeServer?: typeof this['httpServer']; + assets = new Map(); + + constructor( + protected globalLogger: GlobalLogger, + protected registry: RPCRegistry, + protected serpHost: SerpHost, + protected threadLocal: AsyncLocalContext, + protected threads: ThreadedServiceRegistry, + ) { + super(...arguments); + } + + h2c() { + this.httpAlternativeServer = this.httpServer; + const fn = this.koaApp.callback(); + this.httpServer = http2.createServer((req, res) => { + const ar = new AsyncResource('HTTP2ServerRequest'); + ar.runInAsyncScope(fn, this.koaApp, req, res); + }); + // useResourceBasedDefaultTracker(); + + return this; + } + + override async init() { + await this.walkForAssets(); + await this.dependencyReady(); + + for (const [k, v] of this.registry.conf.entries()) { + if (v.tags?.includes('crawl')) { + this.registry.conf.delete(k); + } + } + + await super.init(); + } + + async walkForAssets() { + const files = await FsWalk.walkOut(path.resolve(__dirname, '..', '..', 'public')); + + for (const file of files) { + if (file.type !== 'file') { + continue; + } + this.assets.set(file.relativePath.toString(), file); + } + } + + override listen(port: number) { + const r = super.listen(port); + if (this.httpAlternativeServer) { + const altPort = port + 1; + this.httpAlternativeServer.listen(altPort, () => { + this.logger.info(`Alternative ${this.httpAlternativeServer!.constructor.name} listening on port ${altPort}`); + }); + } + + return r; + } + + makeAssetsServingController() { + return (ctx: Context, next: Next) => { + const requestPath = ctx.path; + const file = requestPath.slice(1); + if (!file) { + return next(); + } + + const asset = this.assets.get(file); + if (asset?.type !== 'file') { + return next(); + } + + ctx.body = fs.createReadStream(asset.path); + ctx.type = mimeOfExt(path.extname(asset.path.toString())) || 'application/octet-stream'; + ctx.set('Content-Length', asset.stats.size.toString()); + + return; + }; + } + + registerRoutes(): void { + this.koaApp.use(getAuditionMiddleware()); + this.koaApp.use(koaCompress({ + filter(type) { + if (type.startsWith('text/')) { + return true; + } + + if (type.includes('application/json') || type.includes('+json') || type.includes('+xml')) { + return true; + } + + if (type.includes('application/x-ndjson')) { + return true; + } + + return false; + } + })); + this.koaApp.use(this.makeAssetsServingController()); + this.koaApp.use(this.registry.makeShimController()); + } + + + // Using h2c server has an implication that multiple requests may share the same connection and x-cloud-trace-context + // TraceId is expected to be request-bound and unique. So these two has to be distinguished. + @runOnce() + override insertAsyncHookMiddleware() { + const asyncHookMiddleware = async (ctx: Context, next: () => Promise) => { + const googleTraceId = ctx.get('x-cloud-trace-context').split('/')?.[0]; + this.threadLocal.setup({ + traceId: randomUUID(), + traceT0: new Date(), + googleTraceId, + }); + + return next(); + }; + + this.koaApp.use(asyncHookMiddleware); + } + + @Finalizer() + override async standDown() { + const tasks: Promise[] = []; + if (this.httpAlternativeServer?.listening) { + (this.httpAlternativeServer as http.Server).closeIdleConnections?.(); + this.httpAlternativeServer.close(); + tasks.push(new Promise((resolve, reject) => { + this.httpAlternativeServer!.close((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + })); + } + tasks.push(super.standDown()); + await Promise.all(tasks); + } + +} +const instance = container.resolve(SERPStandAloneServer); + +export default instance; + +if (process.env.NODE_ENV?.includes('dry-run')) { + instance.serviceReady().then(() => finalizer.terminate()); +} else { + instance.serviceReady().then((s) => s.h2c().listen(parseInt(process.env.PORT || '') || 3000)); +} diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..b2ab9ce --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,28 @@ +declare module 'langdetect' { + interface DetectionResult { + lang: string; + prob: number; + } + + export function detect(text: string): DetectionResult[]; + export function detectOne(text: string): string | null; +} + +declare module 'jsdom' { + import EventEmitter from 'events'; + export class JSDOM { + constructor(html: string, options?: any); + window: typeof window; + } + export class VirtualConsole extends EventEmitter { + constructor(); + sendTo(console: any, options?: any); + } +} + +declare module 'simple-zstd' { + import { Duplex } from 'stream'; + export function ZSTDCompress(lvl: Number): Duplex; + export function ZSTDDecompress(): Duplex; + export function ZSTDDecompressMaybe(): Duplex; +} diff --git a/src/utils/encoding.ts b/src/utils/encoding.ts new file mode 100644 index 0000000..0f34a9b --- /dev/null +++ b/src/utils/encoding.ts @@ -0,0 +1,34 @@ +import { createReadStream } from 'fs'; +import { Readable } from 'stream'; +import { TextDecoderStream } from 'stream/web'; + +export async function decodeFileStream( + fileStream: Readable, + encoding: string = 'utf-8', +): Promise { + const decodeStream = new TextDecoderStream(encoding, { fatal: false, ignoreBOM: false }); + Readable.toWeb(fileStream).pipeThrough(decodeStream); + const chunks = []; + + for await (const chunk of decodeStream.readable) { + chunks.push(chunk); + } + + return chunks.join(''); +} + + +export async function readFile( + filePath: string, + encoding: string = 'utf-8', +): Promise { + const decodeStream = new TextDecoderStream(encoding, { fatal: false, ignoreBOM: false }); + Readable.toWeb(createReadStream(filePath)).pipeThrough(decodeStream); + const chunks = []; + + for await (const chunk of decodeStream.readable) { + chunks.push(chunk); + } + + return chunks.join(''); +} \ No newline at end of file diff --git a/src/utils/get-function-url.ts b/src/utils/get-function-url.ts new file mode 100644 index 0000000..389e688 --- /dev/null +++ b/src/utils/get-function-url.ts @@ -0,0 +1,24 @@ +import { GoogleAuth } from 'google-auth-library'; + +/** + * Get the URL of a given v2 cloud function. + * + * @param {string} name the function's name + * @param {string} location the function's location + * @return {Promise} The URL of the function + */ +export async function getFunctionUrl(name: string, location = "us-central1") { + const projectId = `reader-6b7dc`; + const url = "https://cloudfunctions.googleapis.com/v2beta/" + + `projects/${projectId}/locations/${location}/functions/${name}`; + const auth = new GoogleAuth({ + scopes: 'https://www.googleapis.com/auth/cloud-platform', + }); + const client = await auth.getClient(); + const res = await client.request({ url }); + const uri = res.data?.serviceConfig?.uri; + if (!uri) { + throw new Error(`Unable to retreive uri for function at ${url}`); + } + return uri; +} diff --git a/src/utils/ip.ts b/src/utils/ip.ts new file mode 100644 index 0000000..d8d387e --- /dev/null +++ b/src/utils/ip.ts @@ -0,0 +1,167 @@ +import { isIPv4, isIPv6 } from 'net'; + +export function parseIp(ip: string): Buffer { + if (isIPv4(ip)) { + const [a, b, c, d] = ip.split('.').map(Number); + + const buf = Buffer.alloc(4); + buf.writeUInt8(a, 0); + buf.writeUInt8(b, 1); + buf.writeUInt8(c, 2); + buf.writeUInt8(d, 3); + + return buf; + } + + if (isIPv6(ip)) { + if (ip.includes('.')) { + const parts = ip.split(':'); + const ipv4Part = parts.pop(); + if (!ipv4Part) throw new Error('Invalid IPv6 address'); + const ipv4Bytes = parseIp(ipv4Part); + parts.push('0'); + const ipv6Bytes = parseIp(parts.join(':')); + ipv6Bytes.writeUInt32BE(ipv4Bytes.readUInt32BE(0), 12); + + return ipv6Bytes; + } + + const buf = Buffer.alloc(16); + + // Expand :: notation + let expanded = ip; + if (ip.includes('::')) { + const sides = ip.split('::'); + const left = sides[0] ? sides[0].split(':') : []; + const right = sides[1] ? sides[1].split(':') : []; + const middle = Array(8 - left.length - right.length).fill('0'); + expanded = [...left, ...middle, ...right].join(':'); + } + + // Convert to buffer + const parts = expanded.split(':'); + let offset = 0; + for (const part of parts) { + buf.writeUInt16BE(parseInt(part, 16), offset); + offset += 2; + } + + return buf; + } + + throw new Error('Invalid IP address'); +} + + +export function parseCIDR(cidr: string): [Buffer, Buffer] { + const [ip, prefixTxt] = cidr.split('/'); + const buf = parseIp(ip); + const maskBuf = Buffer.alloc(buf.byteLength, 0xff); + const prefixBits = parseInt(prefixTxt); + + let offsetBits = 0; + while (offsetBits < (buf.byteLength * 8)) { + if (offsetBits <= (prefixBits - 8)) { + offsetBits += 8; + continue; + } + const bitsRemain = prefixBits - offsetBits; + const byteOffset = Math.floor(offsetBits / 8); + + if (bitsRemain > 0) { + const theByte = buf[byteOffset]; + const mask = 0xff << (8 - bitsRemain); + maskBuf[byteOffset] = mask; + buf[byteOffset] = theByte & mask; + + offsetBits += 8; + continue; + }; + buf[byteOffset] = 0; + maskBuf[byteOffset] = 0; + + offsetBits += 8; + } + + return [buf, maskBuf]; +} + +export class CIDR { + buff: Buffer; + mask: Buffer; + text: string; + constructor(cidr: string) { + this.text = cidr; + [this.buff, this.mask] = parseCIDR(cidr); + } + + toString() { + return this.text; + } + + get family() { + return this.buff.byteLength === 4 ? 4 : 6; + } + + test(ip: string | Buffer): boolean { + const parsedIp = typeof ip === 'string' ? parseIp(ip) : ip; + + if (parsedIp.byteLength !== this.buff.byteLength) { + return false; + } + + for (const i of Array(this.buff.byteLength).keys()) { + const t = parsedIp[i]; + const m = this.mask[i]; + + if (m === 0) { + return true; + } + + const r = this.buff[i]; + if ((t & m) !== r) { + return false; + } + } + + return true; + } +} + +const nonPublicNetworks4 = [ + '10.0.0.0/8', + '172.16.0.0/12', + '192.168.0.0/16', + + '127.0.0.0/8', + '255.255.255.255/32', + '169.254.0.0/16', + '224.0.0.0/4', + + '100.64.0.0/10', + '0.0.0.0/32', +]; + + +const nonPublicNetworks6 = [ + 'fc00::/7', + 'fe80::/10', + 'ff00::/8', + + '::127.0.0.0/104', + '::/128', +]; + +const nonPublicCIDRs = [...nonPublicNetworks4, ...nonPublicNetworks6].map(cidr => new CIDR(cidr)); + +export function isIPInNonPublicRange(ip: string) { + const parsed = parseIp(ip); + + for (const cidr of nonPublicCIDRs) { + if (cidr.test(parsed)) { + return true; + } + } + + return false; +} diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts new file mode 100644 index 0000000..f8def89 --- /dev/null +++ b/src/utils/markdown.ts @@ -0,0 +1,39 @@ + +export function tidyMarkdown(markdown: string): string { + + // Step 1: Handle complex broken links with text and optional images spread across multiple lines + let normalizedMarkdown = markdown.replace(/\[\s*([^\]\n]+?)\s*\]\s*\(\s*([^)]+)\s*\)/g, (match, text, url) => { + // Remove internal new lines and excessive spaces within the text + text = text.replace(/\s+/g, ' ').trim(); + url = url.replace(/\s+/g, '').trim(); + return `[${text}](${url})`; + }); + + normalizedMarkdown = normalizedMarkdown.replace(/\[\s*([^\]\n!]*?)\s*\n*(?:!\[([^\]]*)\]\((.*?)\))?\s*\n*\]\s*\(\s*([^)]+)\s*\)/g, (match, text, alt, imgUrl, linkUrl) => { + // Normalize by removing excessive spaces and new lines + text = text.replace(/\s+/g, ' ').trim(); + alt = alt ? alt.replace(/\s+/g, ' ').trim() : ''; + imgUrl = imgUrl ? imgUrl.replace(/\s+/g, '').trim() : ''; + linkUrl = linkUrl.replace(/\s+/g, '').trim(); + if (imgUrl) { + return `[${text} ![${alt}](${imgUrl})](${linkUrl})`; + } else { + return `[${text}](${linkUrl})`; + } + }); + + // Step 2: Normalize regular links that may be broken across lines + normalizedMarkdown = normalizedMarkdown.replace(/\[\s*([^\]]+)\]\s*\(\s*([^)]+)\)/g, (match, text, url) => { + text = text.replace(/\s+/g, ' ').trim(); + url = url.replace(/\s+/g, '').trim(); + return `[${text}](${url})`; + }); + + // Step 3: Replace more than two consecutive empty lines with exactly two empty lines + normalizedMarkdown = normalizedMarkdown.replace(/\n{3,}/g, '\n\n'); + + // Step 4: Remove leading spaces from each line + normalizedMarkdown = normalizedMarkdown.replace(/^[ \t]+/gm, ''); + + return normalizedMarkdown.trim(); +} diff --git a/src/utils/misc.ts b/src/utils/misc.ts new file mode 100644 index 0000000..6f5c346 --- /dev/null +++ b/src/utils/misc.ts @@ -0,0 +1,27 @@ +import { ParamValidationError } from 'civkit'; + +export function cleanAttribute(attribute: string | null) { + return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''; +} + + +export function tryDecodeURIComponent(input: string) { + try { + return decodeURIComponent(input); + } catch (err) { + if (URL.canParse(input, 'http://localhost:3000')) { + return input; + } + + throw new ParamValidationError(`Invalid URIComponent: ${input}`); + } +} + + +export async function* toAsyncGenerator(val: T) { + yield val; +} + +export async function* toGenerator(val: T) { + yield val; +} \ No newline at end of file diff --git a/src/utils/tailwind-classes.ts b/src/utils/tailwind-classes.ts new file mode 100644 index 0000000..7f82d00 --- /dev/null +++ b/src/utils/tailwind-classes.ts @@ -0,0 +1,9625 @@ +export const tailwindClasses = new Set([ + "aspect-auto", + "aspect-square", + "aspect-video", + "container", + "columns-1", + "columns-2", + "columns-3", + "columns-4", + "columns-5", + "columns-6", + "columns-7", + "columns-8", + "columns-9", + "columns-10", + "columns-11", + "columns-12", + "columns-auto", + "columns-3xs", + "columns-2xs", + "columns-xs", + "columns-sm", + "columns-md", + "columns-lg", + "columns-xl", + "columns-2xl", + "columns-3xl", + "columns-4xl", + "columns-5xl", + "columns-6xl", + "columns-7xl", + "break-after-auto", + "break-after-avoid", + "break-after-all", + "break-after-avoid-page", + "break-after-page", + "break-after-left", + "break-after-right", + "break-after-column", + "break-before-auto", + "break-before-avoid", + "break-before-all", + "break-before-avoid-page", + "break-before-page", + "break-before-left", + "break-before-right", + "break-before-column", + "break-inside-auto", + "break-inside-avoid", + "break-inside-avoid-page", + "break-inside-avoid-column", + "box-decoration-clone", + "box-decoration-slice", + "box-border", + "box-content", + "block", + "inline-block", + "inline", + "flex", + "inline-flex", + "table", + "inline-table", + "table-caption", + "table-cell", + "table-column", + "table-column-group", + "table-footer-group", + "table-header-group", + "table-row-group", + "table-row", + "flow-root", + "grid", + "inline-grid", + "contents", + "list-item", + // "hidden", + "float-start", + "float-end", + "float-right", + "float-left", + "float-none", + "clear-start", + "clear-end", + "clear-left", + "clear-right", + "clear-both", + "clear-none", + "isolate", + "isolation-auto", + "object-contain", + "object-cover", + "object-fill", + "object-none", + "object-scale-down", + "object-bottom", + "object-center", + "object-left", + "object-left-bottom", + "object-left-top", + "object-right", + "object-right-bottom", + "object-right-top", + "object-top", + "overflow-auto", + "overflow-hidden", + "overflow-clip", + "overflow-visible", + "overflow-scroll", + "overflow-x-auto", + "overflow-y-auto", + "overflow-x-hidden", + "overflow-y-hidden", + "overflow-x-clip", + "overflow-y-clip", + "overflow-x-visible", + "overflow-y-visible", + "overflow-x-scroll", + "overflow-y-scroll", + "overscroll-auto", + "overscroll-contain", + "overscroll-none", + "overscroll-y-auto", + "overscroll-y-contain", + "overscroll-y-none", + "overscroll-x-auto", + "overscroll-x-contain", + "overscroll-x-none", + "static", + "fixed", + "absolute", + "relative", + "sticky", + "inset-0", + "inset-x-0", + "inset-y-0", + "start-0", + "end-0", + "top-0", + "right-0", + "bottom-0", + "left-0", + "inset-px", + "inset-x-px", + "inset-y-px", + "start-px", + "end-px", + "top-px", + "right-px", + "bottom-px", + "left-px", + "inset-0.5", + "inset-x-0.5", + "inset-y-0.5", + "start-0.5", + "end-0.5", + "top-0.5", + "right-0.5", + "bottom-0.5", + "left-0.5", + "inset-1", + "inset-x-1", + "inset-y-1", + "start-1", + "end-1", + "top-1", + "right-1", + "bottom-1", + "left-1", + "inset-1.5", + "inset-x-1.5", + "inset-y-1.5", + "start-1.5", + "end-1.5", + "top-1.5", + "right-1.5", + "bottom-1.5", + "left-1.5", + "inset-2", + "inset-x-2", + "inset-y-2", + "start-2", + "end-2", + "top-2", + "right-2", + "bottom-2", + "left-2", + "inset-2.5", + "inset-x-2.5", + "inset-y-2.5", + "start-2.5", + "end-2.5", + "top-2.5", + "right-2.5", + "bottom-2.5", + "left-2.5", + "inset-3", + "inset-x-3", + "inset-y-3", + "start-3", + "end-3", + "top-3", + "right-3", + "bottom-3", + "left-3", + "inset-3.5", + "inset-x-3.5", + "inset-y-3.5", + "start-3.5", + "end-3.5", + "top-3.5", + "right-3.5", + "bottom-3.5", + "left-3.5", + "inset-4", + "inset-x-4", + "inset-y-4", + "start-4", + "end-4", + "top-4", + "right-4", + "bottom-4", + "left-4", + "inset-5", + "inset-x-5", + "inset-y-5", + "start-5", + "end-5", + "top-5", + "right-5", + "bottom-5", + "left-5", + "inset-6", + "inset-x-6", + "inset-y-6", + "start-6", + "end-6", + "top-6", + "right-6", + "bottom-6", + "left-6", + "inset-7", + "inset-x-7", + "inset-y-7", + "start-7", + "end-7", + "top-7", + "right-7", + "bottom-7", + "left-7", + "inset-8", + "inset-x-8", + "inset-y-8", + "start-8", + "end-8", + "top-8", + "right-8", + "bottom-8", + "left-8", + "inset-9", + "inset-x-9", + "inset-y-9", + "start-9", + "end-9", + "top-9", + "right-9", + "bottom-9", + "left-9", + "inset-10", + "inset-x-10", + "inset-y-10", + "start-10", + "end-10", + "top-10", + "right-10", + "bottom-10", + "left-10", + "inset-11", + "inset-x-11", + "inset-y-11", + "start-11", + "end-11", + "top-11", + "right-11", + "bottom-11", + "left-11", + "inset-12", + "inset-x-12", + "inset-y-12", + "start-12", + "end-12", + "top-12", + "right-12", + "bottom-12", + "left-12", + "inset-14", + "inset-x-14", + "inset-y-14", + "start-14", + "end-14", + "top-14", + "right-14", + "bottom-14", + "left-14", + "inset-16", + "inset-x-16", + "inset-y-16", + "start-16", + "end-16", + "top-16", + "right-16", + "bottom-16", + "left-16", + "inset-20", + "inset-x-20", + "inset-y-20", + "start-20", + "end-20", + "top-20", + "right-20", + "bottom-20", + "left-20", + "inset-24", + "inset-x-24", + "inset-y-24", + "start-24", + "end-24", + "top-24", + "right-24", + "bottom-24", + "left-24", + "inset-28", + "inset-x-28", + "inset-y-28", + "start-28", + "end-28", + "top-28", + "right-28", + "bottom-28", + "left-28", + "inset-32", + "inset-x-32", + "inset-y-32", + "start-32", + "end-32", + "top-32", + "right-32", + "bottom-32", + "left-32", + "inset-36", + "inset-x-36", + "inset-y-36", + "start-36", + "end-36", + "top-36", + "right-36", + "bottom-36", + "left-36", + "inset-40", + "inset-x-40", + "inset-y-40", + "start-40", + "end-40", + "top-40", + "right-40", + "bottom-40", + "left-40", + "inset-44", + "inset-x-44", + "inset-y-44", + "start-44", + "end-44", + "top-44", + "right-44", + "bottom-44", + "left-44", + "inset-48", + "inset-x-48", + "inset-y-48", + "start-48", + "end-48", + "top-48", + "right-48", + "bottom-48", + "left-48", + "inset-52", + "inset-x-52", + "inset-y-52", + "start-52", + "end-52", + "top-52", + "right-52", + "bottom-52", + "left-52", + "inset-56", + "inset-x-56", + "inset-y-56", + "start-56", + "end-56", + "top-56", + "right-56", + "bottom-56", + "left-56", + "inset-60", + "inset-x-60", + "inset-y-60", + "start-60", + "end-60", + "top-60", + "right-60", + "bottom-60", + "left-60", + "inset-64", + "inset-x-64", + "inset-y-64", + "start-64", + "end-64", + "top-64", + "right-64", + "bottom-64", + "left-64", + "inset-72", + "inset-x-72", + "inset-y-72", + "start-72", + "end-72", + "top-72", + "right-72", + "bottom-72", + "left-72", + "inset-80", + "inset-x-80", + "inset-y-80", + "start-80", + "end-80", + "top-80", + "right-80", + "bottom-80", + "left-80", + "inset-96", + "inset-x-96", + "inset-y-96", + "start-96", + "end-96", + "top-96", + "right-96", + "bottom-96", + "left-96", + "inset-auto", + "inset-1/2", + "inset-1/3", + "inset-2/3", + "inset-1/4", + "inset-2/4", + "inset-3/4", + "inset-full", + "inset-x-auto", + "inset-x-1/2", + "inset-x-1/3", + "inset-x-2/3", + "inset-x-1/4", + "inset-x-2/4", + "inset-x-3/4", + "inset-x-full", + "inset-y-auto", + "inset-y-1/2", + "inset-y-1/3", + "inset-y-2/3", + "inset-y-1/4", + "inset-y-2/4", + "inset-y-3/4", + "inset-y-full", + "start-auto", + "start-1/2", + "start-1/3", + "start-2/3", + "start-1/4", + "start-2/4", + "start-3/4", + "start-full", + "end-auto", + "end-1/2", + "end-1/3", + "end-2/3", + "end-1/4", + "end-2/4", + "end-3/4", + "end-full", + "top-auto", + "top-1/2", + "top-1/3", + "top-2/3", + "top-1/4", + "top-2/4", + "top-3/4", + "top-full", + "right-auto", + "right-1/2", + "right-1/3", + "right-2/3", + "right-1/4", + "right-2/4", + "right-3/4", + "right-full", + "bottom-auto", + "bottom-1/2", + "bottom-1/3", + "bottom-2/3", + "bottom-1/4", + "bottom-2/4", + "bottom-3/4", + "bottom-full", + "left-auto", + "left-1/2", + "left-1/3", + "left-2/3", + "left-1/4", + "left-2/4", + "left-3/4", + "left-full", + "visible", + "invisible", + "collapse", + "z-0", + "z-10", + "z-20", + "z-30", + "z-40", + "z-50", + "z-auto", + "basis-0", + "basis-1", + "basis-2", + "basis-3", + "basis-4", + "basis-5", + "basis-6", + "basis-7", + "basis-8", + "basis-9", + "basis-10", + "basis-11", + "basis-12", + "basis-14", + "basis-16", + "basis-20", + "basis-24", + "basis-28", + "basis-32", + "basis-36", + "basis-40", + "basis-44", + "basis-48", + "basis-52", + "basis-56", + "basis-60", + "basis-64", + "basis-72", + "basis-80", + "basis-96", + "basis-auto", + "basis-px", + "basis-0.5", + "basis-1.5", + "basis-2.5", + "basis-3.5", + "basis-1/2", + "basis-1/3", + "basis-2/3", + "basis-1/4", + "basis-2/4", + "basis-3/4", + "basis-1/5", + "basis-2/5", + "basis-3/5", + "basis-4/5", + "basis-1/6", + "basis-2/6", + "basis-3/6", + "basis-4/6", + "basis-5/6", + "basis-1/12", + "basis-2/12", + "basis-3/12", + "basis-4/12", + "basis-5/12", + "basis-6/12", + "basis-7/12", + "basis-8/12", + "basis-9/12", + "basis-10/12", + "basis-11/12", + "basis-full", + "flex-row", + "flex-row-reverse", + "flex-col", + "flex-col-reverse", + "flex-wrap", + "flex-wrap-reverse", + "flex-nowrap", + "flex-1", + "flex-auto", + "flex-initial", + "flex-none", + "grow", + "grow-0", + "shrink", + "shrink-0", + "order-1", + "order-2", + "order-3", + "order-4", + "order-5", + "order-6", + "order-7", + "order-8", + "order-9", + "order-10", + "order-11", + "order-12", + "order-first", + "order-last", + "order-none", + "grid-cols-1", + "grid-cols-2", + "grid-cols-3", + "grid-cols-4", + "grid-cols-5", + "grid-cols-6", + "grid-cols-7", + "grid-cols-8", + "grid-cols-9", + "grid-cols-10", + "grid-cols-11", + "grid-cols-12", + "grid-cols-none", + "grid-cols-subgrid", + "col-auto", + "col-span-1", + "col-span-2", + "col-span-3", + "col-span-4", + "col-span-5", + "col-span-6", + "col-span-7", + "col-span-8", + "col-span-9", + "col-span-10", + "col-span-11", + "col-span-12", + "col-span-full", + "col-start-1", + "col-start-2", + "col-start-3", + "col-start-4", + "col-start-5", + "col-start-6", + "col-start-7", + "col-start-8", + "col-start-9", + "col-start-10", + "col-start-11", + "col-start-12", + "col-start-13", + "col-start-auto", + "col-end-1", + "col-end-2", + "col-end-3", + "col-end-4", + "col-end-5", + "col-end-6", + "col-end-7", + "col-end-8", + "col-end-9", + "col-end-10", + "col-end-11", + "col-end-12", + "col-end-13", + "col-end-auto", + "grid-rows-1", + "grid-rows-2", + "grid-rows-3", + "grid-rows-4", + "grid-rows-5", + "grid-rows-6", + "grid-rows-7", + "grid-rows-8", + "grid-rows-9", + "grid-rows-10", + "grid-rows-11", + "grid-rows-12", + "grid-rows-none", + "grid-rows-subgrid", + "row-auto", + "row-span-1", + "row-span-2", + "row-span-3", + "row-span-4", + "row-span-5", + "row-span-6", + "row-span-7", + "row-span-8", + "row-span-9", + "row-span-10", + "row-span-11", + "row-span-12", + "row-span-full", + "row-start-1", + "row-start-2", + "row-start-3", + "row-start-4", + "row-start-5", + "row-start-6", + "row-start-7", + "row-start-8", + "row-start-9", + "row-start-10", + "row-start-11", + "row-start-12", + "row-start-13", + "row-start-auto", + "row-end-1", + "row-end-2", + "row-end-3", + "row-end-4", + "row-end-5", + "row-end-6", + "row-end-7", + "row-end-8", + "row-end-9", + "row-end-10", + "row-end-11", + "row-end-12", + "row-end-13", + "row-end-auto", + "grid-flow-row", + "grid-flow-col", + "grid-flow-dense", + "grid-flow-row-dense", + "grid-flow-col-dense", + "auto-cols-auto", + "auto-cols-min", + "auto-cols-max", + "auto-cols-fr", + "auto-rows-auto", + "auto-rows-min", + "auto-rows-max", + "auto-rows-fr", + "gap-0", + "gap-x-0", + "gap-y-0", + "gap-px", + "gap-x-px", + "gap-y-px", + "gap-0.5", + "gap-x-0.5", + "gap-y-0.5", + "gap-1", + "gap-x-1", + "gap-y-1", + "gap-1.5", + "gap-x-1.5", + "gap-y-1.5", + "gap-2", + "gap-x-2", + "gap-y-2", + "gap-2.5", + "gap-x-2.5", + "gap-y-2.5", + "gap-3", + "gap-x-3", + "gap-y-3", + "gap-3.5", + "gap-x-3.5", + "gap-y-3.5", + "gap-4", + "gap-x-4", + "gap-y-4", + "gap-5", + "gap-x-5", + "gap-y-5", + "gap-6", + "gap-x-6", + "gap-y-6", + "gap-7", + "gap-x-7", + "gap-y-7", + "gap-8", + "gap-x-8", + "gap-y-8", + "gap-9", + "gap-x-9", + "gap-y-9", + "gap-10", + "gap-x-10", + "gap-y-10", + "gap-11", + "gap-x-11", + "gap-y-11", + "gap-12", + "gap-x-12", + "gap-y-12", + "gap-14", + "gap-x-14", + "gap-y-14", + "gap-16", + "gap-x-16", + "gap-y-16", + "gap-20", + "gap-x-20", + "gap-y-20", + "gap-24", + "gap-x-24", + "gap-y-24", + "gap-28", + "gap-x-28", + "gap-y-28", + "gap-32", + "gap-x-32", + "gap-y-32", + "gap-36", + "gap-x-36", + "gap-y-36", + "gap-40", + "gap-x-40", + "gap-y-40", + "gap-44", + "gap-x-44", + "gap-y-44", + "gap-48", + "gap-x-48", + "gap-y-48", + "gap-52", + "gap-x-52", + "gap-y-52", + "gap-56", + "gap-x-56", + "gap-y-56", + "gap-60", + "gap-x-60", + "gap-y-60", + "gap-64", + "gap-x-64", + "gap-y-64", + "gap-72", + "gap-x-72", + "gap-y-72", + "gap-80", + "gap-x-80", + "gap-y-80", + "gap-96", + "gap-x-96", + "gap-y-96", + "justify-normal", + "justify-start", + "justify-end", + "justify-center", + "justify-between", + "justify-around", + "justify-evenly", + "justify-stretch", + "justify-items-start", + "justify-items-end", + "justify-items-center", + "justify-items-stretch", + "justify-self-auto", + "justify-self-start", + "justify-self-end", + "justify-self-center", + "justify-self-stretch", + "content-normal", + "content-center", + "content-start", + "content-end", + "content-between", + "content-around", + "content-evenly", + "content-baseline", + "content-stretch", + "items-start", + "items-end", + "items-center", + "items-baseline", + "items-stretch", + "self-auto", + "self-start", + "self-end", + "self-center", + "self-stretch", + "self-baseline", + "place-content-center", + "place-content-start", + "place-content-end", + "place-content-between", + "place-content-around", + "place-content-evenly", + "place-content-baseline", + "place-content-stretch", + "place-items-start", + "place-items-end", + "place-items-center", + "place-items-baseline", + "place-items-stretch", + "place-self-auto", + "place-self-start", + "place-self-end", + "place-self-center", + "place-self-stretch", + "p-0", + "px-0", + "py-0", + "ps-0", + "pe-0", + "pt-0", + "pr-0", + "pb-0", + "pl-0", + "p-px", + "px-px", + "py-px", + "ps-px", + "pe-px", + "pt-px", + "pr-px", + "pb-px", + "pl-px", + "p-0.5", + "px-0.5", + "py-0.5", + "ps-0.5", + "pe-0.5", + "pt-0.5", + "pr-0.5", + "pb-0.5", + "pl-0.5", + "p-1", + "px-1", + "py-1", + "ps-1", + "pe-1", + "pt-1", + "pr-1", + "pb-1", + "pl-1", + "p-1.5", + "px-1.5", + "py-1.5", + "ps-1.5", + "pe-1.5", + "pt-1.5", + "pr-1.5", + "pb-1.5", + "pl-1.5", + "p-2", + "px-2", + "py-2", + "ps-2", + "pe-2", + "pt-2", + "pr-2", + "pb-2", + "pl-2", + "p-2.5", + "px-2.5", + "py-2.5", + "ps-2.5", + "pe-2.5", + "pt-2.5", + "pr-2.5", + "pb-2.5", + "pl-2.5", + "p-3", + "px-3", + "py-3", + "ps-3", + "pe-3", + "pt-3", + "pr-3", + "pb-3", + "pl-3", + "p-3.5", + "px-3.5", + "py-3.5", + "ps-3.5", + "pe-3.5", + "pt-3.5", + "pr-3.5", + "pb-3.5", + "pl-3.5", + "p-4", + "px-4", + "py-4", + "ps-4", + "pe-4", + "pt-4", + "pr-4", + "pb-4", + "pl-4", + "p-5", + "px-5", + "py-5", + "ps-5", + "pe-5", + "pt-5", + "pr-5", + "pb-5", + "pl-5", + "p-6", + "px-6", + "py-6", + "ps-6", + "pe-6", + "pt-6", + "pr-6", + "pb-6", + "pl-6", + "p-7", + "px-7", + "py-7", + "ps-7", + "pe-7", + "pt-7", + "pr-7", + "pb-7", + "pl-7", + "p-8", + "px-8", + "py-8", + "ps-8", + "pe-8", + "pt-8", + "pr-8", + "pb-8", + "pl-8", + "p-9", + "px-9", + "py-9", + "ps-9", + "pe-9", + "pt-9", + "pr-9", + "pb-9", + "pl-9", + "p-10", + "px-10", + "py-10", + "ps-10", + "pe-10", + "pt-10", + "pr-10", + "pb-10", + "pl-10", + "p-11", + "px-11", + "py-11", + "ps-11", + "pe-11", + "pt-11", + "pr-11", + "pb-11", + "pl-11", + "p-12", + "px-12", + "py-12", + "ps-12", + "pe-12", + "pt-12", + "pr-12", + "pb-12", + "pl-12", + "p-14", + "px-14", + "py-14", + "ps-14", + "pe-14", + "pt-14", + "pr-14", + "pb-14", + "pl-14", + "p-16", + "px-16", + "py-16", + "ps-16", + "pe-16", + "pt-16", + "pr-16", + "pb-16", + "pl-16", + "p-20", + "px-20", + "py-20", + "ps-20", + "pe-20", + "pt-20", + "pr-20", + "pb-20", + "pl-20", + "p-24", + "px-24", + "py-24", + "ps-24", + "pe-24", + "pt-24", + "pr-24", + "pb-24", + "pl-24", + "p-28", + "px-28", + "py-28", + "ps-28", + "pe-28", + "pt-28", + "pr-28", + "pb-28", + "pl-28", + "p-32", + "px-32", + "py-32", + "ps-32", + "pe-32", + "pt-32", + "pr-32", + "pb-32", + "pl-32", + "p-36", + "px-36", + "py-36", + "ps-36", + "pe-36", + "pt-36", + "pr-36", + "pb-36", + "pl-36", + "p-40", + "px-40", + "py-40", + "ps-40", + "pe-40", + "pt-40", + "pr-40", + "pb-40", + "pl-40", + "p-44", + "px-44", + "py-44", + "ps-44", + "pe-44", + "pt-44", + "pr-44", + "pb-44", + "pl-44", + "p-48", + "px-48", + "py-48", + "ps-48", + "pe-48", + "pt-48", + "pr-48", + "pb-48", + "pl-48", + "p-52", + "px-52", + "py-52", + "ps-52", + "pe-52", + "pt-52", + "pr-52", + "pb-52", + "pl-52", + "p-56", + "px-56", + "py-56", + "ps-56", + "pe-56", + "pt-56", + "pr-56", + "pb-56", + "pl-56", + "p-60", + "px-60", + "py-60", + "ps-60", + "pe-60", + "pt-60", + "pr-60", + "pb-60", + "pl-60", + "p-64", + "px-64", + "py-64", + "ps-64", + "pe-64", + "pt-64", + "pr-64", + "pb-64", + "pl-64", + "p-72", + "px-72", + "py-72", + "ps-72", + "pe-72", + "pt-72", + "pr-72", + "pb-72", + "pl-72", + "p-80", + "px-80", + "py-80", + "ps-80", + "pe-80", + "pt-80", + "pr-80", + "pb-80", + "pl-80", + "p-96", + "px-96", + "py-96", + "ps-96", + "pe-96", + "pt-96", + "pr-96", + "pb-96", + "pl-96", + "m-0", + "mx-0", + "my-0", + "ms-0", + "me-0", + "mt-0", + "mr-0", + "mb-0", + "ml-0", + "m-px", + "mx-px", + "my-px", + "ms-px", + "me-px", + "mt-px", + "mr-px", + "mb-px", + "ml-px", + "m-0.5", + "mx-0.5", + "my-0.5", + "ms-0.5", + "me-0.5", + "mt-0.5", + "mr-0.5", + "mb-0.5", + "ml-0.5", + "m-1", + "mx-1", + "my-1", + "ms-1", + "me-1", + "mt-1", + "mr-1", + "mb-1", + "ml-1", + "m-1.5", + "mx-1.5", + "my-1.5", + "ms-1.5", + "me-1.5", + "mt-1.5", + "mr-1.5", + "mb-1.5", + "ml-1.5", + "m-2", + "mx-2", + "my-2", + "ms-2", + "me-2", + "mt-2", + "mr-2", + "mb-2", + "ml-2", + "m-2.5", + "mx-2.5", + "my-2.5", + "ms-2.5", + "me-2.5", + "mt-2.5", + "mr-2.5", + "mb-2.5", + "ml-2.5", + "m-3", + "mx-3", + "my-3", + "ms-3", + "me-3", + "mt-3", + "mr-3", + "mb-3", + "ml-3", + "m-3.5", + "mx-3.5", + "my-3.5", + "ms-3.5", + "me-3.5", + "mt-3.5", + "mr-3.5", + "mb-3.5", + "ml-3.5", + "m-4", + "mx-4", + "my-4", + "ms-4", + "me-4", + "mt-4", + "mr-4", + "mb-4", + "ml-4", + "m-5", + "mx-5", + "my-5", + "ms-5", + "me-5", + "mt-5", + "mr-5", + "mb-5", + "ml-5", + "m-6", + "mx-6", + "my-6", + "ms-6", + "me-6", + "mt-6", + "mr-6", + "mb-6", + "ml-6", + "m-7", + "mx-7", + "my-7", + "ms-7", + "me-7", + "mt-7", + "mr-7", + "mb-7", + "ml-7", + "m-8", + "mx-8", + "my-8", + "ms-8", + "me-8", + "mt-8", + "mr-8", + "mb-8", + "ml-8", + "m-9", + "mx-9", + "my-9", + "ms-9", + "me-9", + "mt-9", + "mr-9", + "mb-9", + "ml-9", + "m-10", + "mx-10", + "my-10", + "ms-10", + "me-10", + "mt-10", + "mr-10", + "mb-10", + "ml-10", + "m-11", + "mx-11", + "my-11", + "ms-11", + "me-11", + "mt-11", + "mr-11", + "mb-11", + "ml-11", + "m-12", + "mx-12", + "my-12", + "ms-12", + "me-12", + "mt-12", + "mr-12", + "mb-12", + "ml-12", + "m-14", + "mx-14", + "my-14", + "ms-14", + "me-14", + "mt-14", + "mr-14", + "mb-14", + "ml-14", + "m-16", + "mx-16", + "my-16", + "ms-16", + "me-16", + "mt-16", + "mr-16", + "mb-16", + "ml-16", + "m-20", + "mx-20", + "my-20", + "ms-20", + "me-20", + "mt-20", + "mr-20", + "mb-20", + "ml-20", + "m-24", + "mx-24", + "my-24", + "ms-24", + "me-24", + "mt-24", + "mr-24", + "mb-24", + "ml-24", + "m-28", + "mx-28", + "my-28", + "ms-28", + "me-28", + "mt-28", + "mr-28", + "mb-28", + "ml-28", + "m-32", + "mx-32", + "my-32", + "ms-32", + "me-32", + "mt-32", + "mr-32", + "mb-32", + "ml-32", + "m-36", + "mx-36", + "my-36", + "ms-36", + "me-36", + "mt-36", + "mr-36", + "mb-36", + "ml-36", + "m-40", + "mx-40", + "my-40", + "ms-40", + "me-40", + "mt-40", + "mr-40", + "mb-40", + "ml-40", + "m-44", + "mx-44", + "my-44", + "ms-44", + "me-44", + "mt-44", + "mr-44", + "mb-44", + "ml-44", + "m-48", + "mx-48", + "my-48", + "ms-48", + "me-48", + "mt-48", + "mr-48", + "mb-48", + "ml-48", + "m-52", + "mx-52", + "my-52", + "ms-52", + "me-52", + "mt-52", + "mr-52", + "mb-52", + "ml-52", + "m-56", + "mx-56", + "my-56", + "ms-56", + "me-56", + "mt-56", + "mr-56", + "mb-56", + "ml-56", + "m-60", + "mx-60", + "my-60", + "ms-60", + "me-60", + "mt-60", + "mr-60", + "mb-60", + "ml-60", + "m-64", + "mx-64", + "my-64", + "ms-64", + "me-64", + "mt-64", + "mr-64", + "mb-64", + "ml-64", + "m-72", + "mx-72", + "my-72", + "ms-72", + "me-72", + "mt-72", + "mr-72", + "mb-72", + "ml-72", + "m-80", + "mx-80", + "my-80", + "ms-80", + "me-80", + "mt-80", + "mr-80", + "mb-80", + "ml-80", + "m-96", + "mx-96", + "my-96", + "ms-96", + "me-96", + "mt-96", + "mr-96", + "mb-96", + "ml-96", + "m-auto", + "mx-auto", + "my-auto", + "ms-auto", + "me-auto", + "mt-auto", + "mr-auto", + "mb-auto", + "ml-auto", + "space-x-0", + "space-y-0", + "space-x-0.5", + "space-y-0.5", + "space-x-1", + "space-y-1", + "space-x-1.5", + "space-y-1.5", + "space-x-2", + "space-y-2", + "space-x-2.5", + "space-y-2.5", + "space-x-3", + "space-y-3", + "space-x-3.5", + "space-y-3.5", + "space-x-4", + "space-y-4", + "space-x-5", + "space-y-5", + "space-x-6", + "space-y-6", + "space-x-7", + "space-y-7", + "space-x-8", + "space-y-8", + "space-x-9", + "space-y-9", + "space-x-10", + "space-y-10", + "space-x-11", + "space-y-11", + "space-x-12", + "space-y-12", + "space-x-14", + "space-y-14", + "space-x-16", + "space-y-16", + "space-x-20", + "space-y-20", + "space-x-24", + "space-y-24", + "space-x-28", + "space-y-28", + "space-x-32", + "space-y-32", + "space-x-36", + "space-y-36", + "space-x-40", + "space-y-40", + "space-x-44", + "space-y-44", + "space-x-48", + "space-y-48", + "space-x-52", + "space-y-52", + "space-x-56", + "space-y-56", + "space-x-60", + "space-y-60", + "space-x-64", + "space-y-64", + "space-x-72", + "space-y-72", + "space-x-80", + "space-y-80", + "space-x-96", + "space-y-96", + "space-x-px", + "space-y-px", + "space-y-reverse", + "space-x-reverse", + "w-0", + "w-px", + "w-0.5", + "w-1", + "w-1.5", + "w-2", + "w-2.5", + "w-3", + "w-3.5", + "w-4", + "w-5", + "w-6", + "w-7", + "w-8", + "w-9", + "w-10", + "w-11", + "w-12", + "w-14", + "w-16", + "w-20", + "w-24", + "w-28", + "w-32", + "w-36", + "w-40", + "w-44", + "w-48", + "w-52", + "w-56", + "w-60", + "w-64", + "w-72", + "w-80", + "w-96", + "w-auto", + "w-1/2", + "w-1/3", + "w-2/3", + "w-1/4", + "w-2/4", + "w-3/4", + "w-1/5", + "w-2/5", + "w-3/5", + "w-4/5", + "w-1/6", + "w-2/6", + "w-3/6", + "w-4/6", + "w-5/6", + "w-1/12", + "w-2/12", + "w-3/12", + "w-4/12", + "w-5/12", + "w-6/12", + "w-7/12", + "w-8/12", + "w-9/12", + "w-10/12", + "w-11/12", + "w-full", + "w-screen", + "w-svw", + "w-lvw", + "w-dvw", + "w-min", + "w-max", + "w-fit", + "min-w-0", + "min-w-1", + "min-w-2", + "min-w-3", + "min-w-4", + "min-w-5", + "min-w-6", + "min-w-7", + "min-w-8", + "min-w-9", + "min-w-10", + "min-w-11", + "min-w-12", + "min-w-14", + "min-w-16", + "min-w-20", + "min-w-24", + "min-w-28", + "min-w-32", + "min-w-36", + "min-w-40", + "min-w-44", + "min-w-48", + "min-w-52", + "min-w-56", + "min-w-60", + "min-w-64", + "min-w-72", + "min-w-80", + "min-w-96", + "min-w-px", + "min-w-0.5", + "min-w-1.5", + "min-w-2.5", + "min-w-3.5", + "min-w-full", + "min-w-min", + "min-w-max", + "min-w-fit", + "max-w-0", + "max-w-px", + "max-w-0.5", + "max-w-1", + "max-w-1.5", + "max-w-2", + "max-w-2.5", + "max-w-3", + "max-w-3.5", + "max-w-4", + "max-w-5", + "max-w-6", + "max-w-7", + "max-w-8", + "max-w-9", + "max-w-10", + "max-w-11", + "max-w-12", + "max-w-14", + "max-w-16", + "max-w-20", + "max-w-24", + "max-w-28", + "max-w-32", + "max-w-36", + "max-w-40", + "max-w-44", + "max-w-48", + "max-w-52", + "max-w-56", + "max-w-60", + "max-w-64", + "max-w-72", + "max-w-80", + "max-w-96", + "max-w-none", + "max-w-xs", + "max-w-sm", + "max-w-md", + "max-w-lg", + "max-w-xl", + "max-w-2xl", + "max-w-3xl", + "max-w-4xl", + "max-w-5xl", + "max-w-6xl", + "max-w-7xl", + "max-w-full", + "max-w-min", + "max-w-max", + "max-w-fit", + "max-w-prose", + "max-w-screen-sm", + "max-w-screen-md", + "max-w-screen-lg", + "max-w-screen-xl", + "max-w-screen-2xl", + "h-0", + "h-px", + "h-0.5", + "h-1", + "h-1.5", + "h-2", + "h-2.5", + "h-3", + "h-3.5", + "h-4", + "h-5", + "h-6", + "h-7", + "h-8", + "h-9", + "h-10", + "h-11", + "h-12", + "h-14", + "h-16", + "h-20", + "h-24", + "h-28", + "h-32", + "h-36", + "h-40", + "h-44", + "h-48", + "h-52", + "h-56", + "h-60", + "h-64", + "h-72", + "h-80", + "h-96", + "h-auto", + "h-1/2", + "h-1/3", + "h-2/3", + "h-1/4", + "h-2/4", + "h-3/4", + "h-1/5", + "h-2/5", + "h-3/5", + "h-4/5", + "h-1/6", + "h-2/6", + "h-3/6", + "h-4/6", + "h-5/6", + "h-full", + "h-screen", + "h-svh", + "h-lvh", + "h-dvh", + "h-min", + "h-max", + "h-fit", + "min-h-0", + "min-h-1", + "min-h-2", + "min-h-3", + "min-h-4", + "min-h-5", + "min-h-6", + "min-h-7", + "min-h-8", + "min-h-9", + "min-h-10", + "min-h-11", + "min-h-12", + "min-h-14", + "min-h-16", + "min-h-20", + "min-h-24", + "min-h-28", + "min-h-32", + "min-h-36", + "min-h-40", + "min-h-44", + "min-h-48", + "min-h-52", + "min-h-56", + "min-h-60", + "min-h-64", + "min-h-72", + "min-h-80", + "min-h-96", + "min-h-px", + "min-h-0.5", + "min-h-1.5", + "min-h-2.5", + "min-h-3.5", + "min-h-full", + "min-h-screen", + "min-h-svh", + "min-h-lvh", + "min-h-dvh", + "min-h-min", + "min-h-max", + "min-h-fit", + "max-h-0", + "max-h-px", + "max-h-0.5", + "max-h-1", + "max-h-1.5", + "max-h-2", + "max-h-2.5", + "max-h-3", + "max-h-3.5", + "max-h-4", + "max-h-5", + "max-h-6", + "max-h-7", + "max-h-8", + "max-h-9", + "max-h-10", + "max-h-11", + "max-h-12", + "max-h-14", + "max-h-16", + "max-h-20", + "max-h-24", + "max-h-28", + "max-h-32", + "max-h-36", + "max-h-40", + "max-h-44", + "max-h-48", + "max-h-52", + "max-h-56", + "max-h-60", + "max-h-64", + "max-h-72", + "max-h-80", + "max-h-96", + "max-h-none", + "max-h-full", + "max-h-screen", + "max-h-svh", + "max-h-lvh", + "max-h-dvh", + "max-h-min", + "max-h-max", + "max-h-fit", + "size-0", + "size-px", + "size-0.5", + "size-1", + "size-1.5", + "size-2", + "size-2.5", + "size-3", + "size-3.5", + "size-4", + "size-5", + "size-6", + "size-7", + "size-8", + "size-9", + "size-10", + "size-11", + "size-12", + "size-14", + "size-16", + "size-20", + "size-24", + "size-28", + "size-32", + "size-36", + "size-40", + "size-44", + "size-48", + "size-52", + "size-56", + "size-60", + "size-64", + "size-72", + "size-80", + "size-96", + "size-auto", + "size-1/2", + "size-1/3", + "size-2/3", + "size-1/4", + "size-2/4", + "size-3/4", + "size-1/5", + "size-2/5", + "size-3/5", + "size-4/5", + "size-1/6", + "size-2/6", + "size-3/6", + "size-4/6", + "size-5/6", + "size-1/12", + "size-2/12", + "size-3/12", + "size-4/12", + "size-5/12", + "size-6/12", + "size-7/12", + "size-8/12", + "size-9/12", + "size-10/12", + "size-11/12", + "size-full", + "size-min", + "size-max", + "size-fit", + "font-sans", + "font-serif", + "font-mono", + "text-xs", + "text-sm", + "text-base", + "text-lg", + "text-xl", + "text-2xl", + "text-3xl", + "text-4xl", + "text-5xl", + "text-6xl", + "text-7xl", + "text-8xl", + "text-9xl", + "antialiased", + "subpixel-antialiased", + "italic", + "not-italic", + "font-thin", + "font-extralight", + "font-light", + "font-normal", + "font-medium", + "font-semibold", + "font-bold", + "font-extrabold", + "font-black", + "normal-nums", + "ordinal", + "slashed-zero", + "lining-nums", + "oldstyle-nums", + "proportional-nums", + "tabular-nums", + "diagonal-fractions", + "stacked-fractions", + "tracking-tighter", + "tracking-tight", + "tracking-normal", + "tracking-wide", + "tracking-wider", + "tracking-widest", + "line-clamp-1", + "line-clamp-2", + "line-clamp-3", + "line-clamp-4", + "line-clamp-5", + "line-clamp-6", + "line-clamp-none", + "leading-3", + "leading-4", + "leading-5", + "leading-6", + "leading-7", + "leading-8", + "leading-9", + "leading-10", + "leading-none", + "leading-tight", + "leading-snug", + "leading-normal", + "leading-relaxed", + "leading-loose", + "list-image-none", + "list-inside", + "list-outside", + "list-none", + "list-disc", + "list-decimal", + "text-left", + "text-center", + "text-right", + "text-justify", + "text-start", + "text-end", + "text-inherit", + "text-current", + "text-transparent", + "text-black", + "text-white", + "text-slate-50", + "text-slate-100", + "text-slate-200", + "text-slate-300", + "text-slate-400", + "text-slate-500", + "text-slate-600", + "text-slate-700", + "text-slate-800", + "text-slate-900", + "text-slate-950", + "text-gray-50", + "text-gray-100", + "text-gray-200", + "text-gray-300", + "text-gray-400", + "text-gray-500", + "text-gray-600", + "text-gray-700", + "text-gray-800", + "text-gray-900", + "text-gray-950", + "text-zinc-50", + "text-zinc-100", + "text-zinc-200", + "text-zinc-300", + "text-zinc-400", + "text-zinc-500", + "text-zinc-600", + "text-zinc-700", + "text-zinc-800", + "text-zinc-900", + "text-zinc-950", + "text-neutral-50", + "text-neutral-100", + "text-neutral-200", + "text-neutral-300", + "text-neutral-400", + "text-neutral-500", + "text-neutral-600", + "text-neutral-700", + "text-neutral-800", + "text-neutral-900", + "text-neutral-950", + "text-stone-50", + "text-stone-100", + "text-stone-200", + "text-stone-300", + "text-stone-400", + "text-stone-500", + "text-stone-600", + "text-stone-700", + "text-stone-800", + "text-stone-900", + "text-stone-950", + "text-red-50", + "text-red-100", + "text-red-200", + "text-red-300", + "text-red-400", + "text-red-500", + "text-red-600", + "text-red-700", + "text-red-800", + "text-red-900", + "text-red-950", + "text-orange-50", + "text-orange-100", + "text-orange-200", + "text-orange-300", + "text-orange-400", + "text-orange-500", + "text-orange-600", + "text-orange-700", + "text-orange-800", + "text-orange-900", + "text-orange-950", + "text-amber-50", + "text-amber-100", + "text-amber-200", + "text-amber-300", + "text-amber-400", + "text-amber-500", + "text-amber-600", + "text-amber-700", + "text-amber-800", + "text-amber-900", + "text-amber-950", + "text-yellow-50", + "text-yellow-100", + "text-yellow-200", + "text-yellow-300", + "text-yellow-400", + "text-yellow-500", + "text-yellow-600", + "text-yellow-700", + "text-yellow-800", + "text-yellow-900", + "text-yellow-950", + "text-lime-50", + "text-lime-100", + "text-lime-200", + "text-lime-300", + "text-lime-400", + "text-lime-500", + "text-lime-600", + "text-lime-700", + "text-lime-800", + "text-lime-900", + "text-lime-950", + "text-green-50", + "text-green-100", + "text-green-200", + "text-green-300", + "text-green-400", + "text-green-500", + "text-green-600", + "text-green-700", + "text-green-800", + "text-green-900", + "text-green-950", + "text-emerald-50", + "text-emerald-100", + "text-emerald-200", + "text-emerald-300", + "text-emerald-400", + "text-emerald-500", + "text-emerald-600", + "text-emerald-700", + "text-emerald-800", + "text-emerald-900", + "text-emerald-950", + "text-teal-50", + "text-teal-100", + "text-teal-200", + "text-teal-300", + "text-teal-400", + "text-teal-500", + "text-teal-600", + "text-teal-700", + "text-teal-800", + "text-teal-900", + "text-teal-950", + "text-cyan-50", + "text-cyan-100", + "text-cyan-200", + "text-cyan-300", + "text-cyan-400", + "text-cyan-500", + "text-cyan-600", + "text-cyan-700", + "text-cyan-800", + "text-cyan-900", + "text-cyan-950", + "text-sky-50", + "text-sky-100", + "text-sky-200", + "text-sky-300", + "text-sky-400", + "text-sky-500", + "text-sky-600", + "text-sky-700", + "text-sky-800", + "text-sky-900", + "text-sky-950", + "text-blue-50", + "text-blue-100", + "text-blue-200", + "text-blue-300", + "text-blue-400", + "text-blue-500", + "text-blue-600", + "text-blue-700", + "text-blue-800", + "text-blue-900", + "text-blue-950", + "text-indigo-50", + "text-indigo-100", + "text-indigo-200", + "text-indigo-300", + "text-indigo-400", + "text-indigo-500", + "text-indigo-600", + "text-indigo-700", + "text-indigo-800", + "text-indigo-900", + "text-indigo-950", + "text-violet-50", + "text-violet-100", + "text-violet-200", + "text-violet-300", + "text-violet-400", + "text-violet-500", + "text-violet-600", + "text-violet-700", + "text-violet-800", + "text-violet-900", + "text-violet-950", + "text-purple-50", + "text-purple-100", + "text-purple-200", + "text-purple-300", + "text-purple-400", + "text-purple-500", + "text-purple-600", + "text-purple-700", + "text-purple-800", + "text-purple-900", + "text-purple-950", + "text-fuchsia-50", + "text-fuchsia-100", + "text-fuchsia-200", + "text-fuchsia-300", + "text-fuchsia-400", + "text-fuchsia-500", + "text-fuchsia-600", + "text-fuchsia-700", + "text-fuchsia-800", + "text-fuchsia-900", + "text-fuchsia-950", + "text-pink-50", + "text-pink-100", + "text-pink-200", + "text-pink-300", + "text-pink-400", + "text-pink-500", + "text-pink-600", + "text-pink-700", + "text-pink-800", + "text-pink-900", + "text-pink-950", + "text-rose-50", + "text-rose-100", + "text-rose-200", + "text-rose-300", + "text-rose-400", + "text-rose-500", + "text-rose-600", + "text-rose-700", + "text-rose-800", + "text-rose-900", + "text-rose-950", + "underline", + "overline", + "line-through", + "no-underline", + "decoration-inherit", + "decoration-current", + "decoration-transparent", + "decoration-black", + "decoration-white", + "decoration-slate-50", + "decoration-slate-100", + "decoration-slate-200", + "decoration-slate-300", + "decoration-slate-400", + "decoration-slate-500", + "decoration-slate-600", + "decoration-slate-700", + "decoration-slate-800", + "decoration-slate-900", + "decoration-slate-950", + "decoration-gray-50", + "decoration-gray-100", + "decoration-gray-200", + "decoration-gray-300", + "decoration-gray-400", + "decoration-gray-500", + "decoration-gray-600", + "decoration-gray-700", + "decoration-gray-800", + "decoration-gray-900", + "decoration-gray-950", + "decoration-zinc-50", + "decoration-zinc-100", + "decoration-zinc-200", + "decoration-zinc-300", + "decoration-zinc-400", + "decoration-zinc-500", + "decoration-zinc-600", + "decoration-zinc-700", + "decoration-zinc-800", + "decoration-zinc-900", + "decoration-zinc-950", + "decoration-neutral-50", + "decoration-neutral-100", + "decoration-neutral-200", + "decoration-neutral-300", + "decoration-neutral-400", + "decoration-neutral-500", + "decoration-neutral-600", + "decoration-neutral-700", + "decoration-neutral-800", + "decoration-neutral-900", + "decoration-neutral-950", + "decoration-stone-50", + "decoration-stone-100", + "decoration-stone-200", + "decoration-stone-300", + "decoration-stone-400", + "decoration-stone-500", + "decoration-stone-600", + "decoration-stone-700", + "decoration-stone-800", + "decoration-stone-900", + "decoration-stone-950", + "decoration-red-50", + "decoration-red-100", + "decoration-red-200", + "decoration-red-300", + "decoration-red-400", + "decoration-red-500", + "decoration-red-600", + "decoration-red-700", + "decoration-red-800", + "decoration-red-900", + "decoration-red-950", + "decoration-orange-50", + "decoration-orange-100", + "decoration-orange-200", + "decoration-orange-300", + "decoration-orange-400", + "decoration-orange-500", + "decoration-orange-600", + "decoration-orange-700", + "decoration-orange-800", + "decoration-orange-900", + "decoration-orange-950", + "decoration-amber-50", + "decoration-amber-100", + "decoration-amber-200", + "decoration-amber-300", + "decoration-amber-400", + "decoration-amber-500", + "decoration-amber-600", + "decoration-amber-700", + "decoration-amber-800", + "decoration-amber-900", + "decoration-amber-950", + "decoration-yellow-50", + "decoration-yellow-100", + "decoration-yellow-200", + "decoration-yellow-300", + "decoration-yellow-400", + "decoration-yellow-500", + "decoration-yellow-600", + "decoration-yellow-700", + "decoration-yellow-800", + "decoration-yellow-900", + "decoration-yellow-950", + "decoration-lime-50", + "decoration-lime-100", + "decoration-lime-200", + "decoration-lime-300", + "decoration-lime-400", + "decoration-lime-500", + "decoration-lime-600", + "decoration-lime-700", + "decoration-lime-800", + "decoration-lime-900", + "decoration-lime-950", + "decoration-green-50", + "decoration-green-100", + "decoration-green-200", + "decoration-green-300", + "decoration-green-400", + "decoration-green-500", + "decoration-green-600", + "decoration-green-700", + "decoration-green-800", + "decoration-green-900", + "decoration-green-950", + "decoration-emerald-50", + "decoration-emerald-100", + "decoration-emerald-200", + "decoration-emerald-300", + "decoration-emerald-400", + "decoration-emerald-500", + "decoration-emerald-600", + "decoration-emerald-700", + "decoration-emerald-800", + "decoration-emerald-900", + "decoration-emerald-950", + "decoration-teal-50", + "decoration-teal-100", + "decoration-teal-200", + "decoration-teal-300", + "decoration-teal-400", + "decoration-teal-500", + "decoration-teal-600", + "decoration-teal-700", + "decoration-teal-800", + "decoration-teal-900", + "decoration-teal-950", + "decoration-cyan-50", + "decoration-cyan-100", + "decoration-cyan-200", + "decoration-cyan-300", + "decoration-cyan-400", + "decoration-cyan-500", + "decoration-cyan-600", + "decoration-cyan-700", + "decoration-cyan-800", + "decoration-cyan-900", + "decoration-cyan-950", + "decoration-sky-50", + "decoration-sky-100", + "decoration-sky-200", + "decoration-sky-300", + "decoration-sky-400", + "decoration-sky-500", + "decoration-sky-600", + "decoration-sky-700", + "decoration-sky-800", + "decoration-sky-900", + "decoration-sky-950", + "decoration-blue-50", + "decoration-blue-100", + "decoration-blue-200", + "decoration-blue-300", + "decoration-blue-400", + "decoration-blue-500", + "decoration-blue-600", + "decoration-blue-700", + "decoration-blue-800", + "decoration-blue-900", + "decoration-blue-950", + "decoration-indigo-50", + "decoration-indigo-100", + "decoration-indigo-200", + "decoration-indigo-300", + "decoration-indigo-400", + "decoration-indigo-500", + "decoration-indigo-600", + "decoration-indigo-700", + "decoration-indigo-800", + "decoration-indigo-900", + "decoration-indigo-950", + "decoration-violet-50", + "decoration-violet-100", + "decoration-violet-200", + "decoration-violet-300", + "decoration-violet-400", + "decoration-violet-500", + "decoration-violet-600", + "decoration-violet-700", + "decoration-violet-800", + "decoration-violet-900", + "decoration-violet-950", + "decoration-purple-50", + "decoration-purple-100", + "decoration-purple-200", + "decoration-purple-300", + "decoration-purple-400", + "decoration-purple-500", + "decoration-purple-600", + "decoration-purple-700", + "decoration-purple-800", + "decoration-purple-900", + "decoration-purple-950", + "decoration-fuchsia-50", + "decoration-fuchsia-100", + "decoration-fuchsia-200", + "decoration-fuchsia-300", + "decoration-fuchsia-400", + "decoration-fuchsia-500", + "decoration-fuchsia-600", + "decoration-fuchsia-700", + "decoration-fuchsia-800", + "decoration-fuchsia-900", + "decoration-fuchsia-950", + "decoration-pink-50", + "decoration-pink-100", + "decoration-pink-200", + "decoration-pink-300", + "decoration-pink-400", + "decoration-pink-500", + "decoration-pink-600", + "decoration-pink-700", + "decoration-pink-800", + "decoration-pink-900", + "decoration-pink-950", + "decoration-rose-50", + "decoration-rose-100", + "decoration-rose-200", + "decoration-rose-300", + "decoration-rose-400", + "decoration-rose-500", + "decoration-rose-600", + "decoration-rose-700", + "decoration-rose-800", + "decoration-rose-900", + "decoration-rose-950", + "decoration-solid", + "decoration-double", + "decoration-dotted", + "decoration-dashed", + "decoration-wavy", + "decoration-auto", + "decoration-from-font", + "decoration-0", + "decoration-1", + "decoration-2", + "decoration-4", + "decoration-8", + "underline-offset-auto", + "underline-offset-0", + "underline-offset-1", + "underline-offset-2", + "underline-offset-4", + "underline-offset-8", + "uppercase", + "lowercase", + "capitalize", + "normal-case", + "truncate", + "text-ellipsis", + "text-clip", + "text-wrap", + "text-nowrap", + "text-balance", + "text-pretty", + "indent-0", + "indent-px", + "indent-0.5", + "indent-1", + "indent-1.5", + "indent-2", + "indent-2.5", + "indent-3", + "indent-3.5", + "indent-4", + "indent-5", + "indent-6", + "indent-7", + "indent-8", + "indent-9", + "indent-10", + "indent-11", + "indent-12", + "indent-14", + "indent-16", + "indent-20", + "indent-24", + "indent-28", + "indent-32", + "indent-36", + "indent-40", + "indent-44", + "indent-48", + "indent-52", + "indent-56", + "indent-60", + "indent-64", + "indent-72", + "indent-80", + "indent-96", + "align-baseline", + "align-top", + "align-middle", + "align-bottom", + "align-text-top", + "align-text-bottom", + "align-sub", + "align-super", + "whitespace-normal", + "whitespace-nowrap", + "whitespace-pre", + "whitespace-pre-line", + "whitespace-pre-wrap", + "whitespace-break-spaces", + "break-normal", + "break-words", + "break-all", + "break-keep", + "hyphens-none", + "hyphens-manual", + "hyphens-auto", + "content-none", + "bg-fixed", + "bg-local", + "bg-scroll", + "bg-clip-border", + "bg-clip-padding", + "bg-clip-content", + "bg-clip-text", + "bg-inherit", + "bg-current", + "bg-transparent", + "bg-black", + "bg-white", + "bg-slate-50", + "bg-slate-100", + "bg-slate-200", + "bg-slate-300", + "bg-slate-400", + "bg-slate-500", + "bg-slate-600", + "bg-slate-700", + "bg-slate-800", + "bg-slate-900", + "bg-slate-950", + "bg-gray-50", + "bg-gray-100", + "bg-gray-200", + "bg-gray-300", + "bg-gray-400", + "bg-gray-500", + "bg-gray-600", + "bg-gray-700", + "bg-gray-800", + "bg-gray-900", + "bg-gray-950", + "bg-zinc-50", + "bg-zinc-100", + "bg-zinc-200", + "bg-zinc-300", + "bg-zinc-400", + "bg-zinc-500", + "bg-zinc-600", + "bg-zinc-700", + "bg-zinc-800", + "bg-zinc-900", + "bg-zinc-950", + "bg-neutral-50", + "bg-neutral-100", + "bg-neutral-200", + "bg-neutral-300", + "bg-neutral-400", + "bg-neutral-500", + "bg-neutral-600", + "bg-neutral-700", + "bg-neutral-800", + "bg-neutral-900", + "bg-neutral-950", + "bg-stone-50", + "bg-stone-100", + "bg-stone-200", + "bg-stone-300", + "bg-stone-400", + "bg-stone-500", + "bg-stone-600", + "bg-stone-700", + "bg-stone-800", + "bg-stone-900", + "bg-stone-950", + "bg-red-50", + "bg-red-100", + "bg-red-200", + "bg-red-300", + "bg-red-400", + "bg-red-500", + "bg-red-600", + "bg-red-700", + "bg-red-800", + "bg-red-900", + "bg-red-950", + "bg-orange-50", + "bg-orange-100", + "bg-orange-200", + "bg-orange-300", + "bg-orange-400", + "bg-orange-500", + "bg-orange-600", + "bg-orange-700", + "bg-orange-800", + "bg-orange-900", + "bg-orange-950", + "bg-amber-50", + "bg-amber-100", + "bg-amber-200", + "bg-amber-300", + "bg-amber-400", + "bg-amber-500", + "bg-amber-600", + "bg-amber-700", + "bg-amber-800", + "bg-amber-900", + "bg-amber-950", + "bg-yellow-50", + "bg-yellow-100", + "bg-yellow-200", + "bg-yellow-300", + "bg-yellow-400", + "bg-yellow-500", + "bg-yellow-600", + "bg-yellow-700", + "bg-yellow-800", + "bg-yellow-900", + "bg-yellow-950", + "bg-lime-50", + "bg-lime-100", + "bg-lime-200", + "bg-lime-300", + "bg-lime-400", + "bg-lime-500", + "bg-lime-600", + "bg-lime-700", + "bg-lime-800", + "bg-lime-900", + "bg-lime-950", + "bg-green-50", + "bg-green-100", + "bg-green-200", + "bg-green-300", + "bg-green-400", + "bg-green-500", + "bg-green-600", + "bg-green-700", + "bg-green-800", + "bg-green-900", + "bg-green-950", + "bg-emerald-50", + "bg-emerald-100", + "bg-emerald-200", + "bg-emerald-300", + "bg-emerald-400", + "bg-emerald-500", + "bg-emerald-600", + "bg-emerald-700", + "bg-emerald-800", + "bg-emerald-900", + "bg-emerald-950", + "bg-teal-50", + "bg-teal-100", + "bg-teal-200", + "bg-teal-300", + "bg-teal-400", + "bg-teal-500", + "bg-teal-600", + "bg-teal-700", + "bg-teal-800", + "bg-teal-900", + "bg-teal-950", + "bg-cyan-50", + "bg-cyan-100", + "bg-cyan-200", + "bg-cyan-300", + "bg-cyan-400", + "bg-cyan-500", + "bg-cyan-600", + "bg-cyan-700", + "bg-cyan-800", + "bg-cyan-900", + "bg-cyan-950", + "bg-sky-50", + "bg-sky-100", + "bg-sky-200", + "bg-sky-300", + "bg-sky-400", + "bg-sky-500", + "bg-sky-600", + "bg-sky-700", + "bg-sky-800", + "bg-sky-900", + "bg-sky-950", + "bg-blue-50", + "bg-blue-100", + "bg-blue-200", + "bg-blue-300", + "bg-blue-400", + "bg-blue-500", + "bg-blue-600", + "bg-blue-700", + "bg-blue-800", + "bg-blue-900", + "bg-blue-950", + "bg-indigo-50", + "bg-indigo-100", + "bg-indigo-200", + "bg-indigo-300", + "bg-indigo-400", + "bg-indigo-500", + "bg-indigo-600", + "bg-indigo-700", + "bg-indigo-800", + "bg-indigo-900", + "bg-indigo-950", + "bg-violet-50", + "bg-violet-100", + "bg-violet-200", + "bg-violet-300", + "bg-violet-400", + "bg-violet-500", + "bg-violet-600", + "bg-violet-700", + "bg-violet-800", + "bg-violet-900", + "bg-violet-950", + "bg-purple-50", + "bg-purple-100", + "bg-purple-200", + "bg-purple-300", + "bg-purple-400", + "bg-purple-500", + "bg-purple-600", + "bg-purple-700", + "bg-purple-800", + "bg-purple-900", + "bg-purple-950", + "bg-fuchsia-50", + "bg-fuchsia-100", + "bg-fuchsia-200", + "bg-fuchsia-300", + "bg-fuchsia-400", + "bg-fuchsia-500", + "bg-fuchsia-600", + "bg-fuchsia-700", + "bg-fuchsia-800", + "bg-fuchsia-900", + "bg-fuchsia-950", + "bg-pink-50", + "bg-pink-100", + "bg-pink-200", + "bg-pink-300", + "bg-pink-400", + "bg-pink-500", + "bg-pink-600", + "bg-pink-700", + "bg-pink-800", + "bg-pink-900", + "bg-pink-950", + "bg-rose-50", + "bg-rose-100", + "bg-rose-200", + "bg-rose-300", + "bg-rose-400", + "bg-rose-500", + "bg-rose-600", + "bg-rose-700", + "bg-rose-800", + "bg-rose-900", + "bg-rose-950", + "bg-origin-border", + "bg-origin-padding", + "bg-origin-content", + "bg-bottom", + "bg-center", + "bg-left", + "bg-left-bottom", + "bg-left-top", + "bg-right", + "bg-right-bottom", + "bg-right-top", + "bg-top", + "bg-repeat", + "bg-no-repeat", + "bg-repeat-x", + "bg-repeat-y", + "bg-repeat-round", + "bg-repeat-space", + "bg-auto", + "bg-cover", + "bg-contain", + "bg-none", + "bg-gradient-to-t", + "bg-gradient-to-tr", + "bg-gradient-to-r", + "bg-gradient-to-br", + "bg-gradient-to-b", + "bg-gradient-to-bl", + "bg-gradient-to-l", + "bg-gradient-to-tl", + "from-inherit", + "from-current", + "from-transparent", + "from-black", + "from-white", + "from-slate-50", + "from-slate-100", + "from-slate-200", + "from-slate-300", + "from-slate-400", + "from-slate-500", + "from-slate-600", + "from-slate-700", + "from-slate-800", + "from-slate-900", + "from-slate-950", + "from-gray-50", + "from-gray-100", + "from-gray-200", + "from-gray-300", + "from-gray-400", + "from-gray-500", + "from-gray-600", + "from-gray-700", + "from-gray-800", + "from-gray-900", + "from-gray-950", + "from-zinc-50", + "from-zinc-100", + "from-zinc-200", + "from-zinc-300", + "from-zinc-400", + "from-zinc-500", + "from-zinc-600", + "from-zinc-700", + "from-zinc-800", + "from-zinc-900", + "from-zinc-950", + "from-neutral-50", + "from-neutral-100", + "from-neutral-200", + "from-neutral-300", + "from-neutral-400", + "from-neutral-500", + "from-neutral-600", + "from-neutral-700", + "from-neutral-800", + "from-neutral-900", + "from-neutral-950", + "from-stone-50", + "from-stone-100", + "from-stone-200", + "from-stone-300", + "from-stone-400", + "from-stone-500", + "from-stone-600", + "from-stone-700", + "from-stone-800", + "from-stone-900", + "from-stone-950", + "from-red-50", + "from-red-100", + "from-red-200", + "from-red-300", + "from-red-400", + "from-red-500", + "from-red-600", + "from-red-700", + "from-red-800", + "from-red-900", + "from-red-950", + "from-orange-50", + "from-orange-100", + "from-orange-200", + "from-orange-300", + "from-orange-400", + "from-orange-500", + "from-orange-600", + "from-orange-700", + "from-orange-800", + "from-orange-900", + "from-orange-950", + "from-amber-50", + "from-amber-100", + "from-amber-200", + "from-amber-300", + "from-amber-400", + "from-amber-500", + "from-amber-600", + "from-amber-700", + "from-amber-800", + "from-amber-900", + "from-amber-950", + "from-yellow-50", + "from-yellow-100", + "from-yellow-200", + "from-yellow-300", + "from-yellow-400", + "from-yellow-500", + "from-yellow-600", + "from-yellow-700", + "from-yellow-800", + "from-yellow-900", + "from-yellow-950", + "from-lime-50", + "from-lime-100", + "from-lime-200", + "from-lime-300", + "from-lime-400", + "from-lime-500", + "from-lime-600", + "from-lime-700", + "from-lime-800", + "from-lime-900", + "from-lime-950", + "from-green-50", + "from-green-100", + "from-green-200", + "from-green-300", + "from-green-400", + "from-green-500", + "from-green-600", + "from-green-700", + "from-green-800", + "from-green-900", + "from-green-950", + "from-emerald-50", + "from-emerald-100", + "from-emerald-200", + "from-emerald-300", + "from-emerald-400", + "from-emerald-500", + "from-emerald-600", + "from-emerald-700", + "from-emerald-800", + "from-emerald-900", + "from-emerald-950", + "from-teal-50", + "from-teal-100", + "from-teal-200", + "from-teal-300", + "from-teal-400", + "from-teal-500", + "from-teal-600", + "from-teal-700", + "from-teal-800", + "from-teal-900", + "from-teal-950", + "from-cyan-50", + "from-cyan-100", + "from-cyan-200", + "from-cyan-300", + "from-cyan-400", + "from-cyan-500", + "from-cyan-600", + "from-cyan-700", + "from-cyan-800", + "from-cyan-900", + "from-cyan-950", + "from-sky-50", + "from-sky-100", + "from-sky-200", + "from-sky-300", + "from-sky-400", + "from-sky-500", + "from-sky-600", + "from-sky-700", + "from-sky-800", + "from-sky-900", + "from-sky-950", + "from-blue-50", + "from-blue-100", + "from-blue-200", + "from-blue-300", + "from-blue-400", + "from-blue-500", + "from-blue-600", + "from-blue-700", + "from-blue-800", + "from-blue-900", + "from-blue-950", + "from-indigo-50", + "from-indigo-100", + "from-indigo-200", + "from-indigo-300", + "from-indigo-400", + "from-indigo-500", + "from-indigo-600", + "from-indigo-700", + "from-indigo-800", + "from-indigo-900", + "from-indigo-950", + "from-violet-50", + "from-violet-100", + "from-violet-200", + "from-violet-300", + "from-violet-400", + "from-violet-500", + "from-violet-600", + "from-violet-700", + "from-violet-800", + "from-violet-900", + "from-violet-950", + "from-purple-50", + "from-purple-100", + "from-purple-200", + "from-purple-300", + "from-purple-400", + "from-purple-500", + "from-purple-600", + "from-purple-700", + "from-purple-800", + "from-purple-900", + "from-purple-950", + "from-fuchsia-50", + "from-fuchsia-100", + "from-fuchsia-200", + "from-fuchsia-300", + "from-fuchsia-400", + "from-fuchsia-500", + "from-fuchsia-600", + "from-fuchsia-700", + "from-fuchsia-800", + "from-fuchsia-900", + "from-fuchsia-950", + "from-pink-50", + "from-pink-100", + "from-pink-200", + "from-pink-300", + "from-pink-400", + "from-pink-500", + "from-pink-600", + "from-pink-700", + "from-pink-800", + "from-pink-900", + "from-pink-950", + "from-rose-50", + "from-rose-100", + "from-rose-200", + "from-rose-300", + "from-rose-400", + "from-rose-500", + "from-rose-600", + "from-rose-700", + "from-rose-800", + "from-rose-900", + "from-rose-950", + "from-0%", + "from-5%", + "from-10%", + "from-15%", + "from-20%", + "from-25%", + "from-30%", + "from-35%", + "from-40%", + "from-45%", + "from-50%", + "from-55%", + "from-60%", + "from-65%", + "from-70%", + "from-75%", + "from-80%", + "from-85%", + "from-90%", + "from-95%", + "from-100%", + "via-inherit", + "via-current", + "via-transparent", + "via-black", + "via-white", + "via-slate-50", + "via-slate-100", + "via-slate-200", + "via-slate-300", + "via-slate-400", + "via-slate-500", + "via-slate-600", + "via-slate-700", + "via-slate-800", + "via-slate-900", + "via-slate-950", + "via-gray-50", + "via-gray-100", + "via-gray-200", + "via-gray-300", + "via-gray-400", + "via-gray-500", + "via-gray-600", + "via-gray-700", + "via-gray-800", + "via-gray-900", + "via-gray-950", + "via-zinc-50", + "via-zinc-100", + "via-zinc-200", + "via-zinc-300", + "via-zinc-400", + "via-zinc-500", + "via-zinc-600", + "via-zinc-700", + "via-zinc-800", + "via-zinc-900", + "via-zinc-950", + "via-neutral-50", + "via-neutral-100", + "via-neutral-200", + "via-neutral-300", + "via-neutral-400", + "via-neutral-500", + "via-neutral-600", + "via-neutral-700", + "via-neutral-800", + "via-neutral-900", + "via-neutral-950", + "via-stone-50", + "via-stone-100", + "via-stone-200", + "via-stone-300", + "via-stone-400", + "via-stone-500", + "via-stone-600", + "via-stone-700", + "via-stone-800", + "via-stone-900", + "via-stone-950", + "via-red-50", + "via-red-100", + "via-red-200", + "via-red-300", + "via-red-400", + "via-red-500", + "via-red-600", + "via-red-700", + "via-red-800", + "via-red-900", + "via-red-950", + "via-orange-50", + "via-orange-100", + "via-orange-200", + "via-orange-300", + "via-orange-400", + "via-orange-500", + "via-orange-600", + "via-orange-700", + "via-orange-800", + "via-orange-900", + "via-orange-950", + "via-amber-50", + "via-amber-100", + "via-amber-200", + "via-amber-300", + "via-amber-400", + "via-amber-500", + "via-amber-600", + "via-amber-700", + "via-amber-800", + "via-amber-900", + "via-amber-950", + "via-yellow-50", + "via-yellow-100", + "via-yellow-200", + "via-yellow-300", + "via-yellow-400", + "via-yellow-500", + "via-yellow-600", + "via-yellow-700", + "via-yellow-800", + "via-yellow-900", + "via-yellow-950", + "via-lime-50", + "via-lime-100", + "via-lime-200", + "via-lime-300", + "via-lime-400", + "via-lime-500", + "via-lime-600", + "via-lime-700", + "via-lime-800", + "via-lime-900", + "via-lime-950", + "via-green-50", + "via-green-100", + "via-green-200", + "via-green-300", + "via-green-400", + "via-green-500", + "via-green-600", + "via-green-700", + "via-green-800", + "via-green-900", + "via-green-950", + "via-emerald-50", + "via-emerald-100", + "via-emerald-200", + "via-emerald-300", + "via-emerald-400", + "via-emerald-500", + "via-emerald-600", + "via-emerald-700", + "via-emerald-800", + "via-emerald-900", + "via-emerald-950", + "via-teal-50", + "via-teal-100", + "via-teal-200", + "via-teal-300", + "via-teal-400", + "via-teal-500", + "via-teal-600", + "via-teal-700", + "via-teal-800", + "via-teal-900", + "via-teal-950", + "via-cyan-50", + "via-cyan-100", + "via-cyan-200", + "via-cyan-300", + "via-cyan-400", + "via-cyan-500", + "via-cyan-600", + "via-cyan-700", + "via-cyan-800", + "via-cyan-900", + "via-cyan-950", + "via-sky-50", + "via-sky-100", + "via-sky-200", + "via-sky-300", + "via-sky-400", + "via-sky-500", + "via-sky-600", + "via-sky-700", + "via-sky-800", + "via-sky-900", + "via-sky-950", + "via-blue-50", + "via-blue-100", + "via-blue-200", + "via-blue-300", + "via-blue-400", + "via-blue-500", + "via-blue-600", + "via-blue-700", + "via-blue-800", + "via-blue-900", + "via-blue-950", + "via-indigo-50", + "via-indigo-100", + "via-indigo-200", + "via-indigo-300", + "via-indigo-400", + "via-indigo-500", + "via-indigo-600", + "via-indigo-700", + "via-indigo-800", + "via-indigo-900", + "via-indigo-950", + "via-violet-50", + "via-violet-100", + "via-violet-200", + "via-violet-300", + "via-violet-400", + "via-violet-500", + "via-violet-600", + "via-violet-700", + "via-violet-800", + "via-violet-900", + "via-violet-950", + "via-purple-50", + "via-purple-100", + "via-purple-200", + "via-purple-300", + "via-purple-400", + "via-purple-500", + "via-purple-600", + "via-purple-700", + "via-purple-800", + "via-purple-900", + "via-purple-950", + "via-fuchsia-50", + "via-fuchsia-100", + "via-fuchsia-200", + "via-fuchsia-300", + "via-fuchsia-400", + "via-fuchsia-500", + "via-fuchsia-600", + "via-fuchsia-700", + "via-fuchsia-800", + "via-fuchsia-900", + "via-fuchsia-950", + "via-pink-50", + "via-pink-100", + "via-pink-200", + "via-pink-300", + "via-pink-400", + "via-pink-500", + "via-pink-600", + "via-pink-700", + "via-pink-800", + "via-pink-900", + "via-pink-950", + "via-rose-50", + "via-rose-100", + "via-rose-200", + "via-rose-300", + "via-rose-400", + "via-rose-500", + "via-rose-600", + "via-rose-700", + "via-rose-800", + "via-rose-900", + "via-rose-950", + "via-0%", + "via-5%", + "via-10%", + "via-15%", + "via-20%", + "via-25%", + "via-30%", + "via-35%", + "via-40%", + "via-45%", + "via-50%", + "via-55%", + "via-60%", + "via-65%", + "via-70%", + "via-75%", + "via-80%", + "via-85%", + "via-90%", + "via-95%", + "via-100%", + "to-inherit", + "to-current", + "to-transparent", + "to-black", + "to-white", + "to-slate-50", + "to-slate-100", + "to-slate-200", + "to-slate-300", + "to-slate-400", + "to-slate-500", + "to-slate-600", + "to-slate-700", + "to-slate-800", + "to-slate-900", + "to-slate-950", + "to-gray-50", + "to-gray-100", + "to-gray-200", + "to-gray-300", + "to-gray-400", + "to-gray-500", + "to-gray-600", + "to-gray-700", + "to-gray-800", + "to-gray-900", + "to-gray-950", + "to-zinc-50", + "to-zinc-100", + "to-zinc-200", + "to-zinc-300", + "to-zinc-400", + "to-zinc-500", + "to-zinc-600", + "to-zinc-700", + "to-zinc-800", + "to-zinc-900", + "to-zinc-950", + "to-neutral-50", + "to-neutral-100", + "to-neutral-200", + "to-neutral-300", + "to-neutral-400", + "to-neutral-500", + "to-neutral-600", + "to-neutral-700", + "to-neutral-800", + "to-neutral-900", + "to-neutral-950", + "to-stone-50", + "to-stone-100", + "to-stone-200", + "to-stone-300", + "to-stone-400", + "to-stone-500", + "to-stone-600", + "to-stone-700", + "to-stone-800", + "to-stone-900", + "to-stone-950", + "to-red-50", + "to-red-100", + "to-red-200", + "to-red-300", + "to-red-400", + "to-red-500", + "to-red-600", + "to-red-700", + "to-red-800", + "to-red-900", + "to-red-950", + "to-orange-50", + "to-orange-100", + "to-orange-200", + "to-orange-300", + "to-orange-400", + "to-orange-500", + "to-orange-600", + "to-orange-700", + "to-orange-800", + "to-orange-900", + "to-orange-950", + "to-amber-50", + "to-amber-100", + "to-amber-200", + "to-amber-300", + "to-amber-400", + "to-amber-500", + "to-amber-600", + "to-amber-700", + "to-amber-800", + "to-amber-900", + "to-amber-950", + "to-yellow-50", + "to-yellow-100", + "to-yellow-200", + "to-yellow-300", + "to-yellow-400", + "to-yellow-500", + "to-yellow-600", + "to-yellow-700", + "to-yellow-800", + "to-yellow-900", + "to-yellow-950", + "to-lime-50", + "to-lime-100", + "to-lime-200", + "to-lime-300", + "to-lime-400", + "to-lime-500", + "to-lime-600", + "to-lime-700", + "to-lime-800", + "to-lime-900", + "to-lime-950", + "to-green-50", + "to-green-100", + "to-green-200", + "to-green-300", + "to-green-400", + "to-green-500", + "to-green-600", + "to-green-700", + "to-green-800", + "to-green-900", + "to-green-950", + "to-emerald-50", + "to-emerald-100", + "to-emerald-200", + "to-emerald-300", + "to-emerald-400", + "to-emerald-500", + "to-emerald-600", + "to-emerald-700", + "to-emerald-800", + "to-emerald-900", + "to-emerald-950", + "to-teal-50", + "to-teal-100", + "to-teal-200", + "to-teal-300", + "to-teal-400", + "to-teal-500", + "to-teal-600", + "to-teal-700", + "to-teal-800", + "to-teal-900", + "to-teal-950", + "to-cyan-50", + "to-cyan-100", + "to-cyan-200", + "to-cyan-300", + "to-cyan-400", + "to-cyan-500", + "to-cyan-600", + "to-cyan-700", + "to-cyan-800", + "to-cyan-900", + "to-cyan-950", + "to-sky-50", + "to-sky-100", + "to-sky-200", + "to-sky-300", + "to-sky-400", + "to-sky-500", + "to-sky-600", + "to-sky-700", + "to-sky-800", + "to-sky-900", + "to-sky-950", + "to-blue-50", + "to-blue-100", + "to-blue-200", + "to-blue-300", + "to-blue-400", + "to-blue-500", + "to-blue-600", + "to-blue-700", + "to-blue-800", + "to-blue-900", + "to-blue-950", + "to-indigo-50", + "to-indigo-100", + "to-indigo-200", + "to-indigo-300", + "to-indigo-400", + "to-indigo-500", + "to-indigo-600", + "to-indigo-700", + "to-indigo-800", + "to-indigo-900", + "to-indigo-950", + "to-violet-50", + "to-violet-100", + "to-violet-200", + "to-violet-300", + "to-violet-400", + "to-violet-500", + "to-violet-600", + "to-violet-700", + "to-violet-800", + "to-violet-900", + "to-violet-950", + "to-purple-50", + "to-purple-100", + "to-purple-200", + "to-purple-300", + "to-purple-400", + "to-purple-500", + "to-purple-600", + "to-purple-700", + "to-purple-800", + "to-purple-900", + "to-purple-950", + "to-fuchsia-50", + "to-fuchsia-100", + "to-fuchsia-200", + "to-fuchsia-300", + "to-fuchsia-400", + "to-fuchsia-500", + "to-fuchsia-600", + "to-fuchsia-700", + "to-fuchsia-800", + "to-fuchsia-900", + "to-fuchsia-950", + "to-pink-50", + "to-pink-100", + "to-pink-200", + "to-pink-300", + "to-pink-400", + "to-pink-500", + "to-pink-600", + "to-pink-700", + "to-pink-800", + "to-pink-900", + "to-pink-950", + "to-rose-50", + "to-rose-100", + "to-rose-200", + "to-rose-300", + "to-rose-400", + "to-rose-500", + "to-rose-600", + "to-rose-700", + "to-rose-800", + "to-rose-900", + "to-rose-950", + "to-0%", + "to-5%", + "to-10%", + "to-15%", + "to-20%", + "to-25%", + "to-30%", + "to-35%", + "to-40%", + "to-45%", + "to-50%", + "to-55%", + "to-60%", + "to-65%", + "to-70%", + "to-75%", + "to-80%", + "to-85%", + "to-90%", + "to-95%", + "to-100%", + "rounded-none", + "rounded-sm", + "rounded", + "rounded-md", + "rounded-lg", + "rounded-xl", + "rounded-2xl", + "rounded-3xl", + "rounded-full", + "rounded-s-none", + "rounded-s-sm", + "rounded-s", + "rounded-s-md", + "rounded-s-lg", + "rounded-s-xl", + "rounded-s-2xl", + "rounded-s-3xl", + "rounded-s-full", + "rounded-e-none", + "rounded-e-sm", + "rounded-e", + "rounded-e-md", + "rounded-e-lg", + "rounded-e-xl", + "rounded-e-2xl", + "rounded-e-3xl", + "rounded-e-full", + "rounded-t-none", + "rounded-t-sm", + "rounded-t", + "rounded-t-md", + "rounded-t-lg", + "rounded-t-xl", + "rounded-t-2xl", + "rounded-t-3xl", + "rounded-t-full", + "rounded-r-none", + "rounded-r-sm", + "rounded-r", + "rounded-r-md", + "rounded-r-lg", + "rounded-r-xl", + "rounded-r-2xl", + "rounded-r-3xl", + "rounded-r-full", + "rounded-b-none", + "rounded-b-sm", + "rounded-b", + "rounded-b-md", + "rounded-b-lg", + "rounded-b-xl", + "rounded-b-2xl", + "rounded-b-3xl", + "rounded-b-full", + "rounded-l-none", + "rounded-l-sm", + "rounded-l", + "rounded-l-md", + "rounded-l-lg", + "rounded-l-xl", + "rounded-l-2xl", + "rounded-l-3xl", + "rounded-l-full", + "rounded-ss-none", + "rounded-ss-sm", + "rounded-ss", + "rounded-ss-md", + "rounded-ss-lg", + "rounded-ss-xl", + "rounded-ss-2xl", + "rounded-ss-3xl", + "rounded-ss-full", + "rounded-se-none", + "rounded-se-sm", + "rounded-se", + "rounded-se-md", + "rounded-se-lg", + "rounded-se-xl", + "rounded-se-2xl", + "rounded-se-3xl", + "rounded-se-full", + "rounded-ee-none", + "rounded-ee-sm", + "rounded-ee", + "rounded-ee-md", + "rounded-ee-lg", + "rounded-ee-xl", + "rounded-ee-2xl", + "rounded-ee-3xl", + "rounded-ee-full", + "rounded-es-none", + "rounded-es-sm", + "rounded-es", + "rounded-es-md", + "rounded-es-lg", + "rounded-es-xl", + "rounded-es-2xl", + "rounded-es-3xl", + "rounded-es-full", + "rounded-tl-none", + "rounded-tl-sm", + "rounded-tl", + "rounded-tl-md", + "rounded-tl-lg", + "rounded-tl-xl", + "rounded-tl-2xl", + "rounded-tl-3xl", + "rounded-tl-full", + "rounded-tr-none", + "rounded-tr-sm", + "rounded-tr", + "rounded-tr-md", + "rounded-tr-lg", + "rounded-tr-xl", + "rounded-tr-2xl", + "rounded-tr-3xl", + "rounded-tr-full", + "rounded-br-none", + "rounded-br-sm", + "rounded-br", + "rounded-br-md", + "rounded-br-lg", + "rounded-br-xl", + "rounded-br-2xl", + "rounded-br-3xl", + "rounded-br-full", + "rounded-bl-none", + "rounded-bl-sm", + "rounded-bl", + "rounded-bl-md", + "rounded-bl-lg", + "rounded-bl-xl", + "rounded-bl-2xl", + "rounded-bl-3xl", + "rounded-bl-full", + "rounded-s-*", + "rounded-e-*", + "rounded-ss-*", + "rounded-se-*", + "rounded-es-*", + "rounded-ee-*", + "border-0", + "border-2", + "border-4", + "border-8", + "border", + "border-x-0", + "border-x-2", + "border-x-4", + "border-x-8", + "border-x", + "border-y-0", + "border-y-2", + "border-y-4", + "border-y-8", + "border-y", + "border-s-0", + "border-s-2", + "border-s-4", + "border-s-8", + "border-s", + "border-e-0", + "border-e-2", + "border-e-4", + "border-e-8", + "border-e", + "border-t-0", + "border-t-2", + "border-t-4", + "border-t-8", + "border-t", + "border-r-0", + "border-r-2", + "border-r-4", + "border-r-8", + "border-r", + "border-b-0", + "border-b-2", + "border-b-4", + "border-b-8", + "border-b", + "border-l-0", + "border-l-2", + "border-l-4", + "border-l-8", + "border-l", + "border-inherit", + "border-current", + "border-transparent", + "border-black", + "border-white", + "border-slate-50", + "border-slate-100", + "border-slate-200", + "border-slate-300", + "border-slate-400", + "border-slate-500", + "border-slate-600", + "border-slate-700", + "border-slate-800", + "border-slate-900", + "border-slate-950", + "border-gray-50", + "border-gray-100", + "border-gray-200", + "border-gray-300", + "border-gray-400", + "border-gray-500", + "border-gray-600", + "border-gray-700", + "border-gray-800", + "border-gray-900", + "border-gray-950", + "border-zinc-50", + "border-zinc-100", + "border-zinc-200", + "border-zinc-300", + "border-zinc-400", + "border-zinc-500", + "border-zinc-600", + "border-zinc-700", + "border-zinc-800", + "border-zinc-900", + "border-zinc-950", + "border-neutral-50", + "border-neutral-100", + "border-neutral-200", + "border-neutral-300", + "border-neutral-400", + "border-neutral-500", + "border-neutral-600", + "border-neutral-700", + "border-neutral-800", + "border-neutral-900", + "border-neutral-950", + "border-stone-50", + "border-stone-100", + "border-stone-200", + "border-stone-300", + "border-stone-400", + "border-stone-500", + "border-stone-600", + "border-stone-700", + "border-stone-800", + "border-stone-900", + "border-stone-950", + "border-red-50", + "border-red-100", + "border-red-200", + "border-red-300", + "border-red-400", + "border-red-500", + "border-red-600", + "border-red-700", + "border-red-800", + "border-red-900", + "border-red-950", + "border-orange-50", + "border-orange-100", + "border-orange-200", + "border-orange-300", + "border-orange-400", + "border-orange-500", + "border-orange-600", + "border-orange-700", + "border-orange-800", + "border-orange-900", + "border-orange-950", + "border-amber-50", + "border-amber-100", + "border-amber-200", + "border-amber-300", + "border-amber-400", + "border-amber-500", + "border-amber-600", + "border-amber-700", + "border-amber-800", + "border-amber-900", + "border-amber-950", + "border-yellow-50", + "border-yellow-100", + "border-yellow-200", + "border-yellow-300", + "border-yellow-400", + "border-yellow-500", + "border-yellow-600", + "border-yellow-700", + "border-yellow-800", + "border-yellow-900", + "border-yellow-950", + "border-lime-50", + "border-lime-100", + "border-lime-200", + "border-lime-300", + "border-lime-400", + "border-lime-500", + "border-lime-600", + "border-lime-700", + "border-lime-800", + "border-lime-900", + "border-lime-950", + "border-green-50", + "border-green-100", + "border-green-200", + "border-green-300", + "border-green-400", + "border-green-500", + "border-green-600", + "border-green-700", + "border-green-800", + "border-green-900", + "border-green-950", + "border-emerald-50", + "border-emerald-100", + "border-emerald-200", + "border-emerald-300", + "border-emerald-400", + "border-emerald-500", + "border-emerald-600", + "border-emerald-700", + "border-emerald-800", + "border-emerald-900", + "border-emerald-950", + "border-teal-50", + "border-teal-100", + "border-teal-200", + "border-teal-300", + "border-teal-400", + "border-teal-500", + "border-teal-600", + "border-teal-700", + "border-teal-800", + "border-teal-900", + "border-teal-950", + "border-cyan-50", + "border-cyan-100", + "border-cyan-200", + "border-cyan-300", + "border-cyan-400", + "border-cyan-500", + "border-cyan-600", + "border-cyan-700", + "border-cyan-800", + "border-cyan-900", + "border-cyan-950", + "border-sky-50", + "border-sky-100", + "border-sky-200", + "border-sky-300", + "border-sky-400", + "border-sky-500", + "border-sky-600", + "border-sky-700", + "border-sky-800", + "border-sky-900", + "border-sky-950", + "border-blue-50", + "border-blue-100", + "border-blue-200", + "border-blue-300", + "border-blue-400", + "border-blue-500", + "border-blue-600", + "border-blue-700", + "border-blue-800", + "border-blue-900", + "border-blue-950", + "border-indigo-50", + "border-indigo-100", + "border-indigo-200", + "border-indigo-300", + "border-indigo-400", + "border-indigo-500", + "border-indigo-600", + "border-indigo-700", + "border-indigo-800", + "border-indigo-900", + "border-indigo-950", + "border-violet-50", + "border-violet-100", + "border-violet-200", + "border-violet-300", + "border-violet-400", + "border-violet-500", + "border-violet-600", + "border-violet-700", + "border-violet-800", + "border-violet-900", + "border-violet-950", + "border-purple-50", + "border-purple-100", + "border-purple-200", + "border-purple-300", + "border-purple-400", + "border-purple-500", + "border-purple-600", + "border-purple-700", + "border-purple-800", + "border-purple-900", + "border-purple-950", + "border-fuchsia-50", + "border-fuchsia-100", + "border-fuchsia-200", + "border-fuchsia-300", + "border-fuchsia-400", + "border-fuchsia-500", + "border-fuchsia-600", + "border-fuchsia-700", + "border-fuchsia-800", + "border-fuchsia-900", + "border-fuchsia-950", + "border-pink-50", + "border-pink-100", + "border-pink-200", + "border-pink-300", + "border-pink-400", + "border-pink-500", + "border-pink-600", + "border-pink-700", + "border-pink-800", + "border-pink-900", + "border-pink-950", + "border-rose-50", + "border-rose-100", + "border-rose-200", + "border-rose-300", + "border-rose-400", + "border-rose-500", + "border-rose-600", + "border-rose-700", + "border-rose-800", + "border-rose-900", + "border-rose-950", + "border-x-inherit", + "border-x-current", + "border-x-transparent", + "border-x-black", + "border-x-white", + "border-x-slate-50", + "border-x-slate-100", + "border-x-slate-200", + "border-x-slate-300", + "border-x-slate-400", + "border-x-slate-500", + "border-x-slate-600", + "border-x-slate-700", + "border-x-slate-800", + "border-x-slate-900", + "border-x-slate-950", + "border-x-gray-50", + "border-x-gray-100", + "border-x-gray-200", + "border-x-gray-300", + "border-x-gray-400", + "border-x-gray-500", + "border-x-gray-600", + "border-x-gray-700", + "border-x-gray-800", + "border-x-gray-900", + "border-x-gray-950", + "border-x-zinc-50", + "border-x-zinc-100", + "border-x-zinc-200", + "border-x-zinc-300", + "border-x-zinc-400", + "border-x-zinc-500", + "border-x-zinc-600", + "border-x-zinc-700", + "border-x-zinc-800", + "border-x-zinc-900", + "border-x-zinc-950", + "border-x-neutral-50", + "border-x-neutral-100", + "border-x-neutral-200", + "border-x-neutral-300", + "border-x-neutral-400", + "border-x-neutral-500", + "border-x-neutral-600", + "border-x-neutral-700", + "border-x-neutral-800", + "border-x-neutral-900", + "border-x-neutral-950", + "border-x-stone-50", + "border-x-stone-100", + "border-x-stone-200", + "border-x-stone-300", + "border-x-stone-400", + "border-x-stone-500", + "border-x-stone-600", + "border-x-stone-700", + "border-x-stone-800", + "border-x-stone-900", + "border-x-stone-950", + "border-x-red-50", + "border-x-red-100", + "border-x-red-200", + "border-x-red-300", + "border-x-red-400", + "border-x-red-500", + "border-x-red-600", + "border-x-red-700", + "border-x-red-800", + "border-x-red-900", + "border-x-red-950", + "border-x-orange-50", + "border-x-orange-100", + "border-x-orange-200", + "border-x-orange-300", + "border-x-orange-400", + "border-x-orange-500", + "border-x-orange-600", + "border-x-orange-700", + "border-x-orange-800", + "border-x-orange-900", + "border-x-orange-950", + "border-x-amber-50", + "border-x-amber-100", + "border-x-amber-200", + "border-x-amber-300", + "border-x-amber-400", + "border-x-amber-500", + "border-x-amber-600", + "border-x-amber-700", + "border-x-amber-800", + "border-x-amber-900", + "border-x-amber-950", + "border-x-yellow-50", + "border-x-yellow-100", + "border-x-yellow-200", + "border-x-yellow-300", + "border-x-yellow-400", + "border-x-yellow-500", + "border-x-yellow-600", + "border-x-yellow-700", + "border-x-yellow-800", + "border-x-yellow-900", + "border-x-yellow-950", + "border-x-lime-50", + "border-x-lime-100", + "border-x-lime-200", + "border-x-lime-300", + "border-x-lime-400", + "border-x-lime-500", + "border-x-lime-600", + "border-x-lime-700", + "border-x-lime-800", + "border-x-lime-900", + "border-x-lime-950", + "border-x-green-50", + "border-x-green-100", + "border-x-green-200", + "border-x-green-300", + "border-x-green-400", + "border-x-green-500", + "border-x-green-600", + "border-x-green-700", + "border-x-green-800", + "border-x-green-900", + "border-x-green-950", + "border-x-emerald-50", + "border-x-emerald-100", + "border-x-emerald-200", + "border-x-emerald-300", + "border-x-emerald-400", + "border-x-emerald-500", + "border-x-emerald-600", + "border-x-emerald-700", + "border-x-emerald-800", + "border-x-emerald-900", + "border-x-emerald-950", + "border-x-teal-50", + "border-x-teal-100", + "border-x-teal-200", + "border-x-teal-300", + "border-x-teal-400", + "border-x-teal-500", + "border-x-teal-600", + "border-x-teal-700", + "border-x-teal-800", + "border-x-teal-900", + "border-x-teal-950", + "border-x-cyan-50", + "border-x-cyan-100", + "border-x-cyan-200", + "border-x-cyan-300", + "border-x-cyan-400", + "border-x-cyan-500", + "border-x-cyan-600", + "border-x-cyan-700", + "border-x-cyan-800", + "border-x-cyan-900", + "border-x-cyan-950", + "border-x-sky-50", + "border-x-sky-100", + "border-x-sky-200", + "border-x-sky-300", + "border-x-sky-400", + "border-x-sky-500", + "border-x-sky-600", + "border-x-sky-700", + "border-x-sky-800", + "border-x-sky-900", + "border-x-sky-950", + "border-x-blue-50", + "border-x-blue-100", + "border-x-blue-200", + "border-x-blue-300", + "border-x-blue-400", + "border-x-blue-500", + "border-x-blue-600", + "border-x-blue-700", + "border-x-blue-800", + "border-x-blue-900", + "border-x-blue-950", + "border-x-indigo-50", + "border-x-indigo-100", + "border-x-indigo-200", + "border-x-indigo-300", + "border-x-indigo-400", + "border-x-indigo-500", + "border-x-indigo-600", + "border-x-indigo-700", + "border-x-indigo-800", + "border-x-indigo-900", + "border-x-indigo-950", + "border-x-violet-50", + "border-x-violet-100", + "border-x-violet-200", + "border-x-violet-300", + "border-x-violet-400", + "border-x-violet-500", + "border-x-violet-600", + "border-x-violet-700", + "border-x-violet-800", + "border-x-violet-900", + "border-x-violet-950", + "border-x-purple-50", + "border-x-purple-100", + "border-x-purple-200", + "border-x-purple-300", + "border-x-purple-400", + "border-x-purple-500", + "border-x-purple-600", + "border-x-purple-700", + "border-x-purple-800", + "border-x-purple-900", + "border-x-purple-950", + "border-x-fuchsia-50", + "border-x-fuchsia-100", + "border-x-fuchsia-200", + "border-x-fuchsia-300", + "border-x-fuchsia-400", + "border-x-fuchsia-500", + "border-x-fuchsia-600", + "border-x-fuchsia-700", + "border-x-fuchsia-800", + "border-x-fuchsia-900", + "border-x-fuchsia-950", + "border-x-pink-50", + "border-x-pink-100", + "border-x-pink-200", + "border-x-pink-300", + "border-x-pink-400", + "border-x-pink-500", + "border-x-pink-600", + "border-x-pink-700", + "border-x-pink-800", + "border-x-pink-900", + "border-x-pink-950", + "border-x-rose-50", + "border-x-rose-100", + "border-x-rose-200", + "border-x-rose-300", + "border-x-rose-400", + "border-x-rose-500", + "border-x-rose-600", + "border-x-rose-700", + "border-x-rose-800", + "border-x-rose-900", + "border-x-rose-950", + "border-y-inherit", + "border-y-current", + "border-y-transparent", + "border-y-black", + "border-y-white", + "border-y-slate-50", + "border-y-slate-100", + "border-y-slate-200", + "border-y-slate-300", + "border-y-slate-400", + "border-y-slate-500", + "border-y-slate-600", + "border-y-slate-700", + "border-y-slate-800", + "border-y-slate-900", + "border-y-slate-950", + "border-y-gray-50", + "border-y-gray-100", + "border-y-gray-200", + "border-y-gray-300", + "border-y-gray-400", + "border-y-gray-500", + "border-y-gray-600", + "border-y-gray-700", + "border-y-gray-800", + "border-y-gray-900", + "border-y-gray-950", + "border-y-zinc-50", + "border-y-zinc-100", + "border-y-zinc-200", + "border-y-zinc-300", + "border-y-zinc-400", + "border-y-zinc-500", + "border-y-zinc-600", + "border-y-zinc-700", + "border-y-zinc-800", + "border-y-zinc-900", + "border-y-zinc-950", + "border-y-neutral-50", + "border-y-neutral-100", + "border-y-neutral-200", + "border-y-neutral-300", + "border-y-neutral-400", + "border-y-neutral-500", + "border-y-neutral-600", + "border-y-neutral-700", + "border-y-neutral-800", + "border-y-neutral-900", + "border-y-neutral-950", + "border-y-stone-50", + "border-y-stone-100", + "border-y-stone-200", + "border-y-stone-300", + "border-y-stone-400", + "border-y-stone-500", + "border-y-stone-600", + "border-y-stone-700", + "border-y-stone-800", + "border-y-stone-900", + "border-y-stone-950", + "border-y-red-50", + "border-y-red-100", + "border-y-red-200", + "border-y-red-300", + "border-y-red-400", + "border-y-red-500", + "border-y-red-600", + "border-y-red-700", + "border-y-red-800", + "border-y-red-900", + "border-y-red-950", + "border-y-orange-50", + "border-y-orange-100", + "border-y-orange-200", + "border-y-orange-300", + "border-y-orange-400", + "border-y-orange-500", + "border-y-orange-600", + "border-y-orange-700", + "border-y-orange-800", + "border-y-orange-900", + "border-y-orange-950", + "border-y-amber-50", + "border-y-amber-100", + "border-y-amber-200", + "border-y-amber-300", + "border-y-amber-400", + "border-y-amber-500", + "border-y-amber-600", + "border-y-amber-700", + "border-y-amber-800", + "border-y-amber-900", + "border-y-amber-950", + "border-y-yellow-50", + "border-y-yellow-100", + "border-y-yellow-200", + "border-y-yellow-300", + "border-y-yellow-400", + "border-y-yellow-500", + "border-y-yellow-600", + "border-y-yellow-700", + "border-y-yellow-800", + "border-y-yellow-900", + "border-y-yellow-950", + "border-y-lime-50", + "border-y-lime-100", + "border-y-lime-200", + "border-y-lime-300", + "border-y-lime-400", + "border-y-lime-500", + "border-y-lime-600", + "border-y-lime-700", + "border-y-lime-800", + "border-y-lime-900", + "border-y-lime-950", + "border-y-green-50", + "border-y-green-100", + "border-y-green-200", + "border-y-green-300", + "border-y-green-400", + "border-y-green-500", + "border-y-green-600", + "border-y-green-700", + "border-y-green-800", + "border-y-green-900", + "border-y-green-950", + "border-y-emerald-50", + "border-y-emerald-100", + "border-y-emerald-200", + "border-y-emerald-300", + "border-y-emerald-400", + "border-y-emerald-500", + "border-y-emerald-600", + "border-y-emerald-700", + "border-y-emerald-800", + "border-y-emerald-900", + "border-y-emerald-950", + "border-y-teal-50", + "border-y-teal-100", + "border-y-teal-200", + "border-y-teal-300", + "border-y-teal-400", + "border-y-teal-500", + "border-y-teal-600", + "border-y-teal-700", + "border-y-teal-800", + "border-y-teal-900", + "border-y-teal-950", + "border-y-cyan-50", + "border-y-cyan-100", + "border-y-cyan-200", + "border-y-cyan-300", + "border-y-cyan-400", + "border-y-cyan-500", + "border-y-cyan-600", + "border-y-cyan-700", + "border-y-cyan-800", + "border-y-cyan-900", + "border-y-cyan-950", + "border-y-sky-50", + "border-y-sky-100", + "border-y-sky-200", + "border-y-sky-300", + "border-y-sky-400", + "border-y-sky-500", + "border-y-sky-600", + "border-y-sky-700", + "border-y-sky-800", + "border-y-sky-900", + "border-y-sky-950", + "border-y-blue-50", + "border-y-blue-100", + "border-y-blue-200", + "border-y-blue-300", + "border-y-blue-400", + "border-y-blue-500", + "border-y-blue-600", + "border-y-blue-700", + "border-y-blue-800", + "border-y-blue-900", + "border-y-blue-950", + "border-y-indigo-50", + "border-y-indigo-100", + "border-y-indigo-200", + "border-y-indigo-300", + "border-y-indigo-400", + "border-y-indigo-500", + "border-y-indigo-600", + "border-y-indigo-700", + "border-y-indigo-800", + "border-y-indigo-900", + "border-y-indigo-950", + "border-y-violet-50", + "border-y-violet-100", + "border-y-violet-200", + "border-y-violet-300", + "border-y-violet-400", + "border-y-violet-500", + "border-y-violet-600", + "border-y-violet-700", + "border-y-violet-800", + "border-y-violet-900", + "border-y-violet-950", + "border-y-purple-50", + "border-y-purple-100", + "border-y-purple-200", + "border-y-purple-300", + "border-y-purple-400", + "border-y-purple-500", + "border-y-purple-600", + "border-y-purple-700", + "border-y-purple-800", + "border-y-purple-900", + "border-y-purple-950", + "border-y-fuchsia-50", + "border-y-fuchsia-100", + "border-y-fuchsia-200", + "border-y-fuchsia-300", + "border-y-fuchsia-400", + "border-y-fuchsia-500", + "border-y-fuchsia-600", + "border-y-fuchsia-700", + "border-y-fuchsia-800", + "border-y-fuchsia-900", + "border-y-fuchsia-950", + "border-y-pink-50", + "border-y-pink-100", + "border-y-pink-200", + "border-y-pink-300", + "border-y-pink-400", + "border-y-pink-500", + "border-y-pink-600", + "border-y-pink-700", + "border-y-pink-800", + "border-y-pink-900", + "border-y-pink-950", + "border-y-rose-50", + "border-y-rose-100", + "border-y-rose-200", + "border-y-rose-300", + "border-y-rose-400", + "border-y-rose-500", + "border-y-rose-600", + "border-y-rose-700", + "border-y-rose-800", + "border-y-rose-900", + "border-y-rose-950", + "border-s-inherit", + "border-s-current", + "border-s-transparent", + "border-s-black", + "border-s-white", + "border-s-slate-50", + "border-s-slate-100", + "border-s-slate-200", + "border-s-slate-300", + "border-s-slate-400", + "border-s-slate-500", + "border-s-slate-600", + "border-s-slate-700", + "border-s-slate-800", + "border-s-slate-900", + "border-s-slate-950", + "border-s-gray-50", + "border-s-gray-100", + "border-s-gray-200", + "border-s-gray-300", + "border-s-gray-400", + "border-s-gray-500", + "border-s-gray-600", + "border-s-gray-700", + "border-s-gray-800", + "border-s-gray-900", + "border-s-gray-950", + "border-s-zinc-50", + "border-s-zinc-100", + "border-s-zinc-200", + "border-s-zinc-300", + "border-s-zinc-400", + "border-s-zinc-500", + "border-s-zinc-600", + "border-s-zinc-700", + "border-s-zinc-800", + "border-s-zinc-900", + "border-s-zinc-950", + "border-s-neutral-50", + "border-s-neutral-100", + "border-s-neutral-200", + "border-s-neutral-300", + "border-s-neutral-400", + "border-s-neutral-500", + "border-s-neutral-600", + "border-s-neutral-700", + "border-s-neutral-800", + "border-s-neutral-900", + "border-s-neutral-950", + "border-s-stone-50", + "border-s-stone-100", + "border-s-stone-200", + "border-s-stone-300", + "border-s-stone-400", + "border-s-stone-500", + "border-s-stone-600", + "border-s-stone-700", + "border-s-stone-800", + "border-s-stone-900", + "border-s-stone-950", + "border-s-red-50", + "border-s-red-100", + "border-s-red-200", + "border-s-red-300", + "border-s-red-400", + "border-s-red-500", + "border-s-red-600", + "border-s-red-700", + "border-s-red-800", + "border-s-red-900", + "border-s-red-950", + "border-s-orange-50", + "border-s-orange-100", + "border-s-orange-200", + "border-s-orange-300", + "border-s-orange-400", + "border-s-orange-500", + "border-s-orange-600", + "border-s-orange-700", + "border-s-orange-800", + "border-s-orange-900", + "border-s-orange-950", + "border-s-amber-50", + "border-s-amber-100", + "border-s-amber-200", + "border-s-amber-300", + "border-s-amber-400", + "border-s-amber-500", + "border-s-amber-600", + "border-s-amber-700", + "border-s-amber-800", + "border-s-amber-900", + "border-s-amber-950", + "border-s-yellow-50", + "border-s-yellow-100", + "border-s-yellow-200", + "border-s-yellow-300", + "border-s-yellow-400", + "border-s-yellow-500", + "border-s-yellow-600", + "border-s-yellow-700", + "border-s-yellow-800", + "border-s-yellow-900", + "border-s-yellow-950", + "border-s-lime-50", + "border-s-lime-100", + "border-s-lime-200", + "border-s-lime-300", + "border-s-lime-400", + "border-s-lime-500", + "border-s-lime-600", + "border-s-lime-700", + "border-s-lime-800", + "border-s-lime-900", + "border-s-lime-950", + "border-s-green-50", + "border-s-green-100", + "border-s-green-200", + "border-s-green-300", + "border-s-green-400", + "border-s-green-500", + "border-s-green-600", + "border-s-green-700", + "border-s-green-800", + "border-s-green-900", + "border-s-green-950", + "border-s-emerald-50", + "border-s-emerald-100", + "border-s-emerald-200", + "border-s-emerald-300", + "border-s-emerald-400", + "border-s-emerald-500", + "border-s-emerald-600", + "border-s-emerald-700", + "border-s-emerald-800", + "border-s-emerald-900", + "border-s-emerald-950", + "border-s-teal-50", + "border-s-teal-100", + "border-s-teal-200", + "border-s-teal-300", + "border-s-teal-400", + "border-s-teal-500", + "border-s-teal-600", + "border-s-teal-700", + "border-s-teal-800", + "border-s-teal-900", + "border-s-teal-950", + "border-s-cyan-50", + "border-s-cyan-100", + "border-s-cyan-200", + "border-s-cyan-300", + "border-s-cyan-400", + "border-s-cyan-500", + "border-s-cyan-600", + "border-s-cyan-700", + "border-s-cyan-800", + "border-s-cyan-900", + "border-s-cyan-950", + "border-s-sky-50", + "border-s-sky-100", + "border-s-sky-200", + "border-s-sky-300", + "border-s-sky-400", + "border-s-sky-500", + "border-s-sky-600", + "border-s-sky-700", + "border-s-sky-800", + "border-s-sky-900", + "border-s-sky-950", + "border-s-blue-50", + "border-s-blue-100", + "border-s-blue-200", + "border-s-blue-300", + "border-s-blue-400", + "border-s-blue-500", + "border-s-blue-600", + "border-s-blue-700", + "border-s-blue-800", + "border-s-blue-900", + "border-s-blue-950", + "border-s-indigo-50", + "border-s-indigo-100", + "border-s-indigo-200", + "border-s-indigo-300", + "border-s-indigo-400", + "border-s-indigo-500", + "border-s-indigo-600", + "border-s-indigo-700", + "border-s-indigo-800", + "border-s-indigo-900", + "border-s-indigo-950", + "border-s-violet-50", + "border-s-violet-100", + "border-s-violet-200", + "border-s-violet-300", + "border-s-violet-400", + "border-s-violet-500", + "border-s-violet-600", + "border-s-violet-700", + "border-s-violet-800", + "border-s-violet-900", + "border-s-violet-950", + "border-s-purple-50", + "border-s-purple-100", + "border-s-purple-200", + "border-s-purple-300", + "border-s-purple-400", + "border-s-purple-500", + "border-s-purple-600", + "border-s-purple-700", + "border-s-purple-800", + "border-s-purple-900", + "border-s-purple-950", + "border-s-fuchsia-50", + "border-s-fuchsia-100", + "border-s-fuchsia-200", + "border-s-fuchsia-300", + "border-s-fuchsia-400", + "border-s-fuchsia-500", + "border-s-fuchsia-600", + "border-s-fuchsia-700", + "border-s-fuchsia-800", + "border-s-fuchsia-900", + "border-s-fuchsia-950", + "border-s-pink-50", + "border-s-pink-100", + "border-s-pink-200", + "border-s-pink-300", + "border-s-pink-400", + "border-s-pink-500", + "border-s-pink-600", + "border-s-pink-700", + "border-s-pink-800", + "border-s-pink-900", + "border-s-pink-950", + "border-s-rose-50", + "border-s-rose-100", + "border-s-rose-200", + "border-s-rose-300", + "border-s-rose-400", + "border-s-rose-500", + "border-s-rose-600", + "border-s-rose-700", + "border-s-rose-800", + "border-s-rose-900", + "border-s-rose-950", + "border-e-inherit", + "border-e-current", + "border-e-transparent", + "border-e-black", + "border-e-white", + "border-e-slate-50", + "border-e-slate-100", + "border-e-slate-200", + "border-e-slate-300", + "border-e-slate-400", + "border-e-slate-500", + "border-e-slate-600", + "border-e-slate-700", + "border-e-slate-800", + "border-e-slate-900", + "border-e-slate-950", + "border-e-gray-50", + "border-e-gray-100", + "border-e-gray-200", + "border-e-gray-300", + "border-e-gray-400", + "border-e-gray-500", + "border-e-gray-600", + "border-e-gray-700", + "border-e-gray-800", + "border-e-gray-900", + "border-e-gray-950", + "border-e-zinc-50", + "border-e-zinc-100", + "border-e-zinc-200", + "border-e-zinc-300", + "border-e-zinc-400", + "border-e-zinc-500", + "border-e-zinc-600", + "border-e-zinc-700", + "border-e-zinc-800", + "border-e-zinc-900", + "border-e-zinc-950", + "border-e-neutral-50", + "border-e-neutral-100", + "border-e-neutral-200", + "border-e-neutral-300", + "border-e-neutral-400", + "border-e-neutral-500", + "border-e-neutral-600", + "border-e-neutral-700", + "border-e-neutral-800", + "border-e-neutral-900", + "border-e-neutral-950", + "border-e-stone-50", + "border-e-stone-100", + "border-e-stone-200", + "border-e-stone-300", + "border-e-stone-400", + "border-e-stone-500", + "border-e-stone-600", + "border-e-stone-700", + "border-e-stone-800", + "border-e-stone-900", + "border-e-stone-950", + "border-e-red-50", + "border-e-red-100", + "border-e-red-200", + "border-e-red-300", + "border-e-red-400", + "border-e-red-500", + "border-e-red-600", + "border-e-red-700", + "border-e-red-800", + "border-e-red-900", + "border-e-red-950", + "border-e-orange-50", + "border-e-orange-100", + "border-e-orange-200", + "border-e-orange-300", + "border-e-orange-400", + "border-e-orange-500", + "border-e-orange-600", + "border-e-orange-700", + "border-e-orange-800", + "border-e-orange-900", + "border-e-orange-950", + "border-e-amber-50", + "border-e-amber-100", + "border-e-amber-200", + "border-e-amber-300", + "border-e-amber-400", + "border-e-amber-500", + "border-e-amber-600", + "border-e-amber-700", + "border-e-amber-800", + "border-e-amber-900", + "border-e-amber-950", + "border-e-yellow-50", + "border-e-yellow-100", + "border-e-yellow-200", + "border-e-yellow-300", + "border-e-yellow-400", + "border-e-yellow-500", + "border-e-yellow-600", + "border-e-yellow-700", + "border-e-yellow-800", + "border-e-yellow-900", + "border-e-yellow-950", + "border-e-lime-50", + "border-e-lime-100", + "border-e-lime-200", + "border-e-lime-300", + "border-e-lime-400", + "border-e-lime-500", + "border-e-lime-600", + "border-e-lime-700", + "border-e-lime-800", + "border-e-lime-900", + "border-e-lime-950", + "border-e-green-50", + "border-e-green-100", + "border-e-green-200", + "border-e-green-300", + "border-e-green-400", + "border-e-green-500", + "border-e-green-600", + "border-e-green-700", + "border-e-green-800", + "border-e-green-900", + "border-e-green-950", + "border-e-emerald-50", + "border-e-emerald-100", + "border-e-emerald-200", + "border-e-emerald-300", + "border-e-emerald-400", + "border-e-emerald-500", + "border-e-emerald-600", + "border-e-emerald-700", + "border-e-emerald-800", + "border-e-emerald-900", + "border-e-emerald-950", + "border-e-teal-50", + "border-e-teal-100", + "border-e-teal-200", + "border-e-teal-300", + "border-e-teal-400", + "border-e-teal-500", + "border-e-teal-600", + "border-e-teal-700", + "border-e-teal-800", + "border-e-teal-900", + "border-e-teal-950", + "border-e-cyan-50", + "border-e-cyan-100", + "border-e-cyan-200", + "border-e-cyan-300", + "border-e-cyan-400", + "border-e-cyan-500", + "border-e-cyan-600", + "border-e-cyan-700", + "border-e-cyan-800", + "border-e-cyan-900", + "border-e-cyan-950", + "border-e-sky-50", + "border-e-sky-100", + "border-e-sky-200", + "border-e-sky-300", + "border-e-sky-400", + "border-e-sky-500", + "border-e-sky-600", + "border-e-sky-700", + "border-e-sky-800", + "border-e-sky-900", + "border-e-sky-950", + "border-e-blue-50", + "border-e-blue-100", + "border-e-blue-200", + "border-e-blue-300", + "border-e-blue-400", + "border-e-blue-500", + "border-e-blue-600", + "border-e-blue-700", + "border-e-blue-800", + "border-e-blue-900", + "border-e-blue-950", + "border-e-indigo-50", + "border-e-indigo-100", + "border-e-indigo-200", + "border-e-indigo-300", + "border-e-indigo-400", + "border-e-indigo-500", + "border-e-indigo-600", + "border-e-indigo-700", + "border-e-indigo-800", + "border-e-indigo-900", + "border-e-indigo-950", + "border-e-violet-50", + "border-e-violet-100", + "border-e-violet-200", + "border-e-violet-300", + "border-e-violet-400", + "border-e-violet-500", + "border-e-violet-600", + "border-e-violet-700", + "border-e-violet-800", + "border-e-violet-900", + "border-e-violet-950", + "border-e-purple-50", + "border-e-purple-100", + "border-e-purple-200", + "border-e-purple-300", + "border-e-purple-400", + "border-e-purple-500", + "border-e-purple-600", + "border-e-purple-700", + "border-e-purple-800", + "border-e-purple-900", + "border-e-purple-950", + "border-e-fuchsia-50", + "border-e-fuchsia-100", + "border-e-fuchsia-200", + "border-e-fuchsia-300", + "border-e-fuchsia-400", + "border-e-fuchsia-500", + "border-e-fuchsia-600", + "border-e-fuchsia-700", + "border-e-fuchsia-800", + "border-e-fuchsia-900", + "border-e-fuchsia-950", + "border-e-pink-50", + "border-e-pink-100", + "border-e-pink-200", + "border-e-pink-300", + "border-e-pink-400", + "border-e-pink-500", + "border-e-pink-600", + "border-e-pink-700", + "border-e-pink-800", + "border-e-pink-900", + "border-e-pink-950", + "border-e-rose-50", + "border-e-rose-100", + "border-e-rose-200", + "border-e-rose-300", + "border-e-rose-400", + "border-e-rose-500", + "border-e-rose-600", + "border-e-rose-700", + "border-e-rose-800", + "border-e-rose-900", + "border-e-rose-950", + "border-t-inherit", + "border-t-current", + "border-t-transparent", + "border-t-black", + "border-t-white", + "border-t-slate-50", + "border-t-slate-100", + "border-t-slate-200", + "border-t-slate-300", + "border-t-slate-400", + "border-t-slate-500", + "border-t-slate-600", + "border-t-slate-700", + "border-t-slate-800", + "border-t-slate-900", + "border-t-slate-950", + "border-t-gray-50", + "border-t-gray-100", + "border-t-gray-200", + "border-t-gray-300", + "border-t-gray-400", + "border-t-gray-500", + "border-t-gray-600", + "border-t-gray-700", + "border-t-gray-800", + "border-t-gray-900", + "border-t-gray-950", + "border-t-zinc-50", + "border-t-zinc-100", + "border-t-zinc-200", + "border-t-zinc-300", + "border-t-zinc-400", + "border-t-zinc-500", + "border-t-zinc-600", + "border-t-zinc-700", + "border-t-zinc-800", + "border-t-zinc-900", + "border-t-zinc-950", + "border-t-neutral-50", + "border-t-neutral-100", + "border-t-neutral-200", + "border-t-neutral-300", + "border-t-neutral-400", + "border-t-neutral-500", + "border-t-neutral-600", + "border-t-neutral-700", + "border-t-neutral-800", + "border-t-neutral-900", + "border-t-neutral-950", + "border-t-stone-50", + "border-t-stone-100", + "border-t-stone-200", + "border-t-stone-300", + "border-t-stone-400", + "border-t-stone-500", + "border-t-stone-600", + "border-t-stone-700", + "border-t-stone-800", + "border-t-stone-900", + "border-t-stone-950", + "border-t-red-50", + "border-t-red-100", + "border-t-red-200", + "border-t-red-300", + "border-t-red-400", + "border-t-red-500", + "border-t-red-600", + "border-t-red-700", + "border-t-red-800", + "border-t-red-900", + "border-t-red-950", + "border-t-orange-50", + "border-t-orange-100", + "border-t-orange-200", + "border-t-orange-300", + "border-t-orange-400", + "border-t-orange-500", + "border-t-orange-600", + "border-t-orange-700", + "border-t-orange-800", + "border-t-orange-900", + "border-t-orange-950", + "border-t-amber-50", + "border-t-amber-100", + "border-t-amber-200", + "border-t-amber-300", + "border-t-amber-400", + "border-t-amber-500", + "border-t-amber-600", + "border-t-amber-700", + "border-t-amber-800", + "border-t-amber-900", + "border-t-amber-950", + "border-t-yellow-50", + "border-t-yellow-100", + "border-t-yellow-200", + "border-t-yellow-300", + "border-t-yellow-400", + "border-t-yellow-500", + "border-t-yellow-600", + "border-t-yellow-700", + "border-t-yellow-800", + "border-t-yellow-900", + "border-t-yellow-950", + "border-t-lime-50", + "border-t-lime-100", + "border-t-lime-200", + "border-t-lime-300", + "border-t-lime-400", + "border-t-lime-500", + "border-t-lime-600", + "border-t-lime-700", + "border-t-lime-800", + "border-t-lime-900", + "border-t-lime-950", + "border-t-green-50", + "border-t-green-100", + "border-t-green-200", + "border-t-green-300", + "border-t-green-400", + "border-t-green-500", + "border-t-green-600", + "border-t-green-700", + "border-t-green-800", + "border-t-green-900", + "border-t-green-950", + "border-t-emerald-50", + "border-t-emerald-100", + "border-t-emerald-200", + "border-t-emerald-300", + "border-t-emerald-400", + "border-t-emerald-500", + "border-t-emerald-600", + "border-t-emerald-700", + "border-t-emerald-800", + "border-t-emerald-900", + "border-t-emerald-950", + "border-t-teal-50", + "border-t-teal-100", + "border-t-teal-200", + "border-t-teal-300", + "border-t-teal-400", + "border-t-teal-500", + "border-t-teal-600", + "border-t-teal-700", + "border-t-teal-800", + "border-t-teal-900", + "border-t-teal-950", + "border-t-cyan-50", + "border-t-cyan-100", + "border-t-cyan-200", + "border-t-cyan-300", + "border-t-cyan-400", + "border-t-cyan-500", + "border-t-cyan-600", + "border-t-cyan-700", + "border-t-cyan-800", + "border-t-cyan-900", + "border-t-cyan-950", + "border-t-sky-50", + "border-t-sky-100", + "border-t-sky-200", + "border-t-sky-300", + "border-t-sky-400", + "border-t-sky-500", + "border-t-sky-600", + "border-t-sky-700", + "border-t-sky-800", + "border-t-sky-900", + "border-t-sky-950", + "border-t-blue-50", + "border-t-blue-100", + "border-t-blue-200", + "border-t-blue-300", + "border-t-blue-400", + "border-t-blue-500", + "border-t-blue-600", + "border-t-blue-700", + "border-t-blue-800", + "border-t-blue-900", + "border-t-blue-950", + "border-t-indigo-50", + "border-t-indigo-100", + "border-t-indigo-200", + "border-t-indigo-300", + "border-t-indigo-400", + "border-t-indigo-500", + "border-t-indigo-600", + "border-t-indigo-700", + "border-t-indigo-800", + "border-t-indigo-900", + "border-t-indigo-950", + "border-t-violet-50", + "border-t-violet-100", + "border-t-violet-200", + "border-t-violet-300", + "border-t-violet-400", + "border-t-violet-500", + "border-t-violet-600", + "border-t-violet-700", + "border-t-violet-800", + "border-t-violet-900", + "border-t-violet-950", + "border-t-purple-50", + "border-t-purple-100", + "border-t-purple-200", + "border-t-purple-300", + "border-t-purple-400", + "border-t-purple-500", + "border-t-purple-600", + "border-t-purple-700", + "border-t-purple-800", + "border-t-purple-900", + "border-t-purple-950", + "border-t-fuchsia-50", + "border-t-fuchsia-100", + "border-t-fuchsia-200", + "border-t-fuchsia-300", + "border-t-fuchsia-400", + "border-t-fuchsia-500", + "border-t-fuchsia-600", + "border-t-fuchsia-700", + "border-t-fuchsia-800", + "border-t-fuchsia-900", + "border-t-fuchsia-950", + "border-t-pink-50", + "border-t-pink-100", + "border-t-pink-200", + "border-t-pink-300", + "border-t-pink-400", + "border-t-pink-500", + "border-t-pink-600", + "border-t-pink-700", + "border-t-pink-800", + "border-t-pink-900", + "border-t-pink-950", + "border-t-rose-50", + "border-t-rose-100", + "border-t-rose-200", + "border-t-rose-300", + "border-t-rose-400", + "border-t-rose-500", + "border-t-rose-600", + "border-t-rose-700", + "border-t-rose-800", + "border-t-rose-900", + "border-t-rose-950", + "border-r-inherit", + "border-r-current", + "border-r-transparent", + "border-r-black", + "border-r-white", + "border-r-slate-50", + "border-r-slate-100", + "border-r-slate-200", + "border-r-slate-300", + "border-r-slate-400", + "border-r-slate-500", + "border-r-slate-600", + "border-r-slate-700", + "border-r-slate-800", + "border-r-slate-900", + "border-r-slate-950", + "border-r-gray-50", + "border-r-gray-100", + "border-r-gray-200", + "border-r-gray-300", + "border-r-gray-400", + "border-r-gray-500", + "border-r-gray-600", + "border-r-gray-700", + "border-r-gray-800", + "border-r-gray-900", + "border-r-gray-950", + "border-r-zinc-50", + "border-r-zinc-100", + "border-r-zinc-200", + "border-r-zinc-300", + "border-r-zinc-400", + "border-r-zinc-500", + "border-r-zinc-600", + "border-r-zinc-700", + "border-r-zinc-800", + "border-r-zinc-900", + "border-r-zinc-950", + "border-r-neutral-50", + "border-r-neutral-100", + "border-r-neutral-200", + "border-r-neutral-300", + "border-r-neutral-400", + "border-r-neutral-500", + "border-r-neutral-600", + "border-r-neutral-700", + "border-r-neutral-800", + "border-r-neutral-900", + "border-r-neutral-950", + "border-r-stone-50", + "border-r-stone-100", + "border-r-stone-200", + "border-r-stone-300", + "border-r-stone-400", + "border-r-stone-500", + "border-r-stone-600", + "border-r-stone-700", + "border-r-stone-800", + "border-r-stone-900", + "border-r-stone-950", + "border-r-red-50", + "border-r-red-100", + "border-r-red-200", + "border-r-red-300", + "border-r-red-400", + "border-r-red-500", + "border-r-red-600", + "border-r-red-700", + "border-r-red-800", + "border-r-red-900", + "border-r-red-950", + "border-r-orange-50", + "border-r-orange-100", + "border-r-orange-200", + "border-r-orange-300", + "border-r-orange-400", + "border-r-orange-500", + "border-r-orange-600", + "border-r-orange-700", + "border-r-orange-800", + "border-r-orange-900", + "border-r-orange-950", + "border-r-amber-50", + "border-r-amber-100", + "border-r-amber-200", + "border-r-amber-300", + "border-r-amber-400", + "border-r-amber-500", + "border-r-amber-600", + "border-r-amber-700", + "border-r-amber-800", + "border-r-amber-900", + "border-r-amber-950", + "border-r-yellow-50", + "border-r-yellow-100", + "border-r-yellow-200", + "border-r-yellow-300", + "border-r-yellow-400", + "border-r-yellow-500", + "border-r-yellow-600", + "border-r-yellow-700", + "border-r-yellow-800", + "border-r-yellow-900", + "border-r-yellow-950", + "border-r-lime-50", + "border-r-lime-100", + "border-r-lime-200", + "border-r-lime-300", + "border-r-lime-400", + "border-r-lime-500", + "border-r-lime-600", + "border-r-lime-700", + "border-r-lime-800", + "border-r-lime-900", + "border-r-lime-950", + "border-r-green-50", + "border-r-green-100", + "border-r-green-200", + "border-r-green-300", + "border-r-green-400", + "border-r-green-500", + "border-r-green-600", + "border-r-green-700", + "border-r-green-800", + "border-r-green-900", + "border-r-green-950", + "border-r-emerald-50", + "border-r-emerald-100", + "border-r-emerald-200", + "border-r-emerald-300", + "border-r-emerald-400", + "border-r-emerald-500", + "border-r-emerald-600", + "border-r-emerald-700", + "border-r-emerald-800", + "border-r-emerald-900", + "border-r-emerald-950", + "border-r-teal-50", + "border-r-teal-100", + "border-r-teal-200", + "border-r-teal-300", + "border-r-teal-400", + "border-r-teal-500", + "border-r-teal-600", + "border-r-teal-700", + "border-r-teal-800", + "border-r-teal-900", + "border-r-teal-950", + "border-r-cyan-50", + "border-r-cyan-100", + "border-r-cyan-200", + "border-r-cyan-300", + "border-r-cyan-400", + "border-r-cyan-500", + "border-r-cyan-600", + "border-r-cyan-700", + "border-r-cyan-800", + "border-r-cyan-900", + "border-r-cyan-950", + "border-r-sky-50", + "border-r-sky-100", + "border-r-sky-200", + "border-r-sky-300", + "border-r-sky-400", + "border-r-sky-500", + "border-r-sky-600", + "border-r-sky-700", + "border-r-sky-800", + "border-r-sky-900", + "border-r-sky-950", + "border-r-blue-50", + "border-r-blue-100", + "border-r-blue-200", + "border-r-blue-300", + "border-r-blue-400", + "border-r-blue-500", + "border-r-blue-600", + "border-r-blue-700", + "border-r-blue-800", + "border-r-blue-900", + "border-r-blue-950", + "border-r-indigo-50", + "border-r-indigo-100", + "border-r-indigo-200", + "border-r-indigo-300", + "border-r-indigo-400", + "border-r-indigo-500", + "border-r-indigo-600", + "border-r-indigo-700", + "border-r-indigo-800", + "border-r-indigo-900", + "border-r-indigo-950", + "border-r-violet-50", + "border-r-violet-100", + "border-r-violet-200", + "border-r-violet-300", + "border-r-violet-400", + "border-r-violet-500", + "border-r-violet-600", + "border-r-violet-700", + "border-r-violet-800", + "border-r-violet-900", + "border-r-violet-950", + "border-r-purple-50", + "border-r-purple-100", + "border-r-purple-200", + "border-r-purple-300", + "border-r-purple-400", + "border-r-purple-500", + "border-r-purple-600", + "border-r-purple-700", + "border-r-purple-800", + "border-r-purple-900", + "border-r-purple-950", + "border-r-fuchsia-50", + "border-r-fuchsia-100", + "border-r-fuchsia-200", + "border-r-fuchsia-300", + "border-r-fuchsia-400", + "border-r-fuchsia-500", + "border-r-fuchsia-600", + "border-r-fuchsia-700", + "border-r-fuchsia-800", + "border-r-fuchsia-900", + "border-r-fuchsia-950", + "border-r-pink-50", + "border-r-pink-100", + "border-r-pink-200", + "border-r-pink-300", + "border-r-pink-400", + "border-r-pink-500", + "border-r-pink-600", + "border-r-pink-700", + "border-r-pink-800", + "border-r-pink-900", + "border-r-pink-950", + "border-r-rose-50", + "border-r-rose-100", + "border-r-rose-200", + "border-r-rose-300", + "border-r-rose-400", + "border-r-rose-500", + "border-r-rose-600", + "border-r-rose-700", + "border-r-rose-800", + "border-r-rose-900", + "border-r-rose-950", + "border-b-inherit", + "border-b-current", + "border-b-transparent", + "border-b-black", + "border-b-white", + "border-b-slate-50", + "border-b-slate-100", + "border-b-slate-200", + "border-b-slate-300", + "border-b-slate-400", + "border-b-slate-500", + "border-b-slate-600", + "border-b-slate-700", + "border-b-slate-800", + "border-b-slate-900", + "border-b-slate-950", + "border-b-gray-50", + "border-b-gray-100", + "border-b-gray-200", + "border-b-gray-300", + "border-b-gray-400", + "border-b-gray-500", + "border-b-gray-600", + "border-b-gray-700", + "border-b-gray-800", + "border-b-gray-900", + "border-b-gray-950", + "border-b-zinc-50", + "border-b-zinc-100", + "border-b-zinc-200", + "border-b-zinc-300", + "border-b-zinc-400", + "border-b-zinc-500", + "border-b-zinc-600", + "border-b-zinc-700", + "border-b-zinc-800", + "border-b-zinc-900", + "border-b-zinc-950", + "border-b-neutral-50", + "border-b-neutral-100", + "border-b-neutral-200", + "border-b-neutral-300", + "border-b-neutral-400", + "border-b-neutral-500", + "border-b-neutral-600", + "border-b-neutral-700", + "border-b-neutral-800", + "border-b-neutral-900", + "border-b-neutral-950", + "border-b-stone-50", + "border-b-stone-100", + "border-b-stone-200", + "border-b-stone-300", + "border-b-stone-400", + "border-b-stone-500", + "border-b-stone-600", + "border-b-stone-700", + "border-b-stone-800", + "border-b-stone-900", + "border-b-stone-950", + "border-b-red-50", + "border-b-red-100", + "border-b-red-200", + "border-b-red-300", + "border-b-red-400", + "border-b-red-500", + "border-b-red-600", + "border-b-red-700", + "border-b-red-800", + "border-b-red-900", + "border-b-red-950", + "border-b-orange-50", + "border-b-orange-100", + "border-b-orange-200", + "border-b-orange-300", + "border-b-orange-400", + "border-b-orange-500", + "border-b-orange-600", + "border-b-orange-700", + "border-b-orange-800", + "border-b-orange-900", + "border-b-orange-950", + "border-b-amber-50", + "border-b-amber-100", + "border-b-amber-200", + "border-b-amber-300", + "border-b-amber-400", + "border-b-amber-500", + "border-b-amber-600", + "border-b-amber-700", + "border-b-amber-800", + "border-b-amber-900", + "border-b-amber-950", + "border-b-yellow-50", + "border-b-yellow-100", + "border-b-yellow-200", + "border-b-yellow-300", + "border-b-yellow-400", + "border-b-yellow-500", + "border-b-yellow-600", + "border-b-yellow-700", + "border-b-yellow-800", + "border-b-yellow-900", + "border-b-yellow-950", + "border-b-lime-50", + "border-b-lime-100", + "border-b-lime-200", + "border-b-lime-300", + "border-b-lime-400", + "border-b-lime-500", + "border-b-lime-600", + "border-b-lime-700", + "border-b-lime-800", + "border-b-lime-900", + "border-b-lime-950", + "border-b-green-50", + "border-b-green-100", + "border-b-green-200", + "border-b-green-300", + "border-b-green-400", + "border-b-green-500", + "border-b-green-600", + "border-b-green-700", + "border-b-green-800", + "border-b-green-900", + "border-b-green-950", + "border-b-emerald-50", + "border-b-emerald-100", + "border-b-emerald-200", + "border-b-emerald-300", + "border-b-emerald-400", + "border-b-emerald-500", + "border-b-emerald-600", + "border-b-emerald-700", + "border-b-emerald-800", + "border-b-emerald-900", + "border-b-emerald-950", + "border-b-teal-50", + "border-b-teal-100", + "border-b-teal-200", + "border-b-teal-300", + "border-b-teal-400", + "border-b-teal-500", + "border-b-teal-600", + "border-b-teal-700", + "border-b-teal-800", + "border-b-teal-900", + "border-b-teal-950", + "border-b-cyan-50", + "border-b-cyan-100", + "border-b-cyan-200", + "border-b-cyan-300", + "border-b-cyan-400", + "border-b-cyan-500", + "border-b-cyan-600", + "border-b-cyan-700", + "border-b-cyan-800", + "border-b-cyan-900", + "border-b-cyan-950", + "border-b-sky-50", + "border-b-sky-100", + "border-b-sky-200", + "border-b-sky-300", + "border-b-sky-400", + "border-b-sky-500", + "border-b-sky-600", + "border-b-sky-700", + "border-b-sky-800", + "border-b-sky-900", + "border-b-sky-950", + "border-b-blue-50", + "border-b-blue-100", + "border-b-blue-200", + "border-b-blue-300", + "border-b-blue-400", + "border-b-blue-500", + "border-b-blue-600", + "border-b-blue-700", + "border-b-blue-800", + "border-b-blue-900", + "border-b-blue-950", + "border-b-indigo-50", + "border-b-indigo-100", + "border-b-indigo-200", + "border-b-indigo-300", + "border-b-indigo-400", + "border-b-indigo-500", + "border-b-indigo-600", + "border-b-indigo-700", + "border-b-indigo-800", + "border-b-indigo-900", + "border-b-indigo-950", + "border-b-violet-50", + "border-b-violet-100", + "border-b-violet-200", + "border-b-violet-300", + "border-b-violet-400", + "border-b-violet-500", + "border-b-violet-600", + "border-b-violet-700", + "border-b-violet-800", + "border-b-violet-900", + "border-b-violet-950", + "border-b-purple-50", + "border-b-purple-100", + "border-b-purple-200", + "border-b-purple-300", + "border-b-purple-400", + "border-b-purple-500", + "border-b-purple-600", + "border-b-purple-700", + "border-b-purple-800", + "border-b-purple-900", + "border-b-purple-950", + "border-b-fuchsia-50", + "border-b-fuchsia-100", + "border-b-fuchsia-200", + "border-b-fuchsia-300", + "border-b-fuchsia-400", + "border-b-fuchsia-500", + "border-b-fuchsia-600", + "border-b-fuchsia-700", + "border-b-fuchsia-800", + "border-b-fuchsia-900", + "border-b-fuchsia-950", + "border-b-pink-50", + "border-b-pink-100", + "border-b-pink-200", + "border-b-pink-300", + "border-b-pink-400", + "border-b-pink-500", + "border-b-pink-600", + "border-b-pink-700", + "border-b-pink-800", + "border-b-pink-900", + "border-b-pink-950", + "border-b-rose-50", + "border-b-rose-100", + "border-b-rose-200", + "border-b-rose-300", + "border-b-rose-400", + "border-b-rose-500", + "border-b-rose-600", + "border-b-rose-700", + "border-b-rose-800", + "border-b-rose-900", + "border-b-rose-950", + "border-l-inherit", + "border-l-current", + "border-l-transparent", + "border-l-black", + "border-l-white", + "border-l-slate-50", + "border-l-slate-100", + "border-l-slate-200", + "border-l-slate-300", + "border-l-slate-400", + "border-l-slate-500", + "border-l-slate-600", + "border-l-slate-700", + "border-l-slate-800", + "border-l-slate-900", + "border-l-slate-950", + "border-l-gray-50", + "border-l-gray-100", + "border-l-gray-200", + "border-l-gray-300", + "border-l-gray-400", + "border-l-gray-500", + "border-l-gray-600", + "border-l-gray-700", + "border-l-gray-800", + "border-l-gray-900", + "border-l-gray-950", + "border-l-zinc-50", + "border-l-zinc-100", + "border-l-zinc-200", + "border-l-zinc-300", + "border-l-zinc-400", + "border-l-zinc-500", + "border-l-zinc-600", + "border-l-zinc-700", + "border-l-zinc-800", + "border-l-zinc-900", + "border-l-zinc-950", + "border-l-neutral-50", + "border-l-neutral-100", + "border-l-neutral-200", + "border-l-neutral-300", + "border-l-neutral-400", + "border-l-neutral-500", + "border-l-neutral-600", + "border-l-neutral-700", + "border-l-neutral-800", + "border-l-neutral-900", + "border-l-neutral-950", + "border-l-stone-50", + "border-l-stone-100", + "border-l-stone-200", + "border-l-stone-300", + "border-l-stone-400", + "border-l-stone-500", + "border-l-stone-600", + "border-l-stone-700", + "border-l-stone-800", + "border-l-stone-900", + "border-l-stone-950", + "border-l-red-50", + "border-l-red-100", + "border-l-red-200", + "border-l-red-300", + "border-l-red-400", + "border-l-red-500", + "border-l-red-600", + "border-l-red-700", + "border-l-red-800", + "border-l-red-900", + "border-l-red-950", + "border-l-orange-50", + "border-l-orange-100", + "border-l-orange-200", + "border-l-orange-300", + "border-l-orange-400", + "border-l-orange-500", + "border-l-orange-600", + "border-l-orange-700", + "border-l-orange-800", + "border-l-orange-900", + "border-l-orange-950", + "border-l-amber-50", + "border-l-amber-100", + "border-l-amber-200", + "border-l-amber-300", + "border-l-amber-400", + "border-l-amber-500", + "border-l-amber-600", + "border-l-amber-700", + "border-l-amber-800", + "border-l-amber-900", + "border-l-amber-950", + "border-l-yellow-50", + "border-l-yellow-100", + "border-l-yellow-200", + "border-l-yellow-300", + "border-l-yellow-400", + "border-l-yellow-500", + "border-l-yellow-600", + "border-l-yellow-700", + "border-l-yellow-800", + "border-l-yellow-900", + "border-l-yellow-950", + "border-l-lime-50", + "border-l-lime-100", + "border-l-lime-200", + "border-l-lime-300", + "border-l-lime-400", + "border-l-lime-500", + "border-l-lime-600", + "border-l-lime-700", + "border-l-lime-800", + "border-l-lime-900", + "border-l-lime-950", + "border-l-green-50", + "border-l-green-100", + "border-l-green-200", + "border-l-green-300", + "border-l-green-400", + "border-l-green-500", + "border-l-green-600", + "border-l-green-700", + "border-l-green-800", + "border-l-green-900", + "border-l-green-950", + "border-l-emerald-50", + "border-l-emerald-100", + "border-l-emerald-200", + "border-l-emerald-300", + "border-l-emerald-400", + "border-l-emerald-500", + "border-l-emerald-600", + "border-l-emerald-700", + "border-l-emerald-800", + "border-l-emerald-900", + "border-l-emerald-950", + "border-l-teal-50", + "border-l-teal-100", + "border-l-teal-200", + "border-l-teal-300", + "border-l-teal-400", + "border-l-teal-500", + "border-l-teal-600", + "border-l-teal-700", + "border-l-teal-800", + "border-l-teal-900", + "border-l-teal-950", + "border-l-cyan-50", + "border-l-cyan-100", + "border-l-cyan-200", + "border-l-cyan-300", + "border-l-cyan-400", + "border-l-cyan-500", + "border-l-cyan-600", + "border-l-cyan-700", + "border-l-cyan-800", + "border-l-cyan-900", + "border-l-cyan-950", + "border-l-sky-50", + "border-l-sky-100", + "border-l-sky-200", + "border-l-sky-300", + "border-l-sky-400", + "border-l-sky-500", + "border-l-sky-600", + "border-l-sky-700", + "border-l-sky-800", + "border-l-sky-900", + "border-l-sky-950", + "border-l-blue-50", + "border-l-blue-100", + "border-l-blue-200", + "border-l-blue-300", + "border-l-blue-400", + "border-l-blue-500", + "border-l-blue-600", + "border-l-blue-700", + "border-l-blue-800", + "border-l-blue-900", + "border-l-blue-950", + "border-l-indigo-50", + "border-l-indigo-100", + "border-l-indigo-200", + "border-l-indigo-300", + "border-l-indigo-400", + "border-l-indigo-500", + "border-l-indigo-600", + "border-l-indigo-700", + "border-l-indigo-800", + "border-l-indigo-900", + "border-l-indigo-950", + "border-l-violet-50", + "border-l-violet-100", + "border-l-violet-200", + "border-l-violet-300", + "border-l-violet-400", + "border-l-violet-500", + "border-l-violet-600", + "border-l-violet-700", + "border-l-violet-800", + "border-l-violet-900", + "border-l-violet-950", + "border-l-purple-50", + "border-l-purple-100", + "border-l-purple-200", + "border-l-purple-300", + "border-l-purple-400", + "border-l-purple-500", + "border-l-purple-600", + "border-l-purple-700", + "border-l-purple-800", + "border-l-purple-900", + "border-l-purple-950", + "border-l-fuchsia-50", + "border-l-fuchsia-100", + "border-l-fuchsia-200", + "border-l-fuchsia-300", + "border-l-fuchsia-400", + "border-l-fuchsia-500", + "border-l-fuchsia-600", + "border-l-fuchsia-700", + "border-l-fuchsia-800", + "border-l-fuchsia-900", + "border-l-fuchsia-950", + "border-l-pink-50", + "border-l-pink-100", + "border-l-pink-200", + "border-l-pink-300", + "border-l-pink-400", + "border-l-pink-500", + "border-l-pink-600", + "border-l-pink-700", + "border-l-pink-800", + "border-l-pink-900", + "border-l-pink-950", + "border-l-rose-50", + "border-l-rose-100", + "border-l-rose-200", + "border-l-rose-300", + "border-l-rose-400", + "border-l-rose-500", + "border-l-rose-600", + "border-l-rose-700", + "border-l-rose-800", + "border-l-rose-900", + "border-l-rose-950", + "border-solid", + "border-dashed", + "border-dotted", + "border-double", + "border-hidden", + "border-none", + "divide-x-0", + "divide-x-2", + "divide-x-4", + "divide-x-8", + "divide-x", + "divide-y-0", + "divide-y-2", + "divide-y-4", + "divide-y-8", + "divide-y", + "divide-y-reverse", + "divide-x-reverse", + "divide-inherit", + "divide-current", + "divide-transparent", + "divide-black", + "divide-white", + "divide-slate-50", + "divide-slate-100", + "divide-slate-200", + "divide-slate-300", + "divide-slate-400", + "divide-slate-500", + "divide-slate-600", + "divide-slate-700", + "divide-slate-800", + "divide-slate-900", + "divide-slate-950", + "divide-gray-50", + "divide-gray-100", + "divide-gray-200", + "divide-gray-300", + "divide-gray-400", + "divide-gray-500", + "divide-gray-600", + "divide-gray-700", + "divide-gray-800", + "divide-gray-900", + "divide-gray-950", + "divide-zinc-50", + "divide-zinc-100", + "divide-zinc-200", + "divide-zinc-300", + "divide-zinc-400", + "divide-zinc-500", + "divide-zinc-600", + "divide-zinc-700", + "divide-zinc-800", + "divide-zinc-900", + "divide-zinc-950", + "divide-neutral-50", + "divide-neutral-100", + "divide-neutral-200", + "divide-neutral-300", + "divide-neutral-400", + "divide-neutral-500", + "divide-neutral-600", + "divide-neutral-700", + "divide-neutral-800", + "divide-neutral-900", + "divide-neutral-950", + "divide-stone-50", + "divide-stone-100", + "divide-stone-200", + "divide-stone-300", + "divide-stone-400", + "divide-stone-500", + "divide-stone-600", + "divide-stone-700", + "divide-stone-800", + "divide-stone-900", + "divide-stone-950", + "divide-red-50", + "divide-red-100", + "divide-red-200", + "divide-red-300", + "divide-red-400", + "divide-red-500", + "divide-red-600", + "divide-red-700", + "divide-red-800", + "divide-red-900", + "divide-red-950", + "divide-orange-50", + "divide-orange-100", + "divide-orange-200", + "divide-orange-300", + "divide-orange-400", + "divide-orange-500", + "divide-orange-600", + "divide-orange-700", + "divide-orange-800", + "divide-orange-900", + "divide-orange-950", + "divide-amber-50", + "divide-amber-100", + "divide-amber-200", + "divide-amber-300", + "divide-amber-400", + "divide-amber-500", + "divide-amber-600", + "divide-amber-700", + "divide-amber-800", + "divide-amber-900", + "divide-amber-950", + "divide-yellow-50", + "divide-yellow-100", + "divide-yellow-200", + "divide-yellow-300", + "divide-yellow-400", + "divide-yellow-500", + "divide-yellow-600", + "divide-yellow-700", + "divide-yellow-800", + "divide-yellow-900", + "divide-yellow-950", + "divide-lime-50", + "divide-lime-100", + "divide-lime-200", + "divide-lime-300", + "divide-lime-400", + "divide-lime-500", + "divide-lime-600", + "divide-lime-700", + "divide-lime-800", + "divide-lime-900", + "divide-lime-950", + "divide-green-50", + "divide-green-100", + "divide-green-200", + "divide-green-300", + "divide-green-400", + "divide-green-500", + "divide-green-600", + "divide-green-700", + "divide-green-800", + "divide-green-900", + "divide-green-950", + "divide-emerald-50", + "divide-emerald-100", + "divide-emerald-200", + "divide-emerald-300", + "divide-emerald-400", + "divide-emerald-500", + "divide-emerald-600", + "divide-emerald-700", + "divide-emerald-800", + "divide-emerald-900", + "divide-emerald-950", + "divide-teal-50", + "divide-teal-100", + "divide-teal-200", + "divide-teal-300", + "divide-teal-400", + "divide-teal-500", + "divide-teal-600", + "divide-teal-700", + "divide-teal-800", + "divide-teal-900", + "divide-teal-950", + "divide-cyan-50", + "divide-cyan-100", + "divide-cyan-200", + "divide-cyan-300", + "divide-cyan-400", + "divide-cyan-500", + "divide-cyan-600", + "divide-cyan-700", + "divide-cyan-800", + "divide-cyan-900", + "divide-cyan-950", + "divide-sky-50", + "divide-sky-100", + "divide-sky-200", + "divide-sky-300", + "divide-sky-400", + "divide-sky-500", + "divide-sky-600", + "divide-sky-700", + "divide-sky-800", + "divide-sky-900", + "divide-sky-950", + "divide-blue-50", + "divide-blue-100", + "divide-blue-200", + "divide-blue-300", + "divide-blue-400", + "divide-blue-500", + "divide-blue-600", + "divide-blue-700", + "divide-blue-800", + "divide-blue-900", + "divide-blue-950", + "divide-indigo-50", + "divide-indigo-100", + "divide-indigo-200", + "divide-indigo-300", + "divide-indigo-400", + "divide-indigo-500", + "divide-indigo-600", + "divide-indigo-700", + "divide-indigo-800", + "divide-indigo-900", + "divide-indigo-950", + "divide-violet-50", + "divide-violet-100", + "divide-violet-200", + "divide-violet-300", + "divide-violet-400", + "divide-violet-500", + "divide-violet-600", + "divide-violet-700", + "divide-violet-800", + "divide-violet-900", + "divide-violet-950", + "divide-purple-50", + "divide-purple-100", + "divide-purple-200", + "divide-purple-300", + "divide-purple-400", + "divide-purple-500", + "divide-purple-600", + "divide-purple-700", + "divide-purple-800", + "divide-purple-900", + "divide-purple-950", + "divide-fuchsia-50", + "divide-fuchsia-100", + "divide-fuchsia-200", + "divide-fuchsia-300", + "divide-fuchsia-400", + "divide-fuchsia-500", + "divide-fuchsia-600", + "divide-fuchsia-700", + "divide-fuchsia-800", + "divide-fuchsia-900", + "divide-fuchsia-950", + "divide-pink-50", + "divide-pink-100", + "divide-pink-200", + "divide-pink-300", + "divide-pink-400", + "divide-pink-500", + "divide-pink-600", + "divide-pink-700", + "divide-pink-800", + "divide-pink-900", + "divide-pink-950", + "divide-rose-50", + "divide-rose-100", + "divide-rose-200", + "divide-rose-300", + "divide-rose-400", + "divide-rose-500", + "divide-rose-600", + "divide-rose-700", + "divide-rose-800", + "divide-rose-900", + "divide-rose-950", + "divide-solid", + "divide-dashed", + "divide-dotted", + "divide-double", + "divide-none", + "outline-0", + "outline-1", + "outline-2", + "outline-4", + "outline-8", + "outline-inherit", + "outline-current", + "outline-transparent", + "outline-black", + "outline-white", + "outline-slate-50", + "outline-slate-100", + "outline-slate-200", + "outline-slate-300", + "outline-slate-400", + "outline-slate-500", + "outline-slate-600", + "outline-slate-700", + "outline-slate-800", + "outline-slate-900", + "outline-slate-950", + "outline-gray-50", + "outline-gray-100", + "outline-gray-200", + "outline-gray-300", + "outline-gray-400", + "outline-gray-500", + "outline-gray-600", + "outline-gray-700", + "outline-gray-800", + "outline-gray-900", + "outline-gray-950", + "outline-zinc-50", + "outline-zinc-100", + "outline-zinc-200", + "outline-zinc-300", + "outline-zinc-400", + "outline-zinc-500", + "outline-zinc-600", + "outline-zinc-700", + "outline-zinc-800", + "outline-zinc-900", + "outline-zinc-950", + "outline-neutral-50", + "outline-neutral-100", + "outline-neutral-200", + "outline-neutral-300", + "outline-neutral-400", + "outline-neutral-500", + "outline-neutral-600", + "outline-neutral-700", + "outline-neutral-800", + "outline-neutral-900", + "outline-neutral-950", + "outline-stone-50", + "outline-stone-100", + "outline-stone-200", + "outline-stone-300", + "outline-stone-400", + "outline-stone-500", + "outline-stone-600", + "outline-stone-700", + "outline-stone-800", + "outline-stone-900", + "outline-stone-950", + "outline-red-50", + "outline-red-100", + "outline-red-200", + "outline-red-300", + "outline-red-400", + "outline-red-500", + "outline-red-600", + "outline-red-700", + "outline-red-800", + "outline-red-900", + "outline-red-950", + "outline-orange-50", + "outline-orange-100", + "outline-orange-200", + "outline-orange-300", + "outline-orange-400", + "outline-orange-500", + "outline-orange-600", + "outline-orange-700", + "outline-orange-800", + "outline-orange-900", + "outline-orange-950", + "outline-amber-50", + "outline-amber-100", + "outline-amber-200", + "outline-amber-300", + "outline-amber-400", + "outline-amber-500", + "outline-amber-600", + "outline-amber-700", + "outline-amber-800", + "outline-amber-900", + "outline-amber-950", + "outline-yellow-50", + "outline-yellow-100", + "outline-yellow-200", + "outline-yellow-300", + "outline-yellow-400", + "outline-yellow-500", + "outline-yellow-600", + "outline-yellow-700", + "outline-yellow-800", + "outline-yellow-900", + "outline-yellow-950", + "outline-lime-50", + "outline-lime-100", + "outline-lime-200", + "outline-lime-300", + "outline-lime-400", + "outline-lime-500", + "outline-lime-600", + "outline-lime-700", + "outline-lime-800", + "outline-lime-900", + "outline-lime-950", + "outline-green-50", + "outline-green-100", + "outline-green-200", + "outline-green-300", + "outline-green-400", + "outline-green-500", + "outline-green-600", + "outline-green-700", + "outline-green-800", + "outline-green-900", + "outline-green-950", + "outline-emerald-50", + "outline-emerald-100", + "outline-emerald-200", + "outline-emerald-300", + "outline-emerald-400", + "outline-emerald-500", + "outline-emerald-600", + "outline-emerald-700", + "outline-emerald-800", + "outline-emerald-900", + "outline-emerald-950", + "outline-teal-50", + "outline-teal-100", + "outline-teal-200", + "outline-teal-300", + "outline-teal-400", + "outline-teal-500", + "outline-teal-600", + "outline-teal-700", + "outline-teal-800", + "outline-teal-900", + "outline-teal-950", + "outline-cyan-50", + "outline-cyan-100", + "outline-cyan-200", + "outline-cyan-300", + "outline-cyan-400", + "outline-cyan-500", + "outline-cyan-600", + "outline-cyan-700", + "outline-cyan-800", + "outline-cyan-900", + "outline-cyan-950", + "outline-sky-50", + "outline-sky-100", + "outline-sky-200", + "outline-sky-300", + "outline-sky-400", + "outline-sky-500", + "outline-sky-600", + "outline-sky-700", + "outline-sky-800", + "outline-sky-900", + "outline-sky-950", + "outline-blue-50", + "outline-blue-100", + "outline-blue-200", + "outline-blue-300", + "outline-blue-400", + "outline-blue-500", + "outline-blue-600", + "outline-blue-700", + "outline-blue-800", + "outline-blue-900", + "outline-blue-950", + "outline-indigo-50", + "outline-indigo-100", + "outline-indigo-200", + "outline-indigo-300", + "outline-indigo-400", + "outline-indigo-500", + "outline-indigo-600", + "outline-indigo-700", + "outline-indigo-800", + "outline-indigo-900", + "outline-indigo-950", + "outline-violet-50", + "outline-violet-100", + "outline-violet-200", + "outline-violet-300", + "outline-violet-400", + "outline-violet-500", + "outline-violet-600", + "outline-violet-700", + "outline-violet-800", + "outline-violet-900", + "outline-violet-950", + "outline-purple-50", + "outline-purple-100", + "outline-purple-200", + "outline-purple-300", + "outline-purple-400", + "outline-purple-500", + "outline-purple-600", + "outline-purple-700", + "outline-purple-800", + "outline-purple-900", + "outline-purple-950", + "outline-fuchsia-50", + "outline-fuchsia-100", + "outline-fuchsia-200", + "outline-fuchsia-300", + "outline-fuchsia-400", + "outline-fuchsia-500", + "outline-fuchsia-600", + "outline-fuchsia-700", + "outline-fuchsia-800", + "outline-fuchsia-900", + "outline-fuchsia-950", + "outline-pink-50", + "outline-pink-100", + "outline-pink-200", + "outline-pink-300", + "outline-pink-400", + "outline-pink-500", + "outline-pink-600", + "outline-pink-700", + "outline-pink-800", + "outline-pink-900", + "outline-pink-950", + "outline-rose-50", + "outline-rose-100", + "outline-rose-200", + "outline-rose-300", + "outline-rose-400", + "outline-rose-500", + "outline-rose-600", + "outline-rose-700", + "outline-rose-800", + "outline-rose-900", + "outline-rose-950", + "outline-none", + "outline", + "outline-dashed", + "outline-dotted", + "outline-double", + "outline-offset-0", + "outline-offset-1", + "outline-offset-2", + "outline-offset-4", + "outline-offset-8", + "ring-0", + "ring-1", + "ring-2", + "ring", + "ring-4", + "ring-8", + "ring-inset", + "ring-inherit", + "ring-current", + "ring-transparent", + "ring-black", + "ring-white", + "ring-slate-50", + "ring-slate-100", + "ring-slate-200", + "ring-slate-300", + "ring-slate-400", + "ring-slate-500", + "ring-slate-600", + "ring-slate-700", + "ring-slate-800", + "ring-slate-900", + "ring-slate-950", + "ring-gray-50", + "ring-gray-100", + "ring-gray-200", + "ring-gray-300", + "ring-gray-400", + "ring-gray-500", + "ring-gray-600", + "ring-gray-700", + "ring-gray-800", + "ring-gray-900", + "ring-gray-950", + "ring-zinc-50", + "ring-zinc-100", + "ring-zinc-200", + "ring-zinc-300", + "ring-zinc-400", + "ring-zinc-500", + "ring-zinc-600", + "ring-zinc-700", + "ring-zinc-800", + "ring-zinc-900", + "ring-zinc-950", + "ring-neutral-50", + "ring-neutral-100", + "ring-neutral-200", + "ring-neutral-300", + "ring-neutral-400", + "ring-neutral-500", + "ring-neutral-600", + "ring-neutral-700", + "ring-neutral-800", + "ring-neutral-900", + "ring-neutral-950", + "ring-stone-50", + "ring-stone-100", + "ring-stone-200", + "ring-stone-300", + "ring-stone-400", + "ring-stone-500", + "ring-stone-600", + "ring-stone-700", + "ring-stone-800", + "ring-stone-900", + "ring-stone-950", + "ring-red-50", + "ring-red-100", + "ring-red-200", + "ring-red-300", + "ring-red-400", + "ring-red-500", + "ring-red-600", + "ring-red-700", + "ring-red-800", + "ring-red-900", + "ring-red-950", + "ring-orange-50", + "ring-orange-100", + "ring-orange-200", + "ring-orange-300", + "ring-orange-400", + "ring-orange-500", + "ring-orange-600", + "ring-orange-700", + "ring-orange-800", + "ring-orange-900", + "ring-orange-950", + "ring-amber-50", + "ring-amber-100", + "ring-amber-200", + "ring-amber-300", + "ring-amber-400", + "ring-amber-500", + "ring-amber-600", + "ring-amber-700", + "ring-amber-800", + "ring-amber-900", + "ring-amber-950", + "ring-yellow-50", + "ring-yellow-100", + "ring-yellow-200", + "ring-yellow-300", + "ring-yellow-400", + "ring-yellow-500", + "ring-yellow-600", + "ring-yellow-700", + "ring-yellow-800", + "ring-yellow-900", + "ring-yellow-950", + "ring-lime-50", + "ring-lime-100", + "ring-lime-200", + "ring-lime-300", + "ring-lime-400", + "ring-lime-500", + "ring-lime-600", + "ring-lime-700", + "ring-lime-800", + "ring-lime-900", + "ring-lime-950", + "ring-green-50", + "ring-green-100", + "ring-green-200", + "ring-green-300", + "ring-green-400", + "ring-green-500", + "ring-green-600", + "ring-green-700", + "ring-green-800", + "ring-green-900", + "ring-green-950", + "ring-emerald-50", + "ring-emerald-100", + "ring-emerald-200", + "ring-emerald-300", + "ring-emerald-400", + "ring-emerald-500", + "ring-emerald-600", + "ring-emerald-700", + "ring-emerald-800", + "ring-emerald-900", + "ring-emerald-950", + "ring-teal-50", + "ring-teal-100", + "ring-teal-200", + "ring-teal-300", + "ring-teal-400", + "ring-teal-500", + "ring-teal-600", + "ring-teal-700", + "ring-teal-800", + "ring-teal-900", + "ring-teal-950", + "ring-cyan-50", + "ring-cyan-100", + "ring-cyan-200", + "ring-cyan-300", + "ring-cyan-400", + "ring-cyan-500", + "ring-cyan-600", + "ring-cyan-700", + "ring-cyan-800", + "ring-cyan-900", + "ring-cyan-950", + "ring-sky-50", + "ring-sky-100", + "ring-sky-200", + "ring-sky-300", + "ring-sky-400", + "ring-sky-500", + "ring-sky-600", + "ring-sky-700", + "ring-sky-800", + "ring-sky-900", + "ring-sky-950", + "ring-blue-50", + "ring-blue-100", + "ring-blue-200", + "ring-blue-300", + "ring-blue-400", + "ring-blue-500", + "ring-blue-600", + "ring-blue-700", + "ring-blue-800", + "ring-blue-900", + "ring-blue-950", + "ring-indigo-50", + "ring-indigo-100", + "ring-indigo-200", + "ring-indigo-300", + "ring-indigo-400", + "ring-indigo-500", + "ring-indigo-600", + "ring-indigo-700", + "ring-indigo-800", + "ring-indigo-900", + "ring-indigo-950", + "ring-violet-50", + "ring-violet-100", + "ring-violet-200", + "ring-violet-300", + "ring-violet-400", + "ring-violet-500", + "ring-violet-600", + "ring-violet-700", + "ring-violet-800", + "ring-violet-900", + "ring-violet-950", + "ring-purple-50", + "ring-purple-100", + "ring-purple-200", + "ring-purple-300", + "ring-purple-400", + "ring-purple-500", + "ring-purple-600", + "ring-purple-700", + "ring-purple-800", + "ring-purple-900", + "ring-purple-950", + "ring-fuchsia-50", + "ring-fuchsia-100", + "ring-fuchsia-200", + "ring-fuchsia-300", + "ring-fuchsia-400", + "ring-fuchsia-500", + "ring-fuchsia-600", + "ring-fuchsia-700", + "ring-fuchsia-800", + "ring-fuchsia-900", + "ring-fuchsia-950", + "ring-pink-50", + "ring-pink-100", + "ring-pink-200", + "ring-pink-300", + "ring-pink-400", + "ring-pink-500", + "ring-pink-600", + "ring-pink-700", + "ring-pink-800", + "ring-pink-900", + "ring-pink-950", + "ring-rose-50", + "ring-rose-100", + "ring-rose-200", + "ring-rose-300", + "ring-rose-400", + "ring-rose-500", + "ring-rose-600", + "ring-rose-700", + "ring-rose-800", + "ring-rose-900", + "ring-rose-950", + "ring-offset-0", + "ring-offset-1", + "ring-offset-2", + "ring-offset-4", + "ring-offset-8", + "ring-offset-inherit", + "ring-offset-current", + "ring-offset-transparent", + "ring-offset-black", + "ring-offset-white", + "ring-offset-slate-50", + "ring-offset-slate-100", + "ring-offset-slate-200", + "ring-offset-slate-300", + "ring-offset-slate-400", + "ring-offset-slate-500", + "ring-offset-slate-600", + "ring-offset-slate-700", + "ring-offset-slate-800", + "ring-offset-slate-900", + "ring-offset-slate-950", + "ring-offset-gray-50", + "ring-offset-gray-100", + "ring-offset-gray-200", + "ring-offset-gray-300", + "ring-offset-gray-400", + "ring-offset-gray-500", + "ring-offset-gray-600", + "ring-offset-gray-700", + "ring-offset-gray-800", + "ring-offset-gray-900", + "ring-offset-gray-950", + "ring-offset-zinc-50", + "ring-offset-zinc-100", + "ring-offset-zinc-200", + "ring-offset-zinc-300", + "ring-offset-zinc-400", + "ring-offset-zinc-500", + "ring-offset-zinc-600", + "ring-offset-zinc-700", + "ring-offset-zinc-800", + "ring-offset-zinc-900", + "ring-offset-zinc-950", + "ring-offset-neutral-50", + "ring-offset-neutral-100", + "ring-offset-neutral-200", + "ring-offset-neutral-300", + "ring-offset-neutral-400", + "ring-offset-neutral-500", + "ring-offset-neutral-600", + "ring-offset-neutral-700", + "ring-offset-neutral-800", + "ring-offset-neutral-900", + "ring-offset-neutral-950", + "ring-offset-stone-50", + "ring-offset-stone-100", + "ring-offset-stone-200", + "ring-offset-stone-300", + "ring-offset-stone-400", + "ring-offset-stone-500", + "ring-offset-stone-600", + "ring-offset-stone-700", + "ring-offset-stone-800", + "ring-offset-stone-900", + "ring-offset-stone-950", + "ring-offset-red-50", + "ring-offset-red-100", + "ring-offset-red-200", + "ring-offset-red-300", + "ring-offset-red-400", + "ring-offset-red-500", + "ring-offset-red-600", + "ring-offset-red-700", + "ring-offset-red-800", + "ring-offset-red-900", + "ring-offset-red-950", + "ring-offset-orange-50", + "ring-offset-orange-100", + "ring-offset-orange-200", + "ring-offset-orange-300", + "ring-offset-orange-400", + "ring-offset-orange-500", + "ring-offset-orange-600", + "ring-offset-orange-700", + "ring-offset-orange-800", + "ring-offset-orange-900", + "ring-offset-orange-950", + "ring-offset-amber-50", + "ring-offset-amber-100", + "ring-offset-amber-200", + "ring-offset-amber-300", + "ring-offset-amber-400", + "ring-offset-amber-500", + "ring-offset-amber-600", + "ring-offset-amber-700", + "ring-offset-amber-800", + "ring-offset-amber-900", + "ring-offset-amber-950", + "ring-offset-yellow-50", + "ring-offset-yellow-100", + "ring-offset-yellow-200", + "ring-offset-yellow-300", + "ring-offset-yellow-400", + "ring-offset-yellow-500", + "ring-offset-yellow-600", + "ring-offset-yellow-700", + "ring-offset-yellow-800", + "ring-offset-yellow-900", + "ring-offset-yellow-950", + "ring-offset-lime-50", + "ring-offset-lime-100", + "ring-offset-lime-200", + "ring-offset-lime-300", + "ring-offset-lime-400", + "ring-offset-lime-500", + "ring-offset-lime-600", + "ring-offset-lime-700", + "ring-offset-lime-800", + "ring-offset-lime-900", + "ring-offset-lime-950", + "ring-offset-green-50", + "ring-offset-green-100", + "ring-offset-green-200", + "ring-offset-green-300", + "ring-offset-green-400", + "ring-offset-green-500", + "ring-offset-green-600", + "ring-offset-green-700", + "ring-offset-green-800", + "ring-offset-green-900", + "ring-offset-green-950", + "ring-offset-emerald-50", + "ring-offset-emerald-100", + "ring-offset-emerald-200", + "ring-offset-emerald-300", + "ring-offset-emerald-400", + "ring-offset-emerald-500", + "ring-offset-emerald-600", + "ring-offset-emerald-700", + "ring-offset-emerald-800", + "ring-offset-emerald-900", + "ring-offset-emerald-950", + "ring-offset-teal-50", + "ring-offset-teal-100", + "ring-offset-teal-200", + "ring-offset-teal-300", + "ring-offset-teal-400", + "ring-offset-teal-500", + "ring-offset-teal-600", + "ring-offset-teal-700", + "ring-offset-teal-800", + "ring-offset-teal-900", + "ring-offset-teal-950", + "ring-offset-cyan-50", + "ring-offset-cyan-100", + "ring-offset-cyan-200", + "ring-offset-cyan-300", + "ring-offset-cyan-400", + "ring-offset-cyan-500", + "ring-offset-cyan-600", + "ring-offset-cyan-700", + "ring-offset-cyan-800", + "ring-offset-cyan-900", + "ring-offset-cyan-950", + "ring-offset-sky-50", + "ring-offset-sky-100", + "ring-offset-sky-200", + "ring-offset-sky-300", + "ring-offset-sky-400", + "ring-offset-sky-500", + "ring-offset-sky-600", + "ring-offset-sky-700", + "ring-offset-sky-800", + "ring-offset-sky-900", + "ring-offset-sky-950", + "ring-offset-blue-50", + "ring-offset-blue-100", + "ring-offset-blue-200", + "ring-offset-blue-300", + "ring-offset-blue-400", + "ring-offset-blue-500", + "ring-offset-blue-600", + "ring-offset-blue-700", + "ring-offset-blue-800", + "ring-offset-blue-900", + "ring-offset-blue-950", + "ring-offset-indigo-50", + "ring-offset-indigo-100", + "ring-offset-indigo-200", + "ring-offset-indigo-300", + "ring-offset-indigo-400", + "ring-offset-indigo-500", + "ring-offset-indigo-600", + "ring-offset-indigo-700", + "ring-offset-indigo-800", + "ring-offset-indigo-900", + "ring-offset-indigo-950", + "ring-offset-violet-50", + "ring-offset-violet-100", + "ring-offset-violet-200", + "ring-offset-violet-300", + "ring-offset-violet-400", + "ring-offset-violet-500", + "ring-offset-violet-600", + "ring-offset-violet-700", + "ring-offset-violet-800", + "ring-offset-violet-900", + "ring-offset-violet-950", + "ring-offset-purple-50", + "ring-offset-purple-100", + "ring-offset-purple-200", + "ring-offset-purple-300", + "ring-offset-purple-400", + "ring-offset-purple-500", + "ring-offset-purple-600", + "ring-offset-purple-700", + "ring-offset-purple-800", + "ring-offset-purple-900", + "ring-offset-purple-950", + "ring-offset-fuchsia-50", + "ring-offset-fuchsia-100", + "ring-offset-fuchsia-200", + "ring-offset-fuchsia-300", + "ring-offset-fuchsia-400", + "ring-offset-fuchsia-500", + "ring-offset-fuchsia-600", + "ring-offset-fuchsia-700", + "ring-offset-fuchsia-800", + "ring-offset-fuchsia-900", + "ring-offset-fuchsia-950", + "ring-offset-pink-50", + "ring-offset-pink-100", + "ring-offset-pink-200", + "ring-offset-pink-300", + "ring-offset-pink-400", + "ring-offset-pink-500", + "ring-offset-pink-600", + "ring-offset-pink-700", + "ring-offset-pink-800", + "ring-offset-pink-900", + "ring-offset-pink-950", + "ring-offset-rose-50", + "ring-offset-rose-100", + "ring-offset-rose-200", + "ring-offset-rose-300", + "ring-offset-rose-400", + "ring-offset-rose-500", + "ring-offset-rose-600", + "ring-offset-rose-700", + "ring-offset-rose-800", + "ring-offset-rose-900", + "ring-offset-rose-950", + "shadow-sm", + "shadow", + "shadow-md", + "shadow-lg", + "shadow-xl", + "shadow-2xl", + "shadow-inner", + "shadow-none", + "shadow-inherit", + "shadow-current", + "shadow-transparent", + "shadow-black", + "shadow-white", + "shadow-slate-50", + "shadow-slate-100", + "shadow-slate-200", + "shadow-slate-300", + "shadow-slate-400", + "shadow-slate-500", + "shadow-slate-600", + "shadow-slate-700", + "shadow-slate-800", + "shadow-slate-900", + "shadow-slate-950", + "shadow-gray-50", + "shadow-gray-100", + "shadow-gray-200", + "shadow-gray-300", + "shadow-gray-400", + "shadow-gray-500", + "shadow-gray-600", + "shadow-gray-700", + "shadow-gray-800", + "shadow-gray-900", + "shadow-gray-950", + "shadow-zinc-50", + "shadow-zinc-100", + "shadow-zinc-200", + "shadow-zinc-300", + "shadow-zinc-400", + "shadow-zinc-500", + "shadow-zinc-600", + "shadow-zinc-700", + "shadow-zinc-800", + "shadow-zinc-900", + "shadow-zinc-950", + "shadow-neutral-50", + "shadow-neutral-100", + "shadow-neutral-200", + "shadow-neutral-300", + "shadow-neutral-400", + "shadow-neutral-500", + "shadow-neutral-600", + "shadow-neutral-700", + "shadow-neutral-800", + "shadow-neutral-900", + "shadow-neutral-950", + "shadow-stone-50", + "shadow-stone-100", + "shadow-stone-200", + "shadow-stone-300", + "shadow-stone-400", + "shadow-stone-500", + "shadow-stone-600", + "shadow-stone-700", + "shadow-stone-800", + "shadow-stone-900", + "shadow-stone-950", + "shadow-red-50", + "shadow-red-100", + "shadow-red-200", + "shadow-red-300", + "shadow-red-400", + "shadow-red-500", + "shadow-red-600", + "shadow-red-700", + "shadow-red-800", + "shadow-red-900", + "shadow-red-950", + "shadow-orange-50", + "shadow-orange-100", + "shadow-orange-200", + "shadow-orange-300", + "shadow-orange-400", + "shadow-orange-500", + "shadow-orange-600", + "shadow-orange-700", + "shadow-orange-800", + "shadow-orange-900", + "shadow-orange-950", + "shadow-amber-50", + "shadow-amber-100", + "shadow-amber-200", + "shadow-amber-300", + "shadow-amber-400", + "shadow-amber-500", + "shadow-amber-600", + "shadow-amber-700", + "shadow-amber-800", + "shadow-amber-900", + "shadow-amber-950", + "shadow-yellow-50", + "shadow-yellow-100", + "shadow-yellow-200", + "shadow-yellow-300", + "shadow-yellow-400", + "shadow-yellow-500", + "shadow-yellow-600", + "shadow-yellow-700", + "shadow-yellow-800", + "shadow-yellow-900", + "shadow-yellow-950", + "shadow-lime-50", + "shadow-lime-100", + "shadow-lime-200", + "shadow-lime-300", + "shadow-lime-400", + "shadow-lime-500", + "shadow-lime-600", + "shadow-lime-700", + "shadow-lime-800", + "shadow-lime-900", + "shadow-lime-950", + "shadow-green-50", + "shadow-green-100", + "shadow-green-200", + "shadow-green-300", + "shadow-green-400", + "shadow-green-500", + "shadow-green-600", + "shadow-green-700", + "shadow-green-800", + "shadow-green-900", + "shadow-green-950", + "shadow-emerald-50", + "shadow-emerald-100", + "shadow-emerald-200", + "shadow-emerald-300", + "shadow-emerald-400", + "shadow-emerald-500", + "shadow-emerald-600", + "shadow-emerald-700", + "shadow-emerald-800", + "shadow-emerald-900", + "shadow-emerald-950", + "shadow-teal-50", + "shadow-teal-100", + "shadow-teal-200", + "shadow-teal-300", + "shadow-teal-400", + "shadow-teal-500", + "shadow-teal-600", + "shadow-teal-700", + "shadow-teal-800", + "shadow-teal-900", + "shadow-teal-950", + "shadow-cyan-50", + "shadow-cyan-100", + "shadow-cyan-200", + "shadow-cyan-300", + "shadow-cyan-400", + "shadow-cyan-500", + "shadow-cyan-600", + "shadow-cyan-700", + "shadow-cyan-800", + "shadow-cyan-900", + "shadow-cyan-950", + "shadow-sky-50", + "shadow-sky-100", + "shadow-sky-200", + "shadow-sky-300", + "shadow-sky-400", + "shadow-sky-500", + "shadow-sky-600", + "shadow-sky-700", + "shadow-sky-800", + "shadow-sky-900", + "shadow-sky-950", + "shadow-blue-50", + "shadow-blue-100", + "shadow-blue-200", + "shadow-blue-300", + "shadow-blue-400", + "shadow-blue-500", + "shadow-blue-600", + "shadow-blue-700", + "shadow-blue-800", + "shadow-blue-900", + "shadow-blue-950", + "shadow-indigo-50", + "shadow-indigo-100", + "shadow-indigo-200", + "shadow-indigo-300", + "shadow-indigo-400", + "shadow-indigo-500", + "shadow-indigo-600", + "shadow-indigo-700", + "shadow-indigo-800", + "shadow-indigo-900", + "shadow-indigo-950", + "shadow-violet-50", + "shadow-violet-100", + "shadow-violet-200", + "shadow-violet-300", + "shadow-violet-400", + "shadow-violet-500", + "shadow-violet-600", + "shadow-violet-700", + "shadow-violet-800", + "shadow-violet-900", + "shadow-violet-950", + "shadow-purple-50", + "shadow-purple-100", + "shadow-purple-200", + "shadow-purple-300", + "shadow-purple-400", + "shadow-purple-500", + "shadow-purple-600", + "shadow-purple-700", + "shadow-purple-800", + "shadow-purple-900", + "shadow-purple-950", + "shadow-fuchsia-50", + "shadow-fuchsia-100", + "shadow-fuchsia-200", + "shadow-fuchsia-300", + "shadow-fuchsia-400", + "shadow-fuchsia-500", + "shadow-fuchsia-600", + "shadow-fuchsia-700", + "shadow-fuchsia-800", + "shadow-fuchsia-900", + "shadow-fuchsia-950", + "shadow-pink-50", + "shadow-pink-100", + "shadow-pink-200", + "shadow-pink-300", + "shadow-pink-400", + "shadow-pink-500", + "shadow-pink-600", + "shadow-pink-700", + "shadow-pink-800", + "shadow-pink-900", + "shadow-pink-950", + "shadow-rose-50", + "shadow-rose-100", + "shadow-rose-200", + "shadow-rose-300", + "shadow-rose-400", + "shadow-rose-500", + "shadow-rose-600", + "shadow-rose-700", + "shadow-rose-800", + "shadow-rose-900", + "shadow-rose-950", + "opacity-0", + "opacity-5", + "opacity-10", + "opacity-15", + "opacity-20", + "opacity-25", + "opacity-30", + "opacity-35", + "opacity-40", + "opacity-45", + "opacity-50", + "opacity-55", + "opacity-60", + "opacity-65", + "opacity-70", + "opacity-75", + "opacity-80", + "opacity-85", + "opacity-90", + "opacity-95", + "opacity-100", + "mix-blend-normal", + "mix-blend-multiply", + "mix-blend-screen", + "mix-blend-overlay", + "mix-blend-darken", + "mix-blend-lighten", + "mix-blend-color-dodge", + "mix-blend-color-burn", + "mix-blend-hard-light", + "mix-blend-soft-light", + "mix-blend-difference", + "mix-blend-exclusion", + "mix-blend-hue", + "mix-blend-saturation", + "mix-blend-color", + "mix-blend-luminosity", + "mix-blend-plus-darker", + "mix-blend-plus-lighter", + "bg-blend-normal", + "bg-blend-multiply", + "bg-blend-screen", + "bg-blend-overlay", + "bg-blend-darken", + "bg-blend-lighten", + "bg-blend-color-dodge", + "bg-blend-color-burn", + "bg-blend-hard-light", + "bg-blend-soft-light", + "bg-blend-difference", + "bg-blend-exclusion", + "bg-blend-hue", + "bg-blend-saturation", + "bg-blend-color", + "bg-blend-luminosity", + "blur-none", + "blur-sm", + "blur", + "blur-md", + "blur-lg", + "blur-xl", + "blur-2xl", + "blur-3xl", + "brightness-0", + "brightness-50", + "brightness-75", + "brightness-90", + "brightness-95", + "brightness-100", + "brightness-105", + "brightness-110", + "brightness-125", + "brightness-150", + "brightness-200", + "contrast-0", + "contrast-50", + "contrast-75", + "contrast-100", + "contrast-125", + "contrast-150", + "contrast-200", + "drop-shadow-sm", + "drop-shadow", + "drop-shadow-md", + "drop-shadow-lg", + "drop-shadow-xl", + "drop-shadow-2xl", + "drop-shadow-none", + "grayscale-0", + "grayscale", + "hue-rotate-0", + "hue-rotate-15", + "hue-rotate-30", + "hue-rotate-60", + "hue-rotate-90", + "hue-rotate-180", + "invert-0", + "invert", + "saturate-0", + "saturate-50", + "saturate-100", + "saturate-150", + "saturate-200", + "sepia-0", + "sepia", + "backdrop-blur-none", + "backdrop-blur-sm", + "backdrop-blur", + "backdrop-blur-md", + "backdrop-blur-lg", + "backdrop-blur-xl", + "backdrop-blur-2xl", + "backdrop-blur-3xl", + "backdrop-brightness-0", + "backdrop-brightness-50", + "backdrop-brightness-75", + "backdrop-brightness-90", + "backdrop-brightness-95", + "backdrop-brightness-100", + "backdrop-brightness-105", + "backdrop-brightness-110", + "backdrop-brightness-125", + "backdrop-brightness-150", + "backdrop-brightness-200", + "backdrop-contrast-0", + "backdrop-contrast-50", + "backdrop-contrast-75", + "backdrop-contrast-100", + "backdrop-contrast-125", + "backdrop-contrast-150", + "backdrop-contrast-200", + "backdrop-grayscale-0", + "backdrop-grayscale", + "backdrop-hue-rotate-0", + "backdrop-hue-rotate-15", + "backdrop-hue-rotate-30", + "backdrop-hue-rotate-60", + "backdrop-hue-rotate-90", + "backdrop-hue-rotate-180", + "backdrop-invert-0", + "backdrop-invert", + "backdrop-opacity-0", + "backdrop-opacity-5", + "backdrop-opacity-10", + "backdrop-opacity-15", + "backdrop-opacity-20", + "backdrop-opacity-25", + "backdrop-opacity-30", + "backdrop-opacity-35", + "backdrop-opacity-40", + "backdrop-opacity-45", + "backdrop-opacity-50", + "backdrop-opacity-55", + "backdrop-opacity-60", + "backdrop-opacity-65", + "backdrop-opacity-70", + "backdrop-opacity-75", + "backdrop-opacity-80", + "backdrop-opacity-85", + "backdrop-opacity-90", + "backdrop-opacity-95", + "backdrop-opacity-100", + "backdrop-saturate-0", + "backdrop-saturate-50", + "backdrop-saturate-100", + "backdrop-saturate-150", + "backdrop-saturate-200", + "backdrop-sepia-0", + "backdrop-sepia", + "border-collapse", + "border-separate", + "Indiana", + "Ohio", + "Michigan", + "Indiana", + "Ohio", + "Michigan", + "border-spacing-0", + "border-spacing-x-0", + "border-spacing-y-0", + "border-spacing-px", + "border-spacing-x-px", + "border-spacing-y-px", + "border-spacing-0.5", + "border-spacing-x-0.5", + "border-spacing-y-0.5", + "border-spacing-1", + "border-spacing-x-1", + "border-spacing-y-1", + "border-spacing-1.5", + "border-spacing-x-1.5", + "border-spacing-y-1.5", + "border-spacing-2", + "border-spacing-x-2", + "border-spacing-y-2", + "border-spacing-2.5", + "border-spacing-x-2.5", + "border-spacing-y-2.5", + "border-spacing-3", + "border-spacing-x-3", + "border-spacing-y-3", + "border-spacing-3.5", + "border-spacing-x-3.5", + "border-spacing-y-3.5", + "border-spacing-4", + "border-spacing-x-4", + "border-spacing-y-4", + "border-spacing-5", + "border-spacing-x-5", + "border-spacing-y-5", + "border-spacing-6", + "border-spacing-x-6", + "border-spacing-y-6", + "border-spacing-7", + "border-spacing-x-7", + "border-spacing-y-7", + "border-spacing-8", + "border-spacing-x-8", + "border-spacing-y-8", + "border-spacing-9", + "border-spacing-x-9", + "border-spacing-y-9", + "border-spacing-10", + "border-spacing-x-10", + "border-spacing-y-10", + "border-spacing-11", + "border-spacing-x-11", + "border-spacing-y-11", + "border-spacing-12", + "border-spacing-x-12", + "border-spacing-y-12", + "border-spacing-14", + "border-spacing-x-14", + "border-spacing-y-14", + "border-spacing-16", + "border-spacing-x-16", + "border-spacing-y-16", + "border-spacing-20", + "border-spacing-x-20", + "border-spacing-y-20", + "border-spacing-24", + "border-spacing-x-24", + "border-spacing-y-24", + "border-spacing-28", + "border-spacing-x-28", + "border-spacing-y-28", + "border-spacing-32", + "border-spacing-x-32", + "border-spacing-y-32", + "border-spacing-36", + "border-spacing-x-36", + "border-spacing-y-36", + "border-spacing-40", + "border-spacing-x-40", + "border-spacing-y-40", + "border-spacing-44", + "border-spacing-x-44", + "border-spacing-y-44", + "border-spacing-48", + "border-spacing-x-48", + "border-spacing-y-48", + "border-spacing-52", + "border-spacing-x-52", + "border-spacing-y-52", + "border-spacing-56", + "border-spacing-x-56", + "border-spacing-y-56", + "border-spacing-60", + "border-spacing-x-60", + "border-spacing-y-60", + "border-spacing-64", + "border-spacing-x-64", + "border-spacing-y-64", + "border-spacing-72", + "border-spacing-x-72", + "border-spacing-y-72", + "border-spacing-80", + "border-spacing-x-80", + "border-spacing-y-80", + "border-spacing-96", + "border-spacing-x-96", + "border-spacing-y-96", + "Indiana", + "Ohio", + "Michigan", + "table-auto", + "table-fixed", + "caption-top", + "caption-bottom", + "transition-none", + "transition-all", + "transition", + "transition-colors", + "transition-opacity", + "transition-shadow", + "transition-transform", + "duration-0", + "duration-75", + "duration-100", + "duration-150", + "duration-200", + "duration-300", + "duration-500", + "duration-700", + "duration-1000", + "ease-linear", + "ease-in", + "ease-out", + "ease-in-out", + "delay-0", + "delay-75", + "delay-100", + "delay-150", + "delay-200", + "delay-300", + "delay-500", + "delay-700", + "delay-1000", + "animate-none", + "animate-spin", + "animate-ping", + "animate-pulse", + "animate-bounce", + "scale-0", + "scale-x-0", + "scale-y-0", + "scale-50", + "scale-x-50", + "scale-y-50", + "scale-75", + "scale-x-75", + "scale-y-75", + "scale-90", + "scale-x-90", + "scale-y-90", + "scale-95", + "scale-x-95", + "scale-y-95", + "scale-100", + "scale-x-100", + "scale-y-100", + "scale-105", + "scale-x-105", + "scale-y-105", + "scale-110", + "scale-x-110", + "scale-y-110", + "scale-125", + "scale-x-125", + "scale-y-125", + "scale-150", + "scale-x-150", + "scale-y-150", + "rotate-0", + "rotate-1", + "rotate-2", + "rotate-3", + "rotate-6", + "rotate-12", + "rotate-45", + "rotate-90", + "rotate-180", + "translate-x-0", + "translate-y-0", + "translate-x-px", + "translate-y-px", + "translate-x-0.5", + "translate-y-0.5", + "translate-x-1", + "translate-y-1", + "translate-x-1.5", + "translate-y-1.5", + "translate-x-2", + "translate-y-2", + "translate-x-2.5", + "translate-y-2.5", + "translate-x-3", + "translate-y-3", + "translate-x-3.5", + "translate-y-3.5", + "translate-x-4", + "translate-y-4", + "translate-x-5", + "translate-y-5", + "translate-x-6", + "translate-y-6", + "translate-x-7", + "translate-y-7", + "translate-x-8", + "translate-y-8", + "translate-x-9", + "translate-y-9", + "translate-x-10", + "translate-y-10", + "translate-x-11", + "translate-y-11", + "translate-x-12", + "translate-y-12", + "translate-x-14", + "translate-y-14", + "translate-x-16", + "translate-y-16", + "translate-x-20", + "translate-y-20", + "translate-x-24", + "translate-y-24", + "translate-x-28", + "translate-y-28", + "translate-x-32", + "translate-y-32", + "translate-x-36", + "translate-y-36", + "translate-x-40", + "translate-y-40", + "translate-x-44", + "translate-y-44", + "translate-x-48", + "translate-y-48", + "translate-x-52", + "translate-y-52", + "translate-x-56", + "translate-y-56", + "translate-x-60", + "translate-y-60", + "translate-x-64", + "translate-y-64", + "translate-x-72", + "translate-y-72", + "translate-x-80", + "translate-y-80", + "translate-x-96", + "translate-y-96", + "translate-x-1/2", + "translate-x-1/3", + "translate-x-2/3", + "translate-x-1/4", + "translate-x-2/4", + "translate-x-3/4", + "translate-x-full", + "translate-y-1/2", + "translate-y-1/3", + "translate-y-2/3", + "translate-y-1/4", + "translate-y-2/4", + "translate-y-3/4", + "translate-y-full", + "skew-x-0", + "skew-y-0", + "skew-x-1", + "skew-y-1", + "skew-x-2", + "skew-y-2", + "skew-x-3", + "skew-y-3", + "skew-x-6", + "skew-y-6", + "skew-x-12", + "skew-y-12", + "origin-center", + "origin-top", + "origin-top-right", + "origin-right", + "origin-bottom-right", + "origin-bottom", + "origin-bottom-left", + "origin-left", + "origin-top-left", + "accent-inherit", + "accent-current", + "accent-transparent", + "accent-black", + "accent-white", + "accent-slate-50", + "accent-slate-100", + "accent-slate-200", + "accent-slate-300", + "accent-slate-400", + "accent-slate-500", + "accent-slate-600", + "accent-slate-700", + "accent-slate-800", + "accent-slate-900", + "accent-slate-950", + "accent-gray-50", + "accent-gray-100", + "accent-gray-200", + "accent-gray-300", + "accent-gray-400", + "accent-gray-500", + "accent-gray-600", + "accent-gray-700", + "accent-gray-800", + "accent-gray-900", + "accent-gray-950", + "accent-zinc-50", + "accent-zinc-100", + "accent-zinc-200", + "accent-zinc-300", + "accent-zinc-400", + "accent-zinc-500", + "accent-zinc-600", + "accent-zinc-700", + "accent-zinc-800", + "accent-zinc-900", + "accent-zinc-950", + "accent-neutral-50", + "accent-neutral-100", + "accent-neutral-200", + "accent-neutral-300", + "accent-neutral-400", + "accent-neutral-500", + "accent-neutral-600", + "accent-neutral-700", + "accent-neutral-800", + "accent-neutral-900", + "accent-neutral-950", + "accent-stone-50", + "accent-stone-100", + "accent-stone-200", + "accent-stone-300", + "accent-stone-400", + "accent-stone-500", + "accent-stone-600", + "accent-stone-700", + "accent-stone-800", + "accent-stone-900", + "accent-stone-950", + "accent-red-50", + "accent-red-100", + "accent-red-200", + "accent-red-300", + "accent-red-400", + "accent-red-500", + "accent-red-600", + "accent-red-700", + "accent-red-800", + "accent-red-900", + "accent-red-950", + "accent-orange-50", + "accent-orange-100", + "accent-orange-200", + "accent-orange-300", + "accent-orange-400", + "accent-orange-500", + "accent-orange-600", + "accent-orange-700", + "accent-orange-800", + "accent-orange-900", + "accent-orange-950", + "accent-amber-50", + "accent-amber-100", + "accent-amber-200", + "accent-amber-300", + "accent-amber-400", + "accent-amber-500", + "accent-amber-600", + "accent-amber-700", + "accent-amber-800", + "accent-amber-900", + "accent-amber-950", + "accent-yellow-50", + "accent-yellow-100", + "accent-yellow-200", + "accent-yellow-300", + "accent-yellow-400", + "accent-yellow-500", + "accent-yellow-600", + "accent-yellow-700", + "accent-yellow-800", + "accent-yellow-900", + "accent-yellow-950", + "accent-lime-50", + "accent-lime-100", + "accent-lime-200", + "accent-lime-300", + "accent-lime-400", + "accent-lime-500", + "accent-lime-600", + "accent-lime-700", + "accent-lime-800", + "accent-lime-900", + "accent-lime-950", + "accent-green-50", + "accent-green-100", + "accent-green-200", + "accent-green-300", + "accent-green-400", + "accent-green-500", + "accent-green-600", + "accent-green-700", + "accent-green-800", + "accent-green-900", + "accent-green-950", + "accent-emerald-50", + "accent-emerald-100", + "accent-emerald-200", + "accent-emerald-300", + "accent-emerald-400", + "accent-emerald-500", + "accent-emerald-600", + "accent-emerald-700", + "accent-emerald-800", + "accent-emerald-900", + "accent-emerald-950", + "accent-teal-50", + "accent-teal-100", + "accent-teal-200", + "accent-teal-300", + "accent-teal-400", + "accent-teal-500", + "accent-teal-600", + "accent-teal-700", + "accent-teal-800", + "accent-teal-900", + "accent-teal-950", + "accent-cyan-50", + "accent-cyan-100", + "accent-cyan-200", + "accent-cyan-300", + "accent-cyan-400", + "accent-cyan-500", + "accent-cyan-600", + "accent-cyan-700", + "accent-cyan-800", + "accent-cyan-900", + "accent-cyan-950", + "accent-sky-50", + "accent-sky-100", + "accent-sky-200", + "accent-sky-300", + "accent-sky-400", + "accent-sky-500", + "accent-sky-600", + "accent-sky-700", + "accent-sky-800", + "accent-sky-900", + "accent-sky-950", + "accent-blue-50", + "accent-blue-100", + "accent-blue-200", + "accent-blue-300", + "accent-blue-400", + "accent-blue-500", + "accent-blue-600", + "accent-blue-700", + "accent-blue-800", + "accent-blue-900", + "accent-blue-950", + "accent-indigo-50", + "accent-indigo-100", + "accent-indigo-200", + "accent-indigo-300", + "accent-indigo-400", + "accent-indigo-500", + "accent-indigo-600", + "accent-indigo-700", + "accent-indigo-800", + "accent-indigo-900", + "accent-indigo-950", + "accent-violet-50", + "accent-violet-100", + "accent-violet-200", + "accent-violet-300", + "accent-violet-400", + "accent-violet-500", + "accent-violet-600", + "accent-violet-700", + "accent-violet-800", + "accent-violet-900", + "accent-violet-950", + "accent-purple-50", + "accent-purple-100", + "accent-purple-200", + "accent-purple-300", + "accent-purple-400", + "accent-purple-500", + "accent-purple-600", + "accent-purple-700", + "accent-purple-800", + "accent-purple-900", + "accent-purple-950", + "accent-fuchsia-50", + "accent-fuchsia-100", + "accent-fuchsia-200", + "accent-fuchsia-300", + "accent-fuchsia-400", + "accent-fuchsia-500", + "accent-fuchsia-600", + "accent-fuchsia-700", + "accent-fuchsia-800", + "accent-fuchsia-900", + "accent-fuchsia-950", + "accent-pink-50", + "accent-pink-100", + "accent-pink-200", + "accent-pink-300", + "accent-pink-400", + "accent-pink-500", + "accent-pink-600", + "accent-pink-700", + "accent-pink-800", + "accent-pink-900", + "accent-pink-950", + "accent-rose-50", + "accent-rose-100", + "accent-rose-200", + "accent-rose-300", + "accent-rose-400", + "accent-rose-500", + "accent-rose-600", + "accent-rose-700", + "accent-rose-800", + "accent-rose-900", + "accent-rose-950", + "accent-auto", + "appearance-none", + "appearance-auto", + "cursor-auto", + "cursor-default", + "cursor-pointer", + "cursor-wait", + "cursor-text", + "cursor-move", + "cursor-help", + "cursor-not-allowed", + "cursor-none", + "cursor-context-menu", + "cursor-progress", + "cursor-cell", + "cursor-crosshair", + "cursor-vertical-text", + "cursor-alias", + "cursor-copy", + "cursor-no-drop", + "cursor-grab", + "cursor-grabbing", + "cursor-all-scroll", + "cursor-col-resize", + "cursor-row-resize", + "cursor-n-resize", + "cursor-e-resize", + "cursor-s-resize", + "cursor-w-resize", + "cursor-ne-resize", + "cursor-nw-resize", + "cursor-se-resize", + "cursor-sw-resize", + "cursor-ew-resize", + "cursor-ns-resize", + "cursor-nesw-resize", + "cursor-nwse-resize", + "cursor-zoom-in", + "cursor-zoom-out", + "caret-inherit", + "caret-current", + "caret-transparent", + "caret-black", + "caret-white", + "caret-slate-50", + "caret-slate-100", + "caret-slate-200", + "caret-slate-300", + "caret-slate-400", + "caret-slate-500", + "caret-slate-600", + "caret-slate-700", + "caret-slate-800", + "caret-slate-900", + "caret-slate-950", + "caret-gray-50", + "caret-gray-100", + "caret-gray-200", + "caret-gray-300", + "caret-gray-400", + "caret-gray-500", + "caret-gray-600", + "caret-gray-700", + "caret-gray-800", + "caret-gray-900", + "caret-gray-950", + "caret-zinc-50", + "caret-zinc-100", + "caret-zinc-200", + "caret-zinc-300", + "caret-zinc-400", + "caret-zinc-500", + "caret-zinc-600", + "caret-zinc-700", + "caret-zinc-800", + "caret-zinc-900", + "caret-zinc-950", + "caret-neutral-50", + "caret-neutral-100", + "caret-neutral-200", + "caret-neutral-300", + "caret-neutral-400", + "caret-neutral-500", + "caret-neutral-600", + "caret-neutral-700", + "caret-neutral-800", + "caret-neutral-900", + "caret-neutral-950", + "caret-stone-50", + "caret-stone-100", + "caret-stone-200", + "caret-stone-300", + "caret-stone-400", + "caret-stone-500", + "caret-stone-600", + "caret-stone-700", + "caret-stone-800", + "caret-stone-900", + "caret-stone-950", + "caret-red-50", + "caret-red-100", + "caret-red-200", + "caret-red-300", + "caret-red-400", + "caret-red-500", + "caret-red-600", + "caret-red-700", + "caret-red-800", + "caret-red-900", + "caret-red-950", + "caret-orange-50", + "caret-orange-100", + "caret-orange-200", + "caret-orange-300", + "caret-orange-400", + "caret-orange-500", + "caret-orange-600", + "caret-orange-700", + "caret-orange-800", + "caret-orange-900", + "caret-orange-950", + "caret-amber-50", + "caret-amber-100", + "caret-amber-200", + "caret-amber-300", + "caret-amber-400", + "caret-amber-500", + "caret-amber-600", + "caret-amber-700", + "caret-amber-800", + "caret-amber-900", + "caret-amber-950", + "caret-yellow-50", + "caret-yellow-100", + "caret-yellow-200", + "caret-yellow-300", + "caret-yellow-400", + "caret-yellow-500", + "caret-yellow-600", + "caret-yellow-700", + "caret-yellow-800", + "caret-yellow-900", + "caret-yellow-950", + "caret-lime-50", + "caret-lime-100", + "caret-lime-200", + "caret-lime-300", + "caret-lime-400", + "caret-lime-500", + "caret-lime-600", + "caret-lime-700", + "caret-lime-800", + "caret-lime-900", + "caret-lime-950", + "caret-green-50", + "caret-green-100", + "caret-green-200", + "caret-green-300", + "caret-green-400", + "caret-green-500", + "caret-green-600", + "caret-green-700", + "caret-green-800", + "caret-green-900", + "caret-green-950", + "caret-emerald-50", + "caret-emerald-100", + "caret-emerald-200", + "caret-emerald-300", + "caret-emerald-400", + "caret-emerald-500", + "caret-emerald-600", + "caret-emerald-700", + "caret-emerald-800", + "caret-emerald-900", + "caret-emerald-950", + "caret-teal-50", + "caret-teal-100", + "caret-teal-200", + "caret-teal-300", + "caret-teal-400", + "caret-teal-500", + "caret-teal-600", + "caret-teal-700", + "caret-teal-800", + "caret-teal-900", + "caret-teal-950", + "caret-cyan-50", + "caret-cyan-100", + "caret-cyan-200", + "caret-cyan-300", + "caret-cyan-400", + "caret-cyan-500", + "caret-cyan-600", + "caret-cyan-700", + "caret-cyan-800", + "caret-cyan-900", + "caret-cyan-950", + "caret-sky-50", + "caret-sky-100", + "caret-sky-200", + "caret-sky-300", + "caret-sky-400", + "caret-sky-500", + "caret-sky-600", + "caret-sky-700", + "caret-sky-800", + "caret-sky-900", + "caret-sky-950", + "caret-blue-50", + "caret-blue-100", + "caret-blue-200", + "caret-blue-300", + "caret-blue-400", + "caret-blue-500", + "caret-blue-600", + "caret-blue-700", + "caret-blue-800", + "caret-blue-900", + "caret-blue-950", + "caret-indigo-50", + "caret-indigo-100", + "caret-indigo-200", + "caret-indigo-300", + "caret-indigo-400", + "caret-indigo-500", + "caret-indigo-600", + "caret-indigo-700", + "caret-indigo-800", + "caret-indigo-900", + "caret-indigo-950", + "caret-violet-50", + "caret-violet-100", + "caret-violet-200", + "caret-violet-300", + "caret-violet-400", + "caret-violet-500", + "caret-violet-600", + "caret-violet-700", + "caret-violet-800", + "caret-violet-900", + "caret-violet-950", + "caret-purple-50", + "caret-purple-100", + "caret-purple-200", + "caret-purple-300", + "caret-purple-400", + "caret-purple-500", + "caret-purple-600", + "caret-purple-700", + "caret-purple-800", + "caret-purple-900", + "caret-purple-950", + "caret-fuchsia-50", + "caret-fuchsia-100", + "caret-fuchsia-200", + "caret-fuchsia-300", + "caret-fuchsia-400", + "caret-fuchsia-500", + "caret-fuchsia-600", + "caret-fuchsia-700", + "caret-fuchsia-800", + "caret-fuchsia-900", + "caret-fuchsia-950", + "caret-pink-50", + "caret-pink-100", + "caret-pink-200", + "caret-pink-300", + "caret-pink-400", + "caret-pink-500", + "caret-pink-600", + "caret-pink-700", + "caret-pink-800", + "caret-pink-900", + "caret-pink-950", + "caret-rose-50", + "caret-rose-100", + "caret-rose-200", + "caret-rose-300", + "caret-rose-400", + "caret-rose-500", + "caret-rose-600", + "caret-rose-700", + "caret-rose-800", + "caret-rose-900", + "caret-rose-950", + "pointer-events-none", + "pointer-events-auto", + "resize-none", + "resize-y", + "resize-x", + "resize", + "scroll-auto", + "scroll-smooth", + "scroll-m-0", + "scroll-mx-0", + "scroll-my-0", + "scroll-ms-0", + "scroll-me-0", + "scroll-mt-0", + "scroll-mr-0", + "scroll-mb-0", + "scroll-ml-0", + "scroll-m-px", + "scroll-mx-px", + "scroll-my-px", + "scroll-ms-px", + "scroll-me-px", + "scroll-mt-px", + "scroll-mr-px", + "scroll-mb-px", + "scroll-ml-px", + "scroll-m-0.5", + "scroll-mx-0.5", + "scroll-my-0.5", + "scroll-ms-0.5", + "scroll-me-0.5", + "scroll-mt-0.5", + "scroll-mr-0.5", + "scroll-mb-0.5", + "scroll-ml-0.5", + "scroll-m-1", + "scroll-mx-1", + "scroll-my-1", + "scroll-ms-1", + "scroll-me-1", + "scroll-mt-1", + "scroll-mr-1", + "scroll-mb-1", + "scroll-ml-1", + "scroll-m-1.5", + "scroll-mx-1.5", + "scroll-my-1.5", + "scroll-ms-1.5", + "scroll-me-1.5", + "scroll-mt-1.5", + "scroll-mr-1.5", + "scroll-mb-1.5", + "scroll-ml-1.5", + "scroll-m-2", + "scroll-mx-2", + "scroll-my-2", + "scroll-ms-2", + "scroll-me-2", + "scroll-mt-2", + "scroll-mr-2", + "scroll-mb-2", + "scroll-ml-2", + "scroll-m-2.5", + "scroll-mx-2.5", + "scroll-my-2.5", + "scroll-ms-2.5", + "scroll-me-2.5", + "scroll-mt-2.5", + "scroll-mr-2.5", + "scroll-mb-2.5", + "scroll-ml-2.5", + "scroll-m-3", + "scroll-mx-3", + "scroll-my-3", + "scroll-ms-3", + "scroll-me-3", + "scroll-mt-3", + "scroll-mr-3", + "scroll-mb-3", + "scroll-ml-3", + "scroll-m-3.5", + "scroll-mx-3.5", + "scroll-my-3.5", + "scroll-ms-3.5", + "scroll-me-3.5", + "scroll-mt-3.5", + "scroll-mr-3.5", + "scroll-mb-3.5", + "scroll-ml-3.5", + "scroll-m-4", + "scroll-mx-4", + "scroll-my-4", + "scroll-ms-4", + "scroll-me-4", + "scroll-mt-4", + "scroll-mr-4", + "scroll-mb-4", + "scroll-ml-4", + "scroll-m-5", + "scroll-mx-5", + "scroll-my-5", + "scroll-ms-5", + "scroll-me-5", + "scroll-mt-5", + "scroll-mr-5", + "scroll-mb-5", + "scroll-ml-5", + "scroll-m-6", + "scroll-mx-6", + "scroll-my-6", + "scroll-ms-6", + "scroll-me-6", + "scroll-mt-6", + "scroll-mr-6", + "scroll-mb-6", + "scroll-ml-6", + "scroll-m-7", + "scroll-mx-7", + "scroll-my-7", + "scroll-ms-7", + "scroll-me-7", + "scroll-mt-7", + "scroll-mr-7", + "scroll-mb-7", + "scroll-ml-7", + "scroll-m-8", + "scroll-mx-8", + "scroll-my-8", + "scroll-ms-8", + "scroll-me-8", + "scroll-mt-8", + "scroll-mr-8", + "scroll-mb-8", + "scroll-ml-8", + "scroll-m-9", + "scroll-mx-9", + "scroll-my-9", + "scroll-ms-9", + "scroll-me-9", + "scroll-mt-9", + "scroll-mr-9", + "scroll-mb-9", + "scroll-ml-9", + "scroll-m-10", + "scroll-mx-10", + "scroll-my-10", + "scroll-ms-10", + "scroll-me-10", + "scroll-mt-10", + "scroll-mr-10", + "scroll-mb-10", + "scroll-ml-10", + "scroll-m-11", + "scroll-mx-11", + "scroll-my-11", + "scroll-ms-11", + "scroll-me-11", + "scroll-mt-11", + "scroll-mr-11", + "scroll-mb-11", + "scroll-ml-11", + "scroll-m-12", + "scroll-mx-12", + "scroll-my-12", + "scroll-ms-12", + "scroll-me-12", + "scroll-mt-12", + "scroll-mr-12", + "scroll-mb-12", + "scroll-ml-12", + "scroll-m-14", + "scroll-mx-14", + "scroll-my-14", + "scroll-ms-14", + "scroll-me-14", + "scroll-mt-14", + "scroll-mr-14", + "scroll-mb-14", + "scroll-ml-14", + "scroll-m-16", + "scroll-mx-16", + "scroll-my-16", + "scroll-ms-16", + "scroll-me-16", + "scroll-mt-16", + "scroll-mr-16", + "scroll-mb-16", + "scroll-ml-16", + "scroll-m-20", + "scroll-mx-20", + "scroll-my-20", + "scroll-ms-20", + "scroll-me-20", + "scroll-mt-20", + "scroll-mr-20", + "scroll-mb-20", + "scroll-ml-20", + "scroll-m-24", + "scroll-mx-24", + "scroll-my-24", + "scroll-ms-24", + "scroll-me-24", + "scroll-mt-24", + "scroll-mr-24", + "scroll-mb-24", + "scroll-ml-24", + "scroll-m-28", + "scroll-mx-28", + "scroll-my-28", + "scroll-ms-28", + "scroll-me-28", + "scroll-mt-28", + "scroll-mr-28", + "scroll-mb-28", + "scroll-ml-28", + "scroll-m-32", + "scroll-mx-32", + "scroll-my-32", + "scroll-ms-32", + "scroll-me-32", + "scroll-mt-32", + "scroll-mr-32", + "scroll-mb-32", + "scroll-ml-32", + "scroll-m-36", + "scroll-mx-36", + "scroll-my-36", + "scroll-ms-36", + "scroll-me-36", + "scroll-mt-36", + "scroll-mr-36", + "scroll-mb-36", + "scroll-ml-36", + "scroll-m-40", + "scroll-mx-40", + "scroll-my-40", + "scroll-ms-40", + "scroll-me-40", + "scroll-mt-40", + "scroll-mr-40", + "scroll-mb-40", + "scroll-ml-40", + "scroll-m-44", + "scroll-mx-44", + "scroll-my-44", + "scroll-ms-44", + "scroll-me-44", + "scroll-mt-44", + "scroll-mr-44", + "scroll-mb-44", + "scroll-ml-44", + "scroll-m-48", + "scroll-mx-48", + "scroll-my-48", + "scroll-ms-48", + "scroll-me-48", + "scroll-mt-48", + "scroll-mr-48", + "scroll-mb-48", + "scroll-ml-48", + "scroll-m-52", + "scroll-mx-52", + "scroll-my-52", + "scroll-ms-52", + "scroll-me-52", + "scroll-mt-52", + "scroll-mr-52", + "scroll-mb-52", + "scroll-ml-52", + "scroll-m-56", + "scroll-mx-56", + "scroll-my-56", + "scroll-ms-56", + "scroll-me-56", + "scroll-mt-56", + "scroll-mr-56", + "scroll-mb-56", + "scroll-ml-56", + "scroll-m-60", + "scroll-mx-60", + "scroll-my-60", + "scroll-ms-60", + "scroll-me-60", + "scroll-mt-60", + "scroll-mr-60", + "scroll-mb-60", + "scroll-ml-60", + "scroll-m-64", + "scroll-mx-64", + "scroll-my-64", + "scroll-ms-64", + "scroll-me-64", + "scroll-mt-64", + "scroll-mr-64", + "scroll-mb-64", + "scroll-ml-64", + "scroll-m-72", + "scroll-mx-72", + "scroll-my-72", + "scroll-ms-72", + "scroll-me-72", + "scroll-mt-72", + "scroll-mr-72", + "scroll-mb-72", + "scroll-ml-72", + "scroll-m-80", + "scroll-mx-80", + "scroll-my-80", + "scroll-ms-80", + "scroll-me-80", + "scroll-mt-80", + "scroll-mr-80", + "scroll-mb-80", + "scroll-ml-80", + "scroll-m-96", + "scroll-mx-96", + "scroll-my-96", + "scroll-ms-96", + "scroll-me-96", + "scroll-mt-96", + "scroll-mr-96", + "scroll-mb-96", + "scroll-ml-96", + "scroll-p-0", + "scroll-px-0", + "scroll-py-0", + "scroll-ps-0", + "scroll-pe-0", + "scroll-pt-0", + "scroll-pr-0", + "scroll-pb-0", + "scroll-pl-0", + "scroll-p-px", + "scroll-px-px", + "scroll-py-px", + "scroll-ps-px", + "scroll-pe-px", + "scroll-pt-px", + "scroll-pr-px", + "scroll-pb-px", + "scroll-pl-px", + "scroll-p-0.5", + "scroll-px-0.5", + "scroll-py-0.5", + "scroll-ps-0.5", + "scroll-pe-0.5", + "scroll-pt-0.5", + "scroll-pr-0.5", + "scroll-pb-0.5", + "scroll-pl-0.5", + "scroll-p-1", + "scroll-px-1", + "scroll-py-1", + "scroll-ps-1", + "scroll-pe-1", + "scroll-pt-1", + "scroll-pr-1", + "scroll-pb-1", + "scroll-pl-1", + "scroll-p-1.5", + "scroll-px-1.5", + "scroll-py-1.5", + "scroll-ps-1.5", + "scroll-pe-1.5", + "scroll-pt-1.5", + "scroll-pr-1.5", + "scroll-pb-1.5", + "scroll-pl-1.5", + "scroll-p-2", + "scroll-px-2", + "scroll-py-2", + "scroll-ps-2", + "scroll-pe-2", + "scroll-pt-2", + "scroll-pr-2", + "scroll-pb-2", + "scroll-pl-2", + "scroll-p-2.5", + "scroll-px-2.5", + "scroll-py-2.5", + "scroll-ps-2.5", + "scroll-pe-2.5", + "scroll-pt-2.5", + "scroll-pr-2.5", + "scroll-pb-2.5", + "scroll-pl-2.5", + "scroll-p-3", + "scroll-px-3", + "scroll-py-3", + "scroll-ps-3", + "scroll-pe-3", + "scroll-pt-3", + "scroll-pr-3", + "scroll-pb-3", + "scroll-pl-3", + "scroll-p-3.5", + "scroll-px-3.5", + "scroll-py-3.5", + "scroll-ps-3.5", + "scroll-pe-3.5", + "scroll-pt-3.5", + "scroll-pr-3.5", + "scroll-pb-3.5", + "scroll-pl-3.5", + "scroll-p-4", + "scroll-px-4", + "scroll-py-4", + "scroll-ps-4", + "scroll-pe-4", + "scroll-pt-4", + "scroll-pr-4", + "scroll-pb-4", + "scroll-pl-4", + "scroll-p-5", + "scroll-px-5", + "scroll-py-5", + "scroll-ps-5", + "scroll-pe-5", + "scroll-pt-5", + "scroll-pr-5", + "scroll-pb-5", + "scroll-pl-5", + "scroll-p-6", + "scroll-px-6", + "scroll-py-6", + "scroll-ps-6", + "scroll-pe-6", + "scroll-pt-6", + "scroll-pr-6", + "scroll-pb-6", + "scroll-pl-6", + "scroll-p-7", + "scroll-px-7", + "scroll-py-7", + "scroll-ps-7", + "scroll-pe-7", + "scroll-pt-7", + "scroll-pr-7", + "scroll-pb-7", + "scroll-pl-7", + "scroll-p-8", + "scroll-px-8", + "scroll-py-8", + "scroll-ps-8", + "scroll-pe-8", + "scroll-pt-8", + "scroll-pr-8", + "scroll-pb-8", + "scroll-pl-8", + "scroll-p-9", + "scroll-px-9", + "scroll-py-9", + "scroll-ps-9", + "scroll-pe-9", + "scroll-pt-9", + "scroll-pr-9", + "scroll-pb-9", + "scroll-pl-9", + "scroll-p-10", + "scroll-px-10", + "scroll-py-10", + "scroll-ps-10", + "scroll-pe-10", + "scroll-pt-10", + "scroll-pr-10", + "scroll-pb-10", + "scroll-pl-10", + "scroll-p-11", + "scroll-px-11", + "scroll-py-11", + "scroll-ps-11", + "scroll-pe-11", + "scroll-pt-11", + "scroll-pr-11", + "scroll-pb-11", + "scroll-pl-11", + "scroll-p-12", + "scroll-px-12", + "scroll-py-12", + "scroll-ps-12", + "scroll-pe-12", + "scroll-pt-12", + "scroll-pr-12", + "scroll-pb-12", + "scroll-pl-12", + "scroll-p-14", + "scroll-px-14", + "scroll-py-14", + "scroll-ps-14", + "scroll-pe-14", + "scroll-pt-14", + "scroll-pr-14", + "scroll-pb-14", + "scroll-pl-14", + "scroll-p-16", + "scroll-px-16", + "scroll-py-16", + "scroll-ps-16", + "scroll-pe-16", + "scroll-pt-16", + "scroll-pr-16", + "scroll-pb-16", + "scroll-pl-16", + "scroll-p-20", + "scroll-px-20", + "scroll-py-20", + "scroll-ps-20", + "scroll-pe-20", + "scroll-pt-20", + "scroll-pr-20", + "scroll-pb-20", + "scroll-pl-20", + "scroll-p-24", + "scroll-px-24", + "scroll-py-24", + "scroll-ps-24", + "scroll-pe-24", + "scroll-pt-24", + "scroll-pr-24", + "scroll-pb-24", + "scroll-pl-24", + "scroll-p-28", + "scroll-px-28", + "scroll-py-28", + "scroll-ps-28", + "scroll-pe-28", + "scroll-pt-28", + "scroll-pr-28", + "scroll-pb-28", + "scroll-pl-28", + "scroll-p-32", + "scroll-px-32", + "scroll-py-32", + "scroll-ps-32", + "scroll-pe-32", + "scroll-pt-32", + "scroll-pr-32", + "scroll-pb-32", + "scroll-pl-32", + "scroll-p-36", + "scroll-px-36", + "scroll-py-36", + "scroll-ps-36", + "scroll-pe-36", + "scroll-pt-36", + "scroll-pr-36", + "scroll-pb-36", + "scroll-pl-36", + "scroll-p-40", + "scroll-px-40", + "scroll-py-40", + "scroll-ps-40", + "scroll-pe-40", + "scroll-pt-40", + "scroll-pr-40", + "scroll-pb-40", + "scroll-pl-40", + "scroll-p-44", + "scroll-px-44", + "scroll-py-44", + "scroll-ps-44", + "scroll-pe-44", + "scroll-pt-44", + "scroll-pr-44", + "scroll-pb-44", + "scroll-pl-44", + "scroll-p-48", + "scroll-px-48", + "scroll-py-48", + "scroll-ps-48", + "scroll-pe-48", + "scroll-pt-48", + "scroll-pr-48", + "scroll-pb-48", + "scroll-pl-48", + "scroll-p-52", + "scroll-px-52", + "scroll-py-52", + "scroll-ps-52", + "scroll-pe-52", + "scroll-pt-52", + "scroll-pr-52", + "scroll-pb-52", + "scroll-pl-52", + "scroll-p-56", + "scroll-px-56", + "scroll-py-56", + "scroll-ps-56", + "scroll-pe-56", + "scroll-pt-56", + "scroll-pr-56", + "scroll-pb-56", + "scroll-pl-56", + "scroll-p-60", + "scroll-px-60", + "scroll-py-60", + "scroll-ps-60", + "scroll-pe-60", + "scroll-pt-60", + "scroll-pr-60", + "scroll-pb-60", + "scroll-pl-60", + "scroll-p-64", + "scroll-px-64", + "scroll-py-64", + "scroll-ps-64", + "scroll-pe-64", + "scroll-pt-64", + "scroll-pr-64", + "scroll-pb-64", + "scroll-pl-64", + "scroll-p-72", + "scroll-px-72", + "scroll-py-72", + "scroll-ps-72", + "scroll-pe-72", + "scroll-pt-72", + "scroll-pr-72", + "scroll-pb-72", + "scroll-pl-72", + "scroll-p-80", + "scroll-px-80", + "scroll-py-80", + "scroll-ps-80", + "scroll-pe-80", + "scroll-pt-80", + "scroll-pr-80", + "scroll-pb-80", + "scroll-pl-80", + "scroll-p-96", + "scroll-px-96", + "scroll-py-96", + "scroll-ps-96", + "scroll-pe-96", + "scroll-pt-96", + "scroll-pr-96", + "scroll-pb-96", + "scroll-pl-96", + "snap-start", + "snap-end", + "snap-center", + "snap-align-none", + "snap-normal", + "snap-always", + "snap-none", + "snap-x", + "snap-y", + "snap-both", + "snap-mandatory", + "snap-proximity", + "touch-auto", + "touch-none", + "touch-pan-x", + "touch-pan-left", + "touch-pan-right", + "touch-pan-y", + "touch-pan-up", + "touch-pan-down", + "touch-pinch-zoom", + "touch-manipulation", + "select-none", + "select-text", + "select-all", + "select-auto", + "will-change-auto", + "will-change-scroll", + "will-change-contents", + "will-change-transform", + "fill-none", + "fill-inherit", + "fill-current", + "fill-transparent", + "fill-black", + "fill-white", + "fill-slate-50", + "fill-slate-100", + "fill-slate-200", + "fill-slate-300", + "fill-slate-400", + "fill-slate-500", + "fill-slate-600", + "fill-slate-700", + "fill-slate-800", + "fill-slate-900", + "fill-slate-950", + "fill-gray-50", + "fill-gray-100", + "fill-gray-200", + "fill-gray-300", + "fill-gray-400", + "fill-gray-500", + "fill-gray-600", + "fill-gray-700", + "fill-gray-800", + "fill-gray-900", + "fill-gray-950", + "fill-zinc-50", + "fill-zinc-100", + "fill-zinc-200", + "fill-zinc-300", + "fill-zinc-400", + "fill-zinc-500", + "fill-zinc-600", + "fill-zinc-700", + "fill-zinc-800", + "fill-zinc-900", + "fill-zinc-950", + "fill-neutral-50", + "fill-neutral-100", + "fill-neutral-200", + "fill-neutral-300", + "fill-neutral-400", + "fill-neutral-500", + "fill-neutral-600", + "fill-neutral-700", + "fill-neutral-800", + "fill-neutral-900", + "fill-neutral-950", + "fill-stone-50", + "fill-stone-100", + "fill-stone-200", + "fill-stone-300", + "fill-stone-400", + "fill-stone-500", + "fill-stone-600", + "fill-stone-700", + "fill-stone-800", + "fill-stone-900", + "fill-stone-950", + "fill-red-50", + "fill-red-100", + "fill-red-200", + "fill-red-300", + "fill-red-400", + "fill-red-500", + "fill-red-600", + "fill-red-700", + "fill-red-800", + "fill-red-900", + "fill-red-950", + "fill-orange-50", + "fill-orange-100", + "fill-orange-200", + "fill-orange-300", + "fill-orange-400", + "fill-orange-500", + "fill-orange-600", + "fill-orange-700", + "fill-orange-800", + "fill-orange-900", + "fill-orange-950", + "fill-amber-50", + "fill-amber-100", + "fill-amber-200", + "fill-amber-300", + "fill-amber-400", + "fill-amber-500", + "fill-amber-600", + "fill-amber-700", + "fill-amber-800", + "fill-amber-900", + "fill-amber-950", + "fill-yellow-50", + "fill-yellow-100", + "fill-yellow-200", + "fill-yellow-300", + "fill-yellow-400", + "fill-yellow-500", + "fill-yellow-600", + "fill-yellow-700", + "fill-yellow-800", + "fill-yellow-900", + "fill-yellow-950", + "fill-lime-50", + "fill-lime-100", + "fill-lime-200", + "fill-lime-300", + "fill-lime-400", + "fill-lime-500", + "fill-lime-600", + "fill-lime-700", + "fill-lime-800", + "fill-lime-900", + "fill-lime-950", + "fill-green-50", + "fill-green-100", + "fill-green-200", + "fill-green-300", + "fill-green-400", + "fill-green-500", + "fill-green-600", + "fill-green-700", + "fill-green-800", + "fill-green-900", + "fill-green-950", + "fill-emerald-50", + "fill-emerald-100", + "fill-emerald-200", + "fill-emerald-300", + "fill-emerald-400", + "fill-emerald-500", + "fill-emerald-600", + "fill-emerald-700", + "fill-emerald-800", + "fill-emerald-900", + "fill-emerald-950", + "fill-teal-50", + "fill-teal-100", + "fill-teal-200", + "fill-teal-300", + "fill-teal-400", + "fill-teal-500", + "fill-teal-600", + "fill-teal-700", + "fill-teal-800", + "fill-teal-900", + "fill-teal-950", + "fill-cyan-50", + "fill-cyan-100", + "fill-cyan-200", + "fill-cyan-300", + "fill-cyan-400", + "fill-cyan-500", + "fill-cyan-600", + "fill-cyan-700", + "fill-cyan-800", + "fill-cyan-900", + "fill-cyan-950", + "fill-sky-50", + "fill-sky-100", + "fill-sky-200", + "fill-sky-300", + "fill-sky-400", + "fill-sky-500", + "fill-sky-600", + "fill-sky-700", + "fill-sky-800", + "fill-sky-900", + "fill-sky-950", + "fill-blue-50", + "fill-blue-100", + "fill-blue-200", + "fill-blue-300", + "fill-blue-400", + "fill-blue-500", + "fill-blue-600", + "fill-blue-700", + "fill-blue-800", + "fill-blue-900", + "fill-blue-950", + "fill-indigo-50", + "fill-indigo-100", + "fill-indigo-200", + "fill-indigo-300", + "fill-indigo-400", + "fill-indigo-500", + "fill-indigo-600", + "fill-indigo-700", + "fill-indigo-800", + "fill-indigo-900", + "fill-indigo-950", + "fill-violet-50", + "fill-violet-100", + "fill-violet-200", + "fill-violet-300", + "fill-violet-400", + "fill-violet-500", + "fill-violet-600", + "fill-violet-700", + "fill-violet-800", + "fill-violet-900", + "fill-violet-950", + "fill-purple-50", + "fill-purple-100", + "fill-purple-200", + "fill-purple-300", + "fill-purple-400", + "fill-purple-500", + "fill-purple-600", + "fill-purple-700", + "fill-purple-800", + "fill-purple-900", + "fill-purple-950", + "fill-fuchsia-50", + "fill-fuchsia-100", + "fill-fuchsia-200", + "fill-fuchsia-300", + "fill-fuchsia-400", + "fill-fuchsia-500", + "fill-fuchsia-600", + "fill-fuchsia-700", + "fill-fuchsia-800", + "fill-fuchsia-900", + "fill-fuchsia-950", + "fill-pink-50", + "fill-pink-100", + "fill-pink-200", + "fill-pink-300", + "fill-pink-400", + "fill-pink-500", + "fill-pink-600", + "fill-pink-700", + "fill-pink-800", + "fill-pink-900", + "fill-pink-950", + "fill-rose-50", + "fill-rose-100", + "fill-rose-200", + "fill-rose-300", + "fill-rose-400", + "fill-rose-500", + "fill-rose-600", + "fill-rose-700", + "fill-rose-800", + "fill-rose-900", + "fill-rose-950", + "stroke-none", + "stroke-inherit", + "stroke-current", + "stroke-transparent", + "stroke-black", + "stroke-white", + "stroke-slate-50", + "stroke-slate-100", + "stroke-slate-200", + "stroke-slate-300", + "stroke-slate-400", + "stroke-slate-500", + "stroke-slate-600", + "stroke-slate-700", + "stroke-slate-800", + "stroke-slate-900", + "stroke-slate-950", + "stroke-gray-50", + "stroke-gray-100", + "stroke-gray-200", + "stroke-gray-300", + "stroke-gray-400", + "stroke-gray-500", + "stroke-gray-600", + "stroke-gray-700", + "stroke-gray-800", + "stroke-gray-900", + "stroke-gray-950", + "stroke-zinc-50", + "stroke-zinc-100", + "stroke-zinc-200", + "stroke-zinc-300", + "stroke-zinc-400", + "stroke-zinc-500", + "stroke-zinc-600", + "stroke-zinc-700", + "stroke-zinc-800", + "stroke-zinc-900", + "stroke-zinc-950", + "stroke-neutral-50", + "stroke-neutral-100", + "stroke-neutral-200", + "stroke-neutral-300", + "stroke-neutral-400", + "stroke-neutral-500", + "stroke-neutral-600", + "stroke-neutral-700", + "stroke-neutral-800", + "stroke-neutral-900", + "stroke-neutral-950", + "stroke-stone-50", + "stroke-stone-100", + "stroke-stone-200", + "stroke-stone-300", + "stroke-stone-400", + "stroke-stone-500", + "stroke-stone-600", + "stroke-stone-700", + "stroke-stone-800", + "stroke-stone-900", + "stroke-stone-950", + "stroke-red-50", + "stroke-red-100", + "stroke-red-200", + "stroke-red-300", + "stroke-red-400", + "stroke-red-500", + "stroke-red-600", + "stroke-red-700", + "stroke-red-800", + "stroke-red-900", + "stroke-red-950", + "stroke-orange-50", + "stroke-orange-100", + "stroke-orange-200", + "stroke-orange-300", + "stroke-orange-400", + "stroke-orange-500", + "stroke-orange-600", + "stroke-orange-700", + "stroke-orange-800", + "stroke-orange-900", + "stroke-orange-950", + "stroke-amber-50", + "stroke-amber-100", + "stroke-amber-200", + "stroke-amber-300", + "stroke-amber-400", + "stroke-amber-500", + "stroke-amber-600", + "stroke-amber-700", + "stroke-amber-800", + "stroke-amber-900", + "stroke-amber-950", + "stroke-yellow-50", + "stroke-yellow-100", + "stroke-yellow-200", + "stroke-yellow-300", + "stroke-yellow-400", + "stroke-yellow-500", + "stroke-yellow-600", + "stroke-yellow-700", + "stroke-yellow-800", + "stroke-yellow-900", + "stroke-yellow-950", + "stroke-lime-50", + "stroke-lime-100", + "stroke-lime-200", + "stroke-lime-300", + "stroke-lime-400", + "stroke-lime-500", + "stroke-lime-600", + "stroke-lime-700", + "stroke-lime-800", + "stroke-lime-900", + "stroke-lime-950", + "stroke-green-50", + "stroke-green-100", + "stroke-green-200", + "stroke-green-300", + "stroke-green-400", + "stroke-green-500", + "stroke-green-600", + "stroke-green-700", + "stroke-green-800", + "stroke-green-900", + "stroke-green-950", + "stroke-emerald-50", + "stroke-emerald-100", + "stroke-emerald-200", + "stroke-emerald-300", + "stroke-emerald-400", + "stroke-emerald-500", + "stroke-emerald-600", + "stroke-emerald-700", + "stroke-emerald-800", + "stroke-emerald-900", + "stroke-emerald-950", + "stroke-teal-50", + "stroke-teal-100", + "stroke-teal-200", + "stroke-teal-300", + "stroke-teal-400", + "stroke-teal-500", + "stroke-teal-600", + "stroke-teal-700", + "stroke-teal-800", + "stroke-teal-900", + "stroke-teal-950", + "stroke-cyan-50", + "stroke-cyan-100", + "stroke-cyan-200", + "stroke-cyan-300", + "stroke-cyan-400", + "stroke-cyan-500", + "stroke-cyan-600", + "stroke-cyan-700", + "stroke-cyan-800", + "stroke-cyan-900", + "stroke-cyan-950", + "stroke-sky-50", + "stroke-sky-100", + "stroke-sky-200", + "stroke-sky-300", + "stroke-sky-400", + "stroke-sky-500", + "stroke-sky-600", + "stroke-sky-700", + "stroke-sky-800", + "stroke-sky-900", + "stroke-sky-950", + "stroke-blue-50", + "stroke-blue-100", + "stroke-blue-200", + "stroke-blue-300", + "stroke-blue-400", + "stroke-blue-500", + "stroke-blue-600", + "stroke-blue-700", + "stroke-blue-800", + "stroke-blue-900", + "stroke-blue-950", + "stroke-indigo-50", + "stroke-indigo-100", + "stroke-indigo-200", + "stroke-indigo-300", + "stroke-indigo-400", + "stroke-indigo-500", + "stroke-indigo-600", + "stroke-indigo-700", + "stroke-indigo-800", + "stroke-indigo-900", + "stroke-indigo-950", + "stroke-violet-50", + "stroke-violet-100", + "stroke-violet-200", + "stroke-violet-300", + "stroke-violet-400", + "stroke-violet-500", + "stroke-violet-600", + "stroke-violet-700", + "stroke-violet-800", + "stroke-violet-900", + "stroke-violet-950", + "stroke-purple-50", + "stroke-purple-100", + "stroke-purple-200", + "stroke-purple-300", + "stroke-purple-400", + "stroke-purple-500", + "stroke-purple-600", + "stroke-purple-700", + "stroke-purple-800", + "stroke-purple-900", + "stroke-purple-950", + "stroke-fuchsia-50", + "stroke-fuchsia-100", + "stroke-fuchsia-200", + "stroke-fuchsia-300", + "stroke-fuchsia-400", + "stroke-fuchsia-500", + "stroke-fuchsia-600", + "stroke-fuchsia-700", + "stroke-fuchsia-800", + "stroke-fuchsia-900", + "stroke-fuchsia-950", + "stroke-pink-50", + "stroke-pink-100", + "stroke-pink-200", + "stroke-pink-300", + "stroke-pink-400", + "stroke-pink-500", + "stroke-pink-600", + "stroke-pink-700", + "stroke-pink-800", + "stroke-pink-900", + "stroke-pink-950", + "stroke-rose-50", + "stroke-rose-100", + "stroke-rose-200", + "stroke-rose-300", + "stroke-rose-400", + "stroke-rose-500", + "stroke-rose-600", + "stroke-rose-700", + "stroke-rose-800", + "stroke-rose-900", + "stroke-rose-950", + "stroke-0", + "stroke-1", + "stroke-2", + "sr-only", + "not-sr-only", + "forced-color-adjust-auto", + "forced-color-adjust-none", + "transform", + "transform-none", + "transform-gpu", + "transform-cpu", + "group", + "peer" +]); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d5b6d57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "node16", + + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "build", + "sourceMap": true, + "strict": true, + "allowJs": true, + "target": "es2022", + "lib": ["es2022"], + "skipLibCheck": true, + "useDefineForClassFields": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "noImplicitOverride": true, + }, + "compileOnSave": true, + "include": ["src"] +}