diff --git a/.editorconfig b/.editorconfig index ee8bbd1..d442aac 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,3 +6,6 @@ indent_style = space indent_size = 2 charset = utf-8 +[Makefile] +indent_style = tab + diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 5eb38dc..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @gorillamoe @Grueslayer @treywood diff --git a/.github/workflows/conform-docs.yml b/.github/workflows/conform-docs.yml new file mode 100644 index 0000000..f5605f3 --- /dev/null +++ b/.github/workflows/conform-docs.yml @@ -0,0 +1,21 @@ +--- +name: Conform Docs + +on: + pull_request: + paths: + - 'docs/**/*.md' + branches: + - main + +jobs: + lint: + name: Lint Docs + runs-on: ubuntu-latest + container: + image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest + steps: + - uses: actions/checkout@v4 + + - name: Run linter + run: ./scripts/lint.sh check-docs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70a4dc3..a82511e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,23 +1,19 @@ --- -name: Test +name: Tests on: pull_request: + paths: + - '**/*.lua' branches: - main jobs: - tests: - name: Tests - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - container-image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest + test-linux: + name: Test Code on Linux + runs-on: ubuntu-latest container: - image: ${{ matrix.container-image }} + image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest steps: - uses: actions/checkout@v4 - name: Restore cache @@ -25,7 +21,48 @@ jobs: with: path: | .tests - key: ${{ runner.os }}-${{ matrix.container-image }} - + key: ${{ runner.os }}-tests - name: Run tests run: ./scripts/tests.sh run + lint: + name: Lint Code + runs-on: ubuntu-latest + container: + image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest + steps: + - uses: actions/checkout@v4 + + - name: Run linter + run: ./scripts/lint.sh check-code + test-windows: + name: Test Code on Windows + runs-on: windows-latest + if: ${{ github.event.label.name == 'needs-windows-test' }} + steps: + - uses: actions/checkout@v4 + - name: Restore cache + id: cache-deps + uses: actions/cache@v4 + with: + path: | + .tests + ~\scoop + ~\AppData\Roaming\LJ4W + ~\AppData\Roaming\luarocks + key: ${{ runner.os }}-tests + - name: Check if cache hit + if: steps.cache-deps.outputs.cache-hit == 'true' + run: echo "GH_CACHE_HIT=true" >> $Env:GITHUB_ENV + - name: Install dependencies + run: ./scripts/install-ci-test-requirements.ps1 + - name: Save cache + uses: actions/cache/save@v4 + with: + path: | + .tests + ~\scoop + ~\AppData\Roaming\LJ4W + ~\AppData\Roaming\luarocks + key: ${{ runner.os }}-tests + - name: Run tests + run: ./scripts/tests.ps1 diff --git a/.gitignore b/.gitignore index a9432b7..4c3013c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ node_modules .env http-client.env.json +!/schemas/http-client.env.json *.http *graphql-schema.json /http-examples .tests +.nvim diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..1a89e4b --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @gorillamoe @Grueslayer diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e380ff4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,85 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or +advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community includes using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting an individual maintainer on: + +### GitHub + +- @gorillamoe + +> (at `GitHub username` + `@github.com`). + +### Discord + +- gorillamoe + +All complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [https://contributor-covenant.org/version/1/4][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf4ed0f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# Contributing to Kulala + +Thanks for checking out Kulala! +We're excited to hear and learn from you. + +We've put together the following guidelines to +help you figure out where you can best be helpful. + +## Table of Contents + +0. [Types of contributions we're looking for](#types-of-contributions-were-looking-for) +0. [Ground rules & expectations](#ground-rules--expectations) +0. [How to contribute](#how-to-contribute) +0. [Style guide](#style-guide) + 1. [Documentation](#documentation) + 2. [Code](#code) +0. [Setting up your environment](#setting-up-your-environment) +0. [Community](#community) + +## Types of contributions we're looking for + +There are many ways you can directly contribute to Kulala: + +- Feature requests +- Bug reports +- Code contributions +- Writing or editing documentation + +## Ground rules & expectations + +Before we get started, +here are a few things we expect from you (and that you should expect from others): + +- Be kind and thoughtful in your conversations around this project. + We all come from different backgrounds and projects, + which means we likely have different perspectives on "how open source is done." + Try to listen to others rather than convince them that your way is correct. +- Kulala is released with a [Contributor Code of Conduct](./CODE_OF_CONDUCT.md). + By participating in this project, you agree to abide by its terms. +- Please ensure that your contribution passes all tests if you open a pull request. + If there are test failures, you will need to address them before we can merge your contribution. +- When adding content, please consider if it is widely valuable. + Please don't add references or links to things you or your employer have created, + as others will do so if they appreciate it. + +## How to contribute + +If you'd like to contribute, +start by searching through the [pull requests](https://github.com/mistweaverco/kulala.nvim/pulls) to +see whether someone else has raised a similar idea or question. + +If you don't see your idea listed, and you think it fits into the goals of this guide, open a pull request. + +## Style guide + +### Documentation + +If you're writing documentation, +see the [style guide](./docs/.vale/styles) (which uses [vale](https://vale.sh)) to +help your prose match the rest of the documentation. + +### Code + +When writing code, +please follow these configurations: + +- [stylua](./stylua.toml) +- [luacheck](./.luacheckrc) +- [EditorConfig](./.editorconfig) +- [yaml-lint](./.yamllint.yaml) + +Most of them are automatically checked by the CI, +so you don't need to worry about them. + +## Community + +Discussions about the Kulala take place on: + +- This repository's [Issues](https://github.com/mistweaverco/kulala.nvim/issues) and + [Pull Requests](https://github.com/mistweaverco/kulala.nvim/pulls) sections +- The [Kulala Discord server](https://discord.gg/QyVQmfY4Rt) + +Anybody is welcome to join these conversations. + +Wherever possible, +do not take these conversations to private channels, +including contacting the maintainers directly. + +Keeping communication public means everybody can benefit and learn from the conversation. diff --git a/LICENSE b/LICENSE index dc19184..c0175d8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 mistweaver.co +Copyright (c) 2024+ mistweaverco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 76e504c..1017611 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,13 @@ version: ./scripts/set-version.sh $(VERSION) +tag: + ./scripts/tag.sh +release: + ./scripts/tag.sh +docker-build: + if [ "$(OS)" != "linux" ] || [ "$(OS)" != "windows" ]; then (echo "OS must be either linux or windows"; exit 1); fi + docker build -t ghcr.io/mistweaverco/kulala-nvim-$(OS)-testrunner:latest tests/_dockerfiles/$(OS) +docker-push: + if [ "$(OS)" != "linux" ] || [ "$(OS)" != "windows" ]; then (echo "OS must be either linux or windows"; exit 1); fi + docker push ghcr.io/mistweaverco/kulala-nvim-$(OS)-testrunner:latest + diff --git a/README.md b/README.md index 87dc288..17afce9 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ See [configuration options](https://kulala.mwco.app/docs/getting-started/configu For getting this project started. -This project was heaviliy inspired by the idea of having a REST client in Neovim. +This project was heavily inspired by the idea of having a REST client in Neovim. The actual state of [rest.nvim](https://github.com/rest-nvim/rest.nvim) as archived kicked off the development of kulala.nvim. diff --git a/docs/.vale/styles/18F/Abbreviations.yml b/docs/.vale/styles/18F/Abbreviations.yml new file mode 100644 index 0000000..de4b017 --- /dev/null +++ b/docs/.vale/styles/18F/Abbreviations.yml @@ -0,0 +1,15 @@ +--- +extends: substitution +message: "Use '%s' instead of '%s'." +link: https://content-guide.18f.gov/specific-words-and-phrases/ +ignorecase: false +level: error +nonword: true +swap: + '\beg\b': e.g., + '\bie\b': i.e., + 'e\.g\.(?:[^,]|$)': e.g., + 'i\.e\.(?:[^,]|$)': i.e., + '(?i)\d{1,2} ?[ap]m': a.m. or p.m. + 'D\.C\.': DC + '\bUSA?\b': U.S. diff --git a/docs/.vale/styles/18F/Ages.yml b/docs/.vale/styles/18F/Ages.yml new file mode 100644 index 0000000..0705ee3 --- /dev/null +++ b/docs/.vale/styles/18F/Ages.yml @@ -0,0 +1,7 @@ +--- +extends: existence +message: Avoid hyphens in ages unless it clarifies the text. +link: https://content-guide.18f.gov/specific-words-and-phrases/ +level: warning +tokens: + - '\d{1,3}-year-old' diff --git a/docs/.vale/styles/18F/Brands.yml b/docs/.vale/styles/18F/Brands.yml new file mode 100644 index 0000000..fad6ff7 --- /dev/null +++ b/docs/.vale/styles/18F/Brands.yml @@ -0,0 +1,22 @@ +--- +extends: substitution +message: "Use '%s' instead of '%s'." +link: https://content-guide.18f.gov/trademarks-and-brands/ +level: warning +ignorecase: true +swap: + Band-Aid: bandage + Bubble Wrap: packaging bubbles + Chapstick: lip balm + Crayola: crayons + Dumpster: waste container + Hi-Liter: highlighting marker + iPod: MP3 player + Kleenex: tissue + Plexiglas: plastic glass + Post-it note: adhesive note + Q-Tips: cotton swabs + Scotch tape: transparent tape + Styrofoam: plastic foam + Taser: stun gun + Xerox: photocopy diff --git a/docs/.vale/styles/18F/Clarity.yml b/docs/.vale/styles/18F/Clarity.yml new file mode 100644 index 0000000..da7683c --- /dev/null +++ b/docs/.vale/styles/18F/Clarity.yml @@ -0,0 +1,35 @@ +--- +extends: substitution +message: '%s' +link: https://content-guide.18f.gov/plain-language/#words-to-avoid +level: warning +ignorecase: true +swap: + (?:commit|pledge): > + Be more specific — we’re either doing something or we’re not. + advancing: Avoid using 'advancing.' + agenda: Avoid using 'agenda' (unless you’re talking about a meeting). + deploy: > + Avoid using 'deploy', unless you’re talking about the military or + software. + disincentivize: Avoid using 'disincentivize.' + empower: Avoid using 'empower.' + focusing: Avoid using 'focusing.' + foster: Avoid using 'foster' (unless it’s children). + impact(?:ful)?: Avoid using impact or impactful. + incentivize: Avoid using 'incentivize.' + innovative: Use words that describe the positive outcome of the innovation. + key: Avoid using 'key' (unless it unlocks something). + leverage: > + Avoid using 'leverage' (unless you're using it in the financial sense). + progress: What are you actually doing? + promote: Avoid using 'promote' (unless you’re talking about an ad campaign). + streamline: Avoid using 'streamline.' + strengthening: > + Avoid using 'strengthening' (unless you’re referring to bridges or other + structures). + tackling: > + Avoid using 'tackling' (unless you’re referring to football or another + contact sport). + touchpoint: Mention specific system components. + transforming: What are you actually doing to change it? diff --git a/docs/.vale/styles/18F/Contractions.yml b/docs/.vale/styles/18F/Contractions.yml new file mode 100644 index 0000000..875b73a --- /dev/null +++ b/docs/.vale/styles/18F/Contractions.yml @@ -0,0 +1,41 @@ +--- +extends: substitution +message: 'Use "%s" instead of "%s".' +link: https://content-guide.18f.gov/voice-and-tone/#use-contractions +level: error +ignorecase: true +swap: + are not: aren't + cannot: can't + could not: couldn't + did not: didn't + do not: don't + does not: doesn't + has not: hasn't + have not: haven't + how is: how's + how will: how'll + is not: isn't + it is: it's + it will: it'll + should not: shouldn't + that is: that's + that will: that'll + they are: they're + they will: they'll + was not: wasn't + we are: we're + we have: we've + we will: we'll + were not: weren't + what is: what's + what will: what'll + when is: when's + when will: when'll + where is: where's + where will: where'll + who is: who's + who will: who'll + why is: why's + why will: why'll + will not: won't diff --git a/docs/.vale/styles/18F/DropDown.yml b/docs/.vale/styles/18F/DropDown.yml new file mode 100644 index 0000000..14856fa --- /dev/null +++ b/docs/.vale/styles/18F/DropDown.yml @@ -0,0 +1,14 @@ +--- +extends: substitution +message: "Use %s instead of '%s'." +link: https://content-guide.18f.gov/specific-words-and-phrases/ +level: error +# 'drop-down' when used as an adjective. For example, drop-down menu. +# 'drop down' when used as a noun. For example, an option from the drop down. +# Never dropdown. +pos: 'drop/\w+ down/RP|dropdown/\w+|drop-down/NN' +ignorecase: true +swap: + drop down: "'drop-down'" + drop-down: "'drop down'" + dropdown: "'drop-down' or 'drop down'" diff --git a/docs/.vale/styles/18F/Quotes.yml b/docs/.vale/styles/18F/Quotes.yml new file mode 100644 index 0000000..24d6d4f --- /dev/null +++ b/docs/.vale/styles/18F/Quotes.yml @@ -0,0 +1,8 @@ +--- +extends: existence +message: Punctuation should be inside the quotes. +link: https://content-guide.18f.gov/punctuation/#quotes +level: error +nonword: true +tokens: + - '"[^"]+"[.,?]' diff --git a/docs/.vale/styles/18F/Reading.yml b/docs/.vale/styles/18F/Reading.yml new file mode 100644 index 0000000..fe560bc --- /dev/null +++ b/docs/.vale/styles/18F/Reading.yml @@ -0,0 +1,8 @@ +--- +message: Craft sentences at 25 words or fewer, whenever possible. +link: 'https://content-guide.18f.gov/be-concise/#keep-sentences-short-and-sweet' +extends: occurrence +scope: sentence +level: warning +max: 25 +token: '\b(\w+)\b' diff --git a/docs/.vale/styles/18F/SentenceLength.yml b/docs/.vale/styles/18F/SentenceLength.yml new file mode 100644 index 0000000..fe560bc --- /dev/null +++ b/docs/.vale/styles/18F/SentenceLength.yml @@ -0,0 +1,8 @@ +--- +message: Craft sentences at 25 words or fewer, whenever possible. +link: 'https://content-guide.18f.gov/be-concise/#keep-sentences-short-and-sweet' +extends: occurrence +scope: sentence +level: warning +max: 25 +token: '\b(\w+)\b' diff --git a/docs/.vale/styles/18F/Spacing.yml b/docs/.vale/styles/18F/Spacing.yml new file mode 100644 index 0000000..c66189d --- /dev/null +++ b/docs/.vale/styles/18F/Spacing.yml @@ -0,0 +1,9 @@ +--- +extends: existence +message: "'%s' should have one space." +link: https://content-guide.18f.gov/punctuation/#spaces +level: error +nonword: true +tokens: + - '[.?!] {2,}[A-Z]' + - '[.?!][A-Z]' diff --git a/docs/.vale/styles/18F/Terms.yml b/docs/.vale/styles/18F/Terms.yml new file mode 100644 index 0000000..db506ef --- /dev/null +++ b/docs/.vale/styles/18F/Terms.yml @@ -0,0 +1,18 @@ +--- +extends: substitution +message: "Prefer %s over '%s'." +level: warning +ignorecase: false +swap: + 'e(?:-| )mail': email + (?:illegals|illegal aliens): "'undocumented immigrants'" + back-end: "'back end'" + citizen: "'people' or 'The public'" + combating: "'working against' or 'fighting'" + countering: "'answering' or 'responding'" + dialogue: "'speaking(ing)'" + dropdown: "'drop-down' or 'drop down'" + execute: "'run' or 'do'" + open-source: "'open source'" + simple: "'straightforward', 'uncomplicated', or 'clear'" + user testing: "'usability testing'" diff --git a/docs/.vale/styles/18F/ToDo.yml b/docs/.vale/styles/18F/ToDo.yml new file mode 100644 index 0000000..6c2aa7e --- /dev/null +++ b/docs/.vale/styles/18F/ToDo.yml @@ -0,0 +1,11 @@ +--- +extends: substitution +message: "Use '%s' instead of '%s'." +link: https://content-guide.18f.gov/specific-words-and-phrases/ +level: error +# 'to do' (noun) and 'to-do' (adjective). For example, your to dos or your +# to-do list. +pos: 'to/TO do/VB' +ignorecase: true +swap: + to do: to-do diff --git a/docs/.vale/styles/18F/UnexpandedAcronyms.yml b/docs/.vale/styles/18F/UnexpandedAcronyms.yml new file mode 100644 index 0000000..199e07f --- /dev/null +++ b/docs/.vale/styles/18F/UnexpandedAcronyms.yml @@ -0,0 +1,64 @@ +--- +extends: conditional +message: "'%s' should be parenthetically defined." +link: https://content-guide.18f.gov/abbreviations-and-acronyms/ +level: warning +first: '\b([A-Z]{3,5})\b' +second: '(?:\b[A-Z][a-z]+['']? )+\(([A-Z]{3,5})\)' +exceptions: + - ABC + - ADD + - ADHD + - AIDS + - APA + - API + - CBS + - CIA + - CSI + - CSS + - CST + - ESPN + - EST + - FAQ + - FBI + - FBI + - FIXME + - GNU + - GOV + - HIV + - HR + - HTML + - HTTP + - HTTPS + - JSON + - LAN + - MIT + - MLA + - MLB + - MTV + - NAACP + - NAACP + - NASA + - NASA + - NATO + - NBA + - NBC + - NCAA + - NCAAB + - NCAAF + - NFL + - NHL + - NOTE + - PDF + - PGA + - PPV + - PST + - SGML + - SSN + - TNT + - TODO + - URL + - USA + - USSR + - XML + - XXX diff --git a/docs/.vale/styles/documentation/Abbreviations.yml b/docs/.vale/styles/documentation/Abbreviations.yml new file mode 100644 index 0000000..de4b017 --- /dev/null +++ b/docs/.vale/styles/documentation/Abbreviations.yml @@ -0,0 +1,15 @@ +--- +extends: substitution +message: "Use '%s' instead of '%s'." +link: https://content-guide.18f.gov/specific-words-and-phrases/ +ignorecase: false +level: error +nonword: true +swap: + '\beg\b': e.g., + '\bie\b': i.e., + 'e\.g\.(?:[^,]|$)': e.g., + 'i\.e\.(?:[^,]|$)': i.e., + '(?i)\d{1,2} ?[ap]m': a.m. or p.m. + 'D\.C\.': DC + '\bUSA?\b': U.S. diff --git a/docs/.vale/styles/documentation/Ages.yml b/docs/.vale/styles/documentation/Ages.yml new file mode 100644 index 0000000..0705ee3 --- /dev/null +++ b/docs/.vale/styles/documentation/Ages.yml @@ -0,0 +1,7 @@ +--- +extends: existence +message: Avoid hyphens in ages unless it clarifies the text. +link: https://content-guide.18f.gov/specific-words-and-phrases/ +level: warning +tokens: + - '\d{1,3}-year-old' diff --git a/docs/.vale/styles/documentation/CaseSensitiveTerms.yml b/docs/.vale/styles/documentation/CaseSensitiveTerms.yml new file mode 100644 index 0000000..f45043d --- /dev/null +++ b/docs/.vale/styles/documentation/CaseSensitiveTerms.yml @@ -0,0 +1,10 @@ +--- +extends: substitution +message: "Prefer %s over '%s'." +level: warning +ignorecase: false +swap: + devops: "'DevOps'" + github: "'GitHub'" + javascript: "'JavaScript'" + readme: "'README'" diff --git a/docs/.vale/styles/documentation/Clarity.yml b/docs/.vale/styles/documentation/Clarity.yml new file mode 100644 index 0000000..acabf8e --- /dev/null +++ b/docs/.vale/styles/documentation/Clarity.yml @@ -0,0 +1,30 @@ +--- +extends: substitution +message: '%s' +link: https://content-guide.18f.gov/plain-language/#words-to-avoid +level: warning +ignorecase: true +swap: + pledge: > + Be more specific than 'pledge' — we’re either doing something or we’re not. + advancing: Avoid using 'advancing.' + agenda: Avoid using 'agenda' (unless you’re talking about a meeting). + disincentivize: Avoid using 'disincentivize.' + empower: Avoid using 'empower.' + focusing: Avoid using 'focusing.' + foster: Avoid using 'foster' (unless it’s children). + impactful: Avoid using impactful. + incentivize: Avoid using 'incentivize.' + innovative: Use words that describe the positive outcome of the innovation. + leverage: > + Avoid using 'leverage' (unless you're using it in the financial sense). + promote: Avoid using 'promote' (unless you’re talking about an ad campaign). + streamline: Avoid using 'streamline.' + strengthening: > + Avoid using 'strengthening' (unless you’re referring to bridges or other + structures). + tackling: > + Avoid using 'tackling' (unless you’re referring to football or another + contact sport). + touchpoint: Mention specific system components. + transforming: What are you actually doing to change it? diff --git a/docs/.vale/styles/documentation/DropDown.yml b/docs/.vale/styles/documentation/DropDown.yml new file mode 100644 index 0000000..14856fa --- /dev/null +++ b/docs/.vale/styles/documentation/DropDown.yml @@ -0,0 +1,14 @@ +--- +extends: substitution +message: "Use %s instead of '%s'." +link: https://content-guide.18f.gov/specific-words-and-phrases/ +level: error +# 'drop-down' when used as an adjective. For example, drop-down menu. +# 'drop down' when used as a noun. For example, an option from the drop down. +# Never dropdown. +pos: 'drop/\w+ down/RP|dropdown/\w+|drop-down/NN' +ignorecase: true +swap: + drop down: "'drop-down'" + drop-down: "'drop down'" + dropdown: "'drop-down' or 'drop down'" diff --git a/docs/.vale/styles/documentation/LineLength.yml b/docs/.vale/styles/documentation/LineLength.yml new file mode 100644 index 0000000..eb0b5cb --- /dev/null +++ b/docs/.vale/styles/documentation/LineLength.yml @@ -0,0 +1,22 @@ +--- +message: A line should be at most 80 characters long. +link: https://tengolang.com/ +extends: script +scope: raw +level: warning +script: | + text := import("text") + matches := [] + // Remove all instances of code blocks since we don't want to count + // inter-block newlines as a new paragraph. + document := text.re_replace("(?s) *(\n```.*?```\n)", scope, "") + for line in text.split(document, "\n") { + // Skip links + if text.re_match("\\[(.*?)\\]: ", line) { + continue + } + if len(line) > 80 { + start := text.index(scope, line) + matches = append(matches, {begin: start, end: start + len(line)}) + } + } diff --git a/docs/.vale/styles/documentation/SentenceLength.yml b/docs/.vale/styles/documentation/SentenceLength.yml new file mode 100644 index 0000000..fe560bc --- /dev/null +++ b/docs/.vale/styles/documentation/SentenceLength.yml @@ -0,0 +1,8 @@ +--- +message: Craft sentences at 25 words or fewer, whenever possible. +link: 'https://content-guide.18f.gov/be-concise/#keep-sentences-short-and-sweet' +extends: occurrence +scope: sentence +level: warning +max: 25 +token: '\b(\w+)\b' diff --git a/docs/.vale/styles/documentation/Spacing.yml b/docs/.vale/styles/documentation/Spacing.yml new file mode 100644 index 0000000..c66189d --- /dev/null +++ b/docs/.vale/styles/documentation/Spacing.yml @@ -0,0 +1,9 @@ +--- +extends: existence +message: "'%s' should have one space." +link: https://content-guide.18f.gov/punctuation/#spaces +level: error +nonword: true +tokens: + - '[.?!] {2,}[A-Z]' + - '[.?!][A-Z]' diff --git a/docs/.vale/styles/documentation/Terms.yml b/docs/.vale/styles/documentation/Terms.yml new file mode 100644 index 0000000..be3a825 --- /dev/null +++ b/docs/.vale/styles/documentation/Terms.yml @@ -0,0 +1,14 @@ +--- +extends: substitution +message: "Prefer %s over '%s'." +level: warning +ignorecase: true +swap: + 'e(?:-| )mail': email + back-end: "'back end'" + combating: "'working against' or 'fighting'" + countering: "'answering' or 'responding'" + dropdown: "'drop-down' or 'drop down'" + open-source: "'open source'" + simple: "'straightforward', 'uncomplicated', or 'clear'" + user testing: "'usability testing'" diff --git a/docs/.vale/styles/documentation/ToDo.yml b/docs/.vale/styles/documentation/ToDo.yml new file mode 100644 index 0000000..dc190b0 --- /dev/null +++ b/docs/.vale/styles/documentation/ToDo.yml @@ -0,0 +1,11 @@ +--- +extends: substitution +message: "Use '%s' instead of '%s'." +link: https://content-guide.18f.gov/specific-words-and-phrases/ +level: suggestion +# 'to do' (noun) and 'to-do' (adjective). For example, your to dos or your +# to-do list. +pos: 'to/TO do/VB' +ignorecase: true +swap: + to do: to-do diff --git a/docs/.vale/styles/documentation/UnexpandedAcronyms.yml b/docs/.vale/styles/documentation/UnexpandedAcronyms.yml new file mode 100644 index 0000000..8fceeef --- /dev/null +++ b/docs/.vale/styles/documentation/UnexpandedAcronyms.yml @@ -0,0 +1,104 @@ +--- +extends: conditional +message: "'%s' should be parenthetically defined." +link: https://content-guide.18f.gov/abbreviations-and-acronyms/ +level: warning +first: '\b([A-Z]{3,5})\b' +second: '(?:\b[A-Z][a-z]+['']? )+\(([A-Z]{3,5})\)' +exceptions: + - ABI + - ACM + - ADC + - ADR + - AKA + - APA + - API + - ARM + - ASAP + - AVR + - CAN + - CBS + - CEO + - CLI + - CMSIS + - CPU + - CRC + - CSS + - CST + - DMA + - DRAM + - DSP + - EST + - ETL + - FAQ + - FIFO + - FILE + - FIXME + - GCC + - GNU + - GOV + - GPIO + - GPS + - HAL + - HTML + - HTTP + - HTTPS + - IDE + - IEEE + - IRQ + - I2C + - JSON + - LAN + - LFS + - LED + - LLC + - MCAPI + - MIPI + - MIPS + - MIT + - MLA + - MLB + - MMU + - MRAPI + - MSVC + - MTAPI + - NASA + - NFC + - NOT + - NOTE + - 'NULL' + - OLED + - OTA + - PCB + - PDF + - PGA + - PIC + - POSIX + - PPV + - PST + - RAII + - RAM + - RFID + - ROM + - RPC + - RTTI + - RTOS + - SDK + - SFR + - SGML + - SPI + - SRAM + - SSN + - STL + - TBD + - TDD + - TODO + - UART + - USART + - UML + - URL + - USA + - USB + - WIP + - XMI + - XML diff --git a/docs/.vale/styles/proselint/Airlinese.yml b/docs/.vale/styles/proselint/Airlinese.yml new file mode 100644 index 0000000..a6ae9c1 --- /dev/null +++ b/docs/.vale/styles/proselint/Airlinese.yml @@ -0,0 +1,8 @@ +extends: existence +message: "'%s' is airlinese." +ignorecase: true +level: error +tokens: + - enplan(?:e|ed|ing|ement) + - deplan(?:e|ed|ing|ement) + - taking off momentarily diff --git a/docs/.vale/styles/proselint/AnimalLabels.yml b/docs/.vale/styles/proselint/AnimalLabels.yml new file mode 100644 index 0000000..b92e06f --- /dev/null +++ b/docs/.vale/styles/proselint/AnimalLabels.yml @@ -0,0 +1,48 @@ +extends: substitution +message: "Consider using '%s' instead of '%s'." +level: error +action: + name: replace +swap: + (?:bull|ox)-like: taurine + (?:calf|veal)-like: vituline + (?:crow|raven)-like: corvine + (?:leopard|panther)-like: pardine + bird-like: avine + centipede-like: scolopendrine + crab-like: cancrine + crocodile-like: crocodiline + deer-like: damine + eagle-like: aquiline + earthworm-like: lumbricine + falcon-like: falconine + ferine: wild animal-like + fish-like: piscine + fox-like: vulpine + frog-like: ranine + goat-like: hircine + goose-like: anserine + gull-like: laridine + hare-like: leporine + hawk-like: accipitrine + hippopotamus-like: hippopotamine + lizard-like: lacertine + mongoose-like: viverrine + mouse-like: murine + ostrich-like: struthionine + peacock-like: pavonine + porcupine-like: hystricine + rattlesnake-like: crotaline + sable-like: zibeline + sheep-like: ovine + shrew-like: soricine + sparrow-like: passerine + swallow-like: hirundine + swine-like: suilline + tiger-like: tigrine + viper-like: viperine + vulture-like: vulturine + wasp-like: vespine + wolf-like: lupine + woodpecker-like: picine + zebra-like: zebrine diff --git a/docs/.vale/styles/proselint/Annotations.yml b/docs/.vale/styles/proselint/Annotations.yml new file mode 100644 index 0000000..dcb24f4 --- /dev/null +++ b/docs/.vale/styles/proselint/Annotations.yml @@ -0,0 +1,9 @@ +extends: existence +message: "'%s' left in text." +ignorecase: false +level: error +tokens: + - XXX + - FIXME + - TODO + - NOTE diff --git a/docs/.vale/styles/proselint/Apologizing.yml b/docs/.vale/styles/proselint/Apologizing.yml new file mode 100644 index 0000000..11088aa --- /dev/null +++ b/docs/.vale/styles/proselint/Apologizing.yml @@ -0,0 +1,8 @@ +extends: existence +message: "Excessive apologizing: '%s'" +ignorecase: true +level: error +action: + name: remove +tokens: + - More research is needed diff --git a/docs/.vale/styles/proselint/Archaisms.yml b/docs/.vale/styles/proselint/Archaisms.yml new file mode 100644 index 0000000..c8df9ab --- /dev/null +++ b/docs/.vale/styles/proselint/Archaisms.yml @@ -0,0 +1,52 @@ +extends: existence +message: "'%s' is archaic." +ignorecase: true +level: error +tokens: + - alack + - anent + - begat + - belike + - betimes + - boughten + - brocage + - brokage + - camarade + - chiefer + - chiefest + - Christiana + - completely obsolescent + - cozen + - divers + - deflexion + - fain + - forsooth + - foreclose from + - haply + - howbeit + - illumine + - in sooth + - maugre + - meseems + - methinks + - nigh + - peradventure + - perchance + - saith + - shew + - sistren + - spake + - to wit + - verily + - whilom + - withal + - wot + - enclosed please find + - please find enclosed + - enclosed herewith + - enclosed herein + - inforce + - ex postfacto + - foreclose from + - forewent + - for ever diff --git a/docs/.vale/styles/proselint/But.yml b/docs/.vale/styles/proselint/But.yml new file mode 100644 index 0000000..0e2c32b --- /dev/null +++ b/docs/.vale/styles/proselint/But.yml @@ -0,0 +1,8 @@ +extends: existence +message: "Do not start a paragraph with a 'but'." +level: error +scope: paragraph +action: + name: remove +tokens: + - ^But diff --git a/docs/.vale/styles/proselint/Cliches.yml b/docs/.vale/styles/proselint/Cliches.yml new file mode 100644 index 0000000..c56183c --- /dev/null +++ b/docs/.vale/styles/proselint/Cliches.yml @@ -0,0 +1,782 @@ +extends: existence +message: "'%s' is a cliche." +level: error +ignorecase: true +tokens: + - a chip off the old block + - a clean slate + - a dark and stormy night + - a far cry + - a fate worse than death + - a fine kettle of fish + - a loose cannon + - a penny saved is a penny earned + - a tough row to hoe + - a word to the wise + - ace in the hole + - acid test + - add insult to injury + - against all odds + - air your dirty laundry + - alas and alack + - all fun and games + - all hell broke loose + - all in a day's work + - all talk, no action + - all thumbs + - all your eggs in one basket + - all's fair in love and war + - all's well that ends well + - almighty dollar + - American as apple pie + - an axe to grind + - another day, another dollar + - armed to the teeth + - as luck would have it + - as old as time + - as the crow flies + - at loose ends + - at my wits end + - at the end of the day + - avoid like the plague + - babe in the woods + - back against the wall + - back in the saddle + - back to square one + - back to the drawing board + - bad to the bone + - badge of honor + - bald faced liar + - bald-faced lie + - ballpark figure + - banging your head against a brick wall + - baptism by fire + - barking up the wrong tree + - bat out of hell + - be all and end all + - beat a dead horse + - beat around the bush + - been there, done that + - beggars can't be choosers + - behind the eight ball + - bend over backwards + - benefit of the doubt + - bent out of shape + - best thing since sliced bread + - bet your bottom dollar + - better half + - better late than never + - better mousetrap + - better safe than sorry + - between a rock and a hard place + - between a rock and a hard place + - between Scylla and Charybdis + - between the devil and the deep blue see + - betwixt and between + - beyond the pale + - bide your time + - big as life + - big cheese + - big fish in a small pond + - big man on campus + - bigger they are the harder they fall + - bird in the hand + - bird's eye view + - birds and the bees + - birds of a feather flock together + - bit the hand that feeds you + - bite the bullet + - bite the dust + - bitten off more than he can chew + - black as coal + - black as pitch + - black as the ace of spades + - blast from the past + - bleeding heart + - blessing in disguise + - blind ambition + - blind as a bat + - blind leading the blind + - blissful ignorance + - blood is thicker than water + - blood sweat and tears + - blow a fuse + - blow off steam + - blow your own horn + - blushing bride + - boils down to + - bolt from the blue + - bone to pick + - bored stiff + - bored to tears + - bottomless pit + - boys will be boys + - bright and early + - brings home the bacon + - broad across the beam + - broken record + - brought back to reality + - bulk large + - bull by the horns + - bull in a china shop + - burn the midnight oil + - burning question + - burning the candle at both ends + - burst your bubble + - bury the hatchet + - busy as a bee + - but that's another story + - by hook or by crook + - call a spade a spade + - called onto the carpet + - calm before the storm + - can of worms + - can't cut the mustard + - can't hold a candle to + - case of mistaken identity + - cast aspersions + - cat got your tongue + - cat's meow + - caught in the crossfire + - caught red-handed + - chase a red herring + - checkered past + - chomping at the bit + - cleanliness is next to godliness + - clear as a bell + - clear as mud + - close to the vest + - cock and bull story + - cold shoulder + - come hell or high water + - comparing apples and oranges + - compleat + - conspicuous by its absence + - cool as a cucumber + - cool, calm, and collected + - cost a king's ransom + - count your blessings + - crack of dawn + - crash course + - creature comforts + - cross that bridge when you come to it + - crushing blow + - cry like a baby + - cry me a river + - cry over spilt milk + - crystal clear + - crystal clear + - curiosity killed the cat + - cut and dried + - cut through the red tape + - cut to the chase + - cute as a bugs ear + - cute as a button + - cute as a puppy + - cuts to the quick + - cutting edge + - dark before the dawn + - day in, day out + - dead as a doornail + - decision-making process + - devil is in the details + - dime a dozen + - divide and conquer + - dog and pony show + - dog days + - dog eat dog + - dog tired + - don't burn your bridges + - don't count your chickens + - don't look a gift horse in the mouth + - don't rock the boat + - don't step on anyone's toes + - don't take any wooden nickels + - down and out + - down at the heels + - down in the dumps + - down the hatch + - down to earth + - draw the line + - dressed to kill + - dressed to the nines + - drives me up the wall + - dubious distinction + - dull as dishwater + - duly authorized + - dyed in the wool + - eagle eye + - ear to the ground + - early bird catches the worm + - easier said than done + - easy as pie + - eat your heart out + - eat your words + - eleventh hour + - even the playing field + - every dog has its day + - every fiber of my being + - everything but the kitchen sink + - eye for an eye + - eyes peeled + - face the music + - facts of life + - fair weather friend + - fall by the wayside + - fan the flames + - far be it from me + - fast and loose + - feast or famine + - feather your nest + - feathered friends + - few and far between + - fifteen minutes of fame + - fills the bill + - filthy vermin + - fine kettle of fish + - first and foremost + - fish out of water + - fishing for a compliment + - fit as a fiddle + - fit the bill + - fit to be tied + - flash in the pan + - flat as a pancake + - flip your lid + - flog a dead horse + - fly by night + - fly the coop + - follow your heart + - for all intents and purposes + - for free + - for the birds + - for what it's worth + - force of nature + - force to be reckoned with + - forgive and forget + - fox in the henhouse + - free and easy + - free as a bird + - fresh as a daisy + - full steam ahead + - fun in the sun + - garbage in, garbage out + - gentle as a lamb + - get a kick out of + - get a leg up + - get down and dirty + - get the lead out + - get to the bottom of + - get with the program + - get your feet wet + - gets my goat + - gilding the lily + - gilding the lily + - give and take + - go against the grain + - go at it tooth and nail + - go for broke + - go him one better + - go the extra mile + - go with the flow + - goes without saying + - good as gold + - good deed for the day + - good things come to those who wait + - good time was had by all + - good times were had by all + - greased lightning + - greek to me + - green thumb + - green-eyed monster + - grist for the mill + - growing like a weed + - hair of the dog + - hand to mouth + - happy as a clam + - happy as a lark + - hasn't a clue + - have a nice day + - have a short fuse + - have high hopes + - have the last laugh + - haven't got a row to hoe + - he's got his hands full + - head honcho + - head over heels + - hear a pin drop + - heard it through the grapevine + - heart's content + - heavy as lead + - hem and haw + - high and dry + - high and mighty + - high as a kite + - his own worst enemy + - his work cut out for him + - hit paydirt + - hither and yon + - Hobson's choice + - hold your head up high + - hold your horses + - hold your own + - hold your tongue + - honest as the day is long + - horns of a dilemma + - horns of a dilemma + - horse of a different color + - hot under the collar + - hour of need + - I beg to differ + - icing on the cake + - if the shoe fits + - if the shoe were on the other foot + - if you catch my drift + - in a jam + - in a jiffy + - in a nutshell + - in a pig's eye + - in a pinch + - in a word + - in hot water + - in light of + - in the final analysis + - in the gutter + - in the last analysis + - in the nick of time + - in the thick of it + - in your dreams + - innocent bystander + - it ain't over till the fat lady sings + - it goes without saying + - it takes all kinds + - it takes one to know one + - it's a small world + - it's not what you know, it's who you know + - it's only a matter of time + - ivory tower + - Jack of all trades + - jockey for position + - jog your memory + - joined at the hip + - judge a book by its cover + - jump down your throat + - jump in with both feet + - jump on the bandwagon + - jump the gun + - jump to conclusions + - just a hop, skip, and a jump + - just the ticket + - justice is blind + - keep a stiff upper lip + - keep an eye on + - keep it simple, stupid + - keep the home fires burning + - keep up with the Joneses + - keep your chin up + - keep your fingers crossed + - kick the bucket + - kick up your heels + - kick your feet up + - kid in a candy store + - kill two birds with one stone + - kiss of death + - knock it out of the park + - knock on wood + - knock your socks off + - know him from Adam + - know the ropes + - know the score + - knuckle down + - knuckle sandwich + - knuckle under + - labor of love + - ladder of success + - land on your feet + - lap of luxury + - last but not least + - last but not least + - last hurrah + - last-ditch effort + - law of the jungle + - law of the land + - lay down the law + - leaps and bounds + - let sleeping dogs lie + - let the cat out of the bag + - let the good times roll + - let your hair down + - let's talk turkey + - letter perfect + - lick your wounds + - lies like a rug + - life's a bitch + - life's a grind + - light at the end of the tunnel + - lighter than a feather + - lighter than air + - like clockwork + - like father like son + - like taking candy from a baby + - like there's no tomorrow + - lion's share + - live and learn + - live and let live + - long and short of it + - long lost love + - look before you leap + - look down your nose + - look what the cat dragged in + - looking a gift horse in the mouth + - looks like death warmed over + - loose cannon + - lose your head + - lose your temper + - loud as a horn + - lounge lizard + - loved and lost + - low man on the totem pole + - luck of the draw + - luck of the Irish + - make a mockery of + - make hay while the sun shines + - make money hand over fist + - make my day + - make the best of a bad situation + - make the best of it + - make your blood boil + - male chauvinism + - man of few words + - man's best friend + - mark my words + - meaningful dialogue + - missed the boat on that one + - moment in the sun + - moment of glory + - moment of truth + - moment of truth + - money to burn + - more in sorrow than in anger + - more power to you + - more sinned against than sinning + - more than one way to skin a cat + - movers and shakers + - moving experience + - my better half + - naked as a jaybird + - naked truth + - neat as a pin + - needle in a haystack + - needless to say + - neither here nor there + - never look back + - never say never + - nip and tuck + - nip in the bud + - nip it in the bud + - no guts, no glory + - no love lost + - no pain, no gain + - no skin off my back + - no stone unturned + - no time like the present + - no use crying over spilled milk + - nose to the grindstone + - not a hope in hell + - not a minute's peace + - not in my backyard + - not playing with a full deck + - not the end of the world + - not written in stone + - nothing to sneeze at + - nothing ventured nothing gained + - now we're cooking + - off the top of my head + - off the wagon + - off the wall + - old hat + - olden days + - older and wiser + - older than dirt + - older than Methuselah + - on a roll + - on cloud nine + - on pins and needles + - on the bandwagon + - on the money + - on the nose + - on the rocks + - on the same page + - on the spot + - on the tip of my tongue + - on the wagon + - on thin ice + - once bitten, twice shy + - one bad apple doesn't spoil the bushel + - one born every minute + - one brick short + - one foot in the grave + - one in a million + - one red cent + - only game in town + - open a can of worms + - open and shut case + - open the flood gates + - opportunity doesn't knock twice + - out of pocket + - out of sight, out of mind + - out of the frying pan into the fire + - out of the woods + - out on a limb + - over a barrel + - over the hump + - pain and suffering + - pain in the + - panic button + - par for the course + - part and parcel + - party pooper + - pass the buck + - patience is a virtue + - pay through the nose + - penny pincher + - perfect storm + - pig in a poke + - pile it on + - pillar of the community + - pin your hopes on + - pitter patter of little feet + - plain as day + - plain as the nose on your face + - play by the rules + - play your cards right + - playing the field + - playing with fire + - pleased as punch + - plenty of fish in the sea + - point with pride + - poor as a church mouse + - pot calling the kettle black + - presidential timber + - pretty as a picture + - pull a fast one + - pull your punches + - pulled no punches + - pulling your leg + - pure as the driven snow + - put it in a nutshell + - put one over on you + - put the cart before the horse + - put the pedal to the metal + - put your best foot forward + - put your foot down + - quantum jump + - quantum leap + - quick as a bunny + - quick as a lick + - quick as a wink + - quick as lightning + - quiet as a dormouse + - rags to riches + - raining buckets + - raining cats and dogs + - rank and file + - rat race + - reap what you sow + - red as a beet + - red herring + - redound to one's credit + - redound to the benefit of + - reinvent the wheel + - rich and famous + - rings a bell + - ripe old age + - ripped me off + - rise and shine + - road to hell is paved with good intentions + - rob Peter to pay Paul + - roll over in the grave + - rub the wrong way + - ruled the roost + - running in circles + - sad but true + - sadder but wiser + - salt of the earth + - scared stiff + - scared to death + - sea change + - sealed with a kiss + - second to none + - see eye to eye + - seen the light + - seize the day + - set the record straight + - set the world on fire + - set your teeth on edge + - sharp as a tack + - shirked his duties + - shoot for the moon + - shoot the breeze + - shot in the dark + - shoulder to the wheel + - sick as a dog + - sigh of relief + - signed, sealed, and delivered + - sink or swim + - six of one, half a dozen of another + - six of one, half a dozen of the other + - skating on thin ice + - slept like a log + - slinging mud + - slippery as an eel + - slow as molasses + - smart as a whip + - smooth as a baby's bottom + - sneaking suspicion + - snug as a bug in a rug + - sow wild oats + - spare the rod, spoil the child + - speak of the devil + - spilled the beans + - spinning your wheels + - spitting image of + - spoke with relish + - spread like wildfire + - spring to life + - squeaky wheel gets the grease + - stands out like a sore thumb + - start from scratch + - stick in the mud + - still waters run deep + - stitch in time + - stop and smell the roses + - straight as an arrow + - straw that broke the camel's back + - stretched to the breaking point + - strong as an ox + - stubborn as a mule + - stuff that dreams are made of + - stuffed shirt + - sweating blood + - sweating bullets + - take a load off + - take one for the team + - take the bait + - take the bull by the horns + - take the plunge + - takes one to know one + - takes two to tango + - than you can shake a stick at + - the cream of the crop + - the cream rises to the top + - the more the merrier + - the real deal + - the real McCoy + - the red carpet treatment + - the same old story + - the straw that broke the camel's back + - there is no accounting for taste + - thick as a brick + - thick as thieves + - thick as thieves + - thin as a rail + - think outside of the box + - thinking outside the box + - third time's the charm + - this day and age + - this hurts me worse than it hurts you + - this point in time + - thought leaders? + - three sheets to the wind + - through thick and thin + - throw in the towel + - throw the baby out with the bathwater + - tie one on + - tighter than a drum + - time and time again + - time is of the essence + - tip of the iceberg + - tired but happy + - to coin a phrase + - to each his own + - to make a long story short + - to the best of my knowledge + - toe the line + - tongue in cheek + - too good to be true + - too hot to handle + - too numerous to mention + - touch with a ten foot pole + - tough as nails + - trial and error + - trials and tribulations + - tried and true + - trip down memory lane + - twist of fate + - two cents worth + - two peas in a pod + - ugly as sin + - under the counter + - under the gun + - under the same roof + - under the weather + - until the cows come home + - unvarnished truth + - up the creek + - uphill battle + - upper crust + - upset the applecart + - vain attempt + - vain effort + - vanquish the enemy + - various and sundry + - vested interest + - viable alternative + - waiting for the other shoe to drop + - wakeup call + - warm welcome + - watch your p's and q's + - watch your tongue + - watching the clock + - water under the bridge + - wax eloquent + - wax poetic + - we've got a situation here + - weather the storm + - weed them out + - week of Sundays + - went belly up + - wet behind the ears + - what goes around comes around + - what you see is what you get + - when it rains, it pours + - when push comes to shove + - when the cat's away + - when the going gets tough, the tough get going + - whet (?:the|your) appetite + - white as a sheet + - whole ball of wax + - whole hog + - whole nine yards + - wild goose chase + - will wonders never cease? + - wisdom of the ages + - wise as an owl + - wolf at the door + - wool pulled over our eyes + - words fail me + - work like a dog + - world weary + - worst nightmare + - worth its weight in gold + - writ large + - wrong side of the bed + - yanking your chain + - yappy as a dog + - years young + - you are what you eat + - you can run but you can't hide + - you only live once + - you're the boss + - young and foolish + - young and vibrant diff --git a/docs/.vale/styles/proselint/CorporateSpeak.yml b/docs/.vale/styles/proselint/CorporateSpeak.yml new file mode 100644 index 0000000..4de8ee3 --- /dev/null +++ b/docs/.vale/styles/proselint/CorporateSpeak.yml @@ -0,0 +1,30 @@ +extends: existence +message: "'%s' is corporate speak." +ignorecase: true +level: error +tokens: + - at the end of the day + - back to the drawing board + - hit the ground running + - get the ball rolling + - low-hanging fruit + - thrown under the bus + - think outside the box + - let's touch base + - get my manager's blessing + - it's on my radar + - ping me + - i don't have the bandwidth + - no brainer + - par for the course + - bang for your buck + - synergy + - move the goal post + - apples to apples + - win-win + - circle back around + - all hands on deck + - take this offline + - drill-down + - elephant in the room + - on my plate diff --git a/docs/.vale/styles/proselint/Currency.yml b/docs/.vale/styles/proselint/Currency.yml new file mode 100644 index 0000000..ebd4b7d --- /dev/null +++ b/docs/.vale/styles/proselint/Currency.yml @@ -0,0 +1,5 @@ +extends: existence +message: "Incorrect use of symbols in '%s'." +ignorecase: true +raw: + - \$[\d]* ?(?:dollars|usd|us dollars) diff --git a/docs/.vale/styles/proselint/Cursing.yml b/docs/.vale/styles/proselint/Cursing.yml new file mode 100644 index 0000000..e65070a --- /dev/null +++ b/docs/.vale/styles/proselint/Cursing.yml @@ -0,0 +1,15 @@ +extends: existence +message: "Consider replacing '%s'." +level: error +ignorecase: true +tokens: + - shit + - piss + - fuck + - cunt + - cocksucker + - motherfucker + - tits + - fart + - turd + - twat diff --git a/docs/.vale/styles/proselint/DateCase.yml b/docs/.vale/styles/proselint/DateCase.yml new file mode 100644 index 0000000..28c4771 --- /dev/null +++ b/docs/.vale/styles/proselint/DateCase.yml @@ -0,0 +1,7 @@ +extends: existence +message: With lowercase letters, the periods are standard. +ignorecase: true +level: error +nonword: true +tokens: + - '\d{1,2} ?[ap]m' diff --git a/docs/.vale/styles/proselint/DateMidnight.yml b/docs/.vale/styles/proselint/DateMidnight.yml new file mode 100644 index 0000000..0130e1a --- /dev/null +++ b/docs/.vale/styles/proselint/DateMidnight.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Use 'midnight' or 'noon'." +ignorecase: true +level: error +nonword: true +tokens: + - '12 ?[ap]\.?m\.?' diff --git a/docs/.vale/styles/proselint/DateRedundancy.yml b/docs/.vale/styles/proselint/DateRedundancy.yml new file mode 100644 index 0000000..b1f653e --- /dev/null +++ b/docs/.vale/styles/proselint/DateRedundancy.yml @@ -0,0 +1,10 @@ +extends: existence +message: "'a.m.' is always morning; 'p.m.' is always night." +ignorecase: true +level: error +nonword: true +tokens: + - '\d{1,2} ?a\.?m\.? in the morning' + - '\d{1,2} ?p\.?m\.? in the evening' + - '\d{1,2} ?p\.?m\.? at night' + - '\d{1,2} ?p\.?m\.? in the afternoon' diff --git a/docs/.vale/styles/proselint/DateSpacing.yml b/docs/.vale/styles/proselint/DateSpacing.yml new file mode 100644 index 0000000..b7a2fd3 --- /dev/null +++ b/docs/.vale/styles/proselint/DateSpacing.yml @@ -0,0 +1,7 @@ +extends: existence +message: "It's standard to put a space before '%s'" +ignorecase: true +level: error +nonword: true +tokens: + - '\d{1,2}[ap]\.?m\.?' diff --git a/docs/.vale/styles/proselint/DenizenLabels.yml b/docs/.vale/styles/proselint/DenizenLabels.yml new file mode 100644 index 0000000..bc3dd8a --- /dev/null +++ b/docs/.vale/styles/proselint/DenizenLabels.yml @@ -0,0 +1,52 @@ +extends: substitution +message: Did you mean '%s'? +ignorecase: false +action: + name: replace +swap: + (?:Afrikaaner|Afrikander): Afrikaner + (?:Hong Kongite|Hong Kongian): Hong Konger + (?:Indianan|Indianian): Hoosier + (?:Michiganite|Michiganian): Michigander + (?:New Hampshireite|New Hampshireman): New Hampshirite + (?:Newcastlite|Newcastleite): Novocastrian + (?:Providencian|Providencer): Providentian + (?:Trentian|Trentonian): Tridentine + (?:Warsawer|Warsawian): Varsovian + (?:Wolverhamptonite|Wolverhamptonian): Wulfrunian + Alabaman: Alabamian + Albuquerquian: Albuquerquean + Anchoragite: Anchorageite + Arizonian: Arizonan + Arkansawyer: Arkansan + Belarusan: Belarusian + Cayman Islander: Caymanian + Coloradoan: Coloradan + Connecticuter: Nutmegger + Fairbanksian: Fairbanksan + Fort Worther: Fort Worthian + Grenadian: Grenadan + Halifaxer: Haligonian + Hartlepoolian: Hartlepudlian + Illinoisian: Illinoisan + Iowegian: Iowan + Leedsian: Leodenisian + Liverpoolian: Liverpudlian + Los Angelean: Angeleno + Manchesterian: Mancunian + Minneapolisian: Minneapolitan + Missouran: Missourian + Monacan: Monegasque + Neopolitan: Neapolitan + New Jerseyite: New Jerseyan + New Orleansian: New Orleanian + Oklahoma Citian: Oklahoma Cityan + Oklahomian: Oklahoman + Saudi Arabian: Saudi + Seattlite: Seattleite + Surinamer: Surinamese + Tallahassean: Tallahasseean + Tennesseean: Tennessean + Trois-Rivièrester: Trifluvian + Utahan: Utahn + Valladolidian: Vallisoletano diff --git a/docs/.vale/styles/proselint/Diacritical.yml b/docs/.vale/styles/proselint/Diacritical.yml new file mode 100644 index 0000000..fd74808 --- /dev/null +++ b/docs/.vale/styles/proselint/Diacritical.yml @@ -0,0 +1,96 @@ +extends: substitution +message: Consider using '%s' instead of '%s'. +ignorecase: true +level: error +action: + name: replace +swap: + beau ideal: beau idéal + boutonniere: boutonnière + bric-a-brac: bric-à-brac + cafe: café + cause celebre: cause célèbre + chevre: chèvre + cliche: cliché + consomme: consommé + coup de grace: coup de grâce + crudites: crudités + creme brulee: crème brûlée + creme de menthe: crème de menthe + creme fraice: crème fraîche + creme fresh: crème fraîche + crepe: crêpe + debutante: débutante + decor: décor + deja vu: déjà vu + denouement: dénouement + facade: façade + fiance: fiancé + fiancee: fiancée + flambe: flambé + garcon: garçon + lycee: lycée + maitre d: maître d + menage a trois: ménage à trois + negligee: négligée + protege: protégé + protegee: protégée + puree: purée + my resume: my résumé + your resume: your résumé + his resume: his résumé + her resume: her résumé + a resume: a résumé + the resume: the résumé + risque: risqué + roue: roué + soiree: soirée + souffle: soufflé + soupcon: soupçon + touche: touché + tete-a-tete: tête-à-tête + voila: voilà + a la carte: à la carte + a la mode: à la mode + emigre: émigré + + # Spanish loanwords + El Nino: El Niño + jalapeno: jalapeño + La Nina: La Niña + pina colada: piña colada + senor: señor + senora: señora + senorita: señorita + + # Portuguese loanwords + acai: açaí + + # German loanwords + doppelganger: doppelgänger + Fuhrer: Führer + Gewurztraminer: Gewürztraminer + vis-a-vis: vis-à-vis + Ubermensch: Übermensch + + # Swedish loanwords + filmjolk: filmjölk + smorgasbord: smörgåsbord + + # Names, places, and companies + Beyonce: Beyoncé + Bronte: Brontë + Bronte: Brontë + Champs-Elysees: Champs-Élysées + Citroen: Citroën + Curacao: Curaçao + Lowenbrau: Löwenbräu + Monegasque: Monégasque + Motley Crue: Mötley Crüe + Nescafe: Nescafé + Queensryche: Queensrÿche + Quebec: Québec + Quebecois: Québécois + Angstrom: Ångström + angstrom: ångström + Skoda: Škoda diff --git a/docs/.vale/styles/proselint/GenderBias.yml b/docs/.vale/styles/proselint/GenderBias.yml new file mode 100644 index 0000000..d98d3cf --- /dev/null +++ b/docs/.vale/styles/proselint/GenderBias.yml @@ -0,0 +1,45 @@ +extends: substitution +message: Consider using '%s' instead of '%s'. +ignorecase: true +level: error +action: + name: replace +swap: + (?:alumnae|alumni): graduates + (?:alumna|alumnus): graduate + air(?:m[ae]n|wom[ae]n): pilot(s) + anchor(?:m[ae]n|wom[ae]n): anchor(s) + authoress: author + camera(?:m[ae]n|wom[ae]n): camera operator(s) + chair(?:m[ae]n|wom[ae]n): chair(s) + congress(?:m[ae]n|wom[ae]n): member(s) of congress + door(?:m[ae]|wom[ae]n): concierge(s) + draft(?:m[ae]n|wom[ae]n): drafter(s) + fire(?:m[ae]n|wom[ae]n): firefighter(s) + fisher(?:m[ae]n|wom[ae]n): fisher(s) + fresh(?:m[ae]n|wom[ae]n): first-year student(s) + garbage(?:m[ae]n|wom[ae]n): waste collector(s) + lady lawyer: lawyer + ladylike: courteous + landlord: building manager + mail(?:m[ae]n|wom[ae]n): mail carriers + man and wife: husband and wife + man enough: strong enough + mankind: human kind + manmade: manufactured + men and girls: men and women + middle(?:m[ae]n|wom[ae]n): intermediary + news(?:m[ae]n|wom[ae]n): journalist(s) + ombuds(?:man|woman): ombuds + oneupmanship: upstaging + poetess: poet + police(?:m[ae]n|wom[ae]n): police officer(s) + repair(?:m[ae]n|wom[ae]n): technician(s) + sales(?:m[ae]n|wom[ae]n): salesperson or sales people + service(?:m[ae]n|wom[ae]n): soldier(s) + steward(?:ess)?: flight attendant + tribes(?:m[ae]n|wom[ae]n): tribe member(s) + waitress: waiter + woman doctor: doctor + woman scientist[s]?: scientist(s) + work(?:m[ae]n|wom[ae]n): worker(s) diff --git a/docs/.vale/styles/proselint/GroupTerms.yml b/docs/.vale/styles/proselint/GroupTerms.yml new file mode 100644 index 0000000..7a59fa4 --- /dev/null +++ b/docs/.vale/styles/proselint/GroupTerms.yml @@ -0,0 +1,39 @@ +extends: substitution +message: Consider using '%s' instead of '%s'. +ignorecase: true +action: + name: replace +swap: + (?:bunch|group|pack|flock) of chickens: brood of chickens + (?:bunch|group|pack|flock) of crows: murder of crows + (?:bunch|group|pack|flock) of hawks: cast of hawks + (?:bunch|group|pack|flock) of parrots: pandemonium of parrots + (?:bunch|group|pack|flock) of peacocks: muster of peacocks + (?:bunch|group|pack|flock) of penguins: muster of penguins + (?:bunch|group|pack|flock) of sparrows: host of sparrows + (?:bunch|group|pack|flock) of turkeys: rafter of turkeys + (?:bunch|group|pack|flock) of woodpeckers: descent of woodpeckers + (?:bunch|group|pack|herd) of apes: shrewdness of apes + (?:bunch|group|pack|herd) of baboons: troop of baboons + (?:bunch|group|pack|herd) of badgers: cete of badgers + (?:bunch|group|pack|herd) of bears: sloth of bears + (?:bunch|group|pack|herd) of bullfinches: bellowing of bullfinches + (?:bunch|group|pack|herd) of bullocks: drove of bullocks + (?:bunch|group|pack|herd) of caterpillars: army of caterpillars + (?:bunch|group|pack|herd) of cats: clowder of cats + (?:bunch|group|pack|herd) of colts: rag of colts + (?:bunch|group|pack|herd) of crocodiles: bask of crocodiles + (?:bunch|group|pack|herd) of dolphins: school of dolphins + (?:bunch|group|pack|herd) of foxes: skulk of foxes + (?:bunch|group|pack|herd) of gorillas: band of gorillas + (?:bunch|group|pack|herd) of hippopotami: bloat of hippopotami + (?:bunch|group|pack|herd) of horses: drove of horses + (?:bunch|group|pack|herd) of jellyfish: fluther of jellyfish + (?:bunch|group|pack|herd) of kangeroos: mob of kangeroos + (?:bunch|group|pack|herd) of monkeys: troop of monkeys + (?:bunch|group|pack|herd) of oxen: yoke of oxen + (?:bunch|group|pack|herd) of rhinoceros: crash of rhinoceros + (?:bunch|group|pack|herd) of wild boar: sounder of wild boar + (?:bunch|group|pack|herd) of wild pigs: drift of wild pigs + (?:bunch|group|pack|herd) of zebras: zeal of wild pigs + (?:bunch|group|pack|school) of trout: hover of trout diff --git a/docs/.vale/styles/proselint/Hedging.yml b/docs/.vale/styles/proselint/Hedging.yml new file mode 100644 index 0000000..a8615f8 --- /dev/null +++ b/docs/.vale/styles/proselint/Hedging.yml @@ -0,0 +1,8 @@ +extends: existence +message: "'%s' is hedging." +ignorecase: true +level: error +tokens: + - I would argue that + - ', so to speak' + - to a certain degree diff --git a/docs/.vale/styles/proselint/Hyperbole.yml b/docs/.vale/styles/proselint/Hyperbole.yml new file mode 100644 index 0000000..0361772 --- /dev/null +++ b/docs/.vale/styles/proselint/Hyperbole.yml @@ -0,0 +1,6 @@ +extends: existence +message: "'%s' is hyperbolic." +level: error +nonword: true +tokens: + - '[a-z]+[!?]{2,}' diff --git a/docs/.vale/styles/proselint/Jargon.yml b/docs/.vale/styles/proselint/Jargon.yml new file mode 100644 index 0000000..2454a9c --- /dev/null +++ b/docs/.vale/styles/proselint/Jargon.yml @@ -0,0 +1,11 @@ +extends: existence +message: "'%s' is jargon." +ignorecase: true +level: error +tokens: + - in the affirmative + - in the negative + - agendize + - per your order + - per your request + - disincentivize diff --git a/docs/.vale/styles/proselint/LGBTOffensive.yml b/docs/.vale/styles/proselint/LGBTOffensive.yml new file mode 100644 index 0000000..eaf5a84 --- /dev/null +++ b/docs/.vale/styles/proselint/LGBTOffensive.yml @@ -0,0 +1,13 @@ +extends: existence +message: "'%s' is offensive. Remove it or consider the context." +ignorecase: true +tokens: + - fag + - faggot + - dyke + - sodomite + - homosexual agenda + - gay agenda + - transvestite + - homosexual lifestyle + - gay lifestyle diff --git a/docs/.vale/styles/proselint/LGBTTerms.yml b/docs/.vale/styles/proselint/LGBTTerms.yml new file mode 100644 index 0000000..efdf268 --- /dev/null +++ b/docs/.vale/styles/proselint/LGBTTerms.yml @@ -0,0 +1,15 @@ +extends: substitution +message: "Consider using '%s' instead of '%s'." +ignorecase: true +action: + name: replace +swap: + homosexual man: gay man + homosexual men: gay men + homosexual woman: lesbian + homosexual women: lesbians + homosexual people: gay people + homosexual couple: gay couple + sexual preference: sexual orientation + (?:admitted homosexual|avowed homosexual): openly gay + special rights: equal rights diff --git a/docs/.vale/styles/proselint/Malapropisms.yml b/docs/.vale/styles/proselint/Malapropisms.yml new file mode 100644 index 0000000..9699778 --- /dev/null +++ b/docs/.vale/styles/proselint/Malapropisms.yml @@ -0,0 +1,8 @@ +extends: existence +message: "'%s' is a malapropism." +ignorecase: true +level: error +tokens: + - the infinitesimal universe + - a serial experience + - attack my voracity diff --git a/docs/.vale/styles/proselint/Needless.yml b/docs/.vale/styles/proselint/Needless.yml new file mode 100644 index 0000000..807f3c7 --- /dev/null +++ b/docs/.vale/styles/proselint/Needless.yml @@ -0,0 +1,361 @@ +extends: substitution +message: Prefer '%s' over '%s' +ignorecase: true +action: + name: replace +swap: + '(?:cell phone|cell-phone)': cellphone + '(?:cliquey|cliquy)': cliquish + '(?:pygmean|pygmaen)': pygmy + '(?:retributional|retributionary)': retributive + '(?:revokable|revokeable)': revocable + abolishment: abolition + accessary: accessory + accreditate: accredit + accruement: accrual + accusee: accused + acquaintanceship: acquaintance + acquitment: acquittal + administrate: administer + administrated: administered + administrating: administering + adulterate: adulterous + advisatory: advisory + advocator: advocate + aggrievance: grievance + allegator: alleger + allusory: allusive + amative: amorous + amortizement: amortization + amphiboly: amphibology + anecdotalist: anecdotist + anilinctus: anilingus + anticipative: anticipatory + antithetic: antithetical + applicative: applicable + applicatory: applicable + applier: applicator + approbative: approbatory + arbitrager: arbitrageur + arsenous: arsenious + ascendance: ascendancy + ascendence: ascendancy + ascendency: ascendancy + auctorial: authorial + averral: averment + barbwire: barbed wire + benefic: beneficent + benignant: benign + bestowment: bestowal + betrothment: betrothal + blamableness: blameworthiness + butt naked: buck naked + camarade: comrade + carta blanca: carte blanche + casualities: casualties + casuality: casualty + catch on fire: catch fire + catholicly: catholically + cease fire: ceasefire + channelize: channel + chaplainship: chaplaincy + chrysalid: chrysalis + chrysalids: chrysalises + cigaret: cigarette + coemployee: coworker + cognitional: cognitive + cohabitate: cohabit + cohabitor: cohabitant + collodium: collodion + collusory: collusive + commemoratory: commemorative + commonty: commonage + communicatory: communicative + compensative: compensatory + complacence: complacency + complicitous: complicit + computate: compute + conciliative: conciliatory + concomitancy: concomitance + condonance: condonation + confirmative: confirmatory + congruency: congruence + connotate: connote + consanguineal: consanguine + conspicuity: conspicuousness + conspiratorialist: conspirator + constitutionist: constitutionalist + contingence: contigency + contributary: contributory + contumacity: contumacy + conversible: convertible + conveyal: conveyance + copartner: partner + copartnership: partnership + corroboratory: corroborative + cotemporaneous: contemporaneous + cotemporary: contemporary + criminate: incriminate + culpatory: inculpatory + cumbrance: encumbrance + cumulate: accumulate + curatory: curative + daredeviltry: daredevilry + deceptious: deceptive + defamative: defamatory + defraudulent: fraudulent + degeneratory: degenerative + delimitate: delimit + delusory: delusive + denouncement: denunciation + depositee: depositary + depreciative: depreciatory + deprival: deprivation + derogative: derogatory + destroyable: destructible + detoxicate: detoxify + detractory: detractive + deviancy: deviance + deviationist: deviant + digamy: deuterogamy + digitalize: digitize + diminishment: diminution + diplomatist: diplomat + disassociate: dissociate + disciplinatory: disciplinary + discriminant: discriminating + disenthrone: dethrone + disintegratory: disintegrative + dismission: dismissal + disorientate: disorient + disorientated: disoriented + disquieten: disquiet + distraite: distrait + divergency: divergence + dividable: divisible + doctrinary: doctrinaire + documental: documentary + domesticize: domesticate + duplicatory: duplicative + duteous: dutiful + educationalist: educationist + educatory: educative + enigmatas: enigmas + enlargen: enlarge + enswathe: swathe + epical: epic + erotism: eroticism + ethician: ethicist + ex officiis: ex officio + exculpative: exculpatory + exigeant: exigent + exigence: exigency + exotism: exoticism + expedience: expediency + expediential: expedient + expediential: expedient + extensible: extendable + eying: eyeing + fiefdom: fief + flagrance: flagrancy + flatulency: flatulence + fraudful: fraudulent + funebrial: funereal + geographical: geographic + geometrical: geometric + gerry-rigged: jury-rigged + goatherder: goatherd + gustatorial: gustatory + habitude: habit + henceforward: henceforth + hesitance: hesitancy + heterogenous: heterogeneous + hierarchic: hierarchical + hindermost: hindmost + honorand: honoree + hypostasize: hypostatize + hysteric: hysterical + idolatrize: idolize + impanel: empanel + imperviable: impervious + importunacy: importunity + impotency: impotence + imprimatura: imprimatur + improprietous: improper + inalterable: unalterable + incitation: incitement + incommunicative: uncommunicative + inconsistence: inconsistency + incontrollable: uncontrollable + incurment: incurrence + indow: endow + indue: endue + inhibitive: inhibitory + innavigable: unnavigable + innovational: innovative + inquisitional: inquisitorial + insistment: insistence + insolvable: unsolvable + instillment: instillation + instinctual: instinctive + insuror: insurer + insurrectional: insurrectionary + interpretate: interpret + intervenience: intervention + ironical: ironic + jerry-rigged: jury-rigged + judgmatic: judgmental + labyrinthian: labyrinthine + laudative: laudatory + legitimatization: legitimation + legitimatize: legitimize + legitimization: legitimation + lengthways: lengthwise + life-sized: life-size + liquorice: licorice + lithesome: lithe + lollipop: lollypop + loth: loath + lubricous: lubricious + maihem: mayhem + medicinal marijuana: medical marijuana + meliorate: ameliorate + minimalize: minimize + mirk: murk + mirky: murky + misdoubt: doubt + monetarize: monetize + moveable: movable + narcism: narcissism + neglective: neglectful + negligency: negligence + neologizer: neologist + neurologic: neurological + nicknack: knickknack + nictate: nictitate + nonenforceable: unenforceable + normalcy: normality + numbedness: numbness + omittable: omissible + onomatopoetic: onomatopoeic + opinioned: opined + optimum advantage: optimal advantage + orientate: orient + outsized: outsize + oversized: oversize + overthrowal: overthrow + pacificist: pacifist + paederast: pederast + parachronism: anachronism + parti-color: parti-colored + participative: participatory + party-colored: parti-colored + pediatrist: pediatrician + penumbrous: penumbral + perjorative: pejorative + permissory: permissive + permutate: permute + personation: impersonation + pharmaceutic: pharmaceutical + pleuritis: pleurisy + policy holder: policyholder + policyowner: policyholder + politicalize: politicize + precedency: precedence + preceptoral: preceptorial + precipitance: precipitancy + precipitant: precipitate + preclusory: preclusive + precolumbian: pre-Columbian + prefectoral: prefectorial + preponderately: preponderantly + preserval: preservation + preventative: preventive + proconsulship: proconsulate + procreational: procreative + procurance: procurement + propelment: propulsion + propulsory: propulsive + prosecutive: prosecutory + protectory: protective + provocatory: provocative + pruriency: prurience + psychal: psychical + punitory: punitive + quantitate: quantify + questionary: questionnaire + quiescency: quiescence + rabbin: rabbi + reasonability: reasonableness + recidivistic: recidivous + recriminative: recriminatory + recruital: recruitment + recurrency: recurrence + recusance: recusancy + recusation: recusal + recusement: recusal + redemptory: redemptive + referrable: referable + referrible: referable + refutatory: refutative + remitment: remittance + remittal: remission + renouncement: renunciation + renunciable: renounceable + reparatory: reparative + repudiative: repudiatory + requitement: requital + rescindment: rescission + restoral: restoration + reticency: reticence + reviewal: review + revisal: revision + revisional: revisionary + revolute: revolt + saliency: salience + salutiferous: salutary + sensatory: sensory + sessionary: sessional + shareowner: shareholder + sicklily: sickly + signator: signatory + slanderize: slander + societary: societal + sodomist: sodomite + solicitate: solicit + speculatory: speculative + spiritous: spirituous + statutorial: statutory + submergeable: submersible + submittal: submission + subtile: subtle + succuba: succubus + sufficience: sufficiency + suppliant: supplicant + surmisal: surmise + suspendible: suspendable + synthetize: synthesize + systemize: systematize + tactual: tactile + tangental: tangential + tautologous: tautological + tee-shirt: T-shirt + thenceforward: thenceforth + transiency: transience + transposal: transposition + transposal: transposition + unfrequent: infrequent + unreasonability: unreasonableness + unrevokable: irrevocable + unsubstantial: insubstantial + usurpature: usurpation + variative: variational + vegetive: vegetative + vindicative: vindictive + vituperous: vituperative + vociferant: vociferous + volitive: volitional + wolverene: wolverine + wolvish: wolfish + Zoroastrism: Zoroastrianism + diff --git a/docs/.vale/styles/proselint/Nonwords.yml b/docs/.vale/styles/proselint/Nonwords.yml new file mode 100644 index 0000000..57e7b9e --- /dev/null +++ b/docs/.vale/styles/proselint/Nonwords.yml @@ -0,0 +1,38 @@ +extends: substitution +message: "Consider using '%s' instead of '%s'." +ignorecase: true +level: error +action: + name: replace +swap: + affrontery: effrontery + analyzation: analysis + annoyment: annoyance + confirmant: confirmand + confirmants: confirmands + conversate: converse + crained: cranded + discomforture: discomfort|discomfiture + dispersement: disbursement|dispersal + doubtlessly: doubtless|undoubtedly + forebearance: forbearance + improprietous: improper + inclimate: inclement + inimicable: inimical + irregardless: regardless + minimalize: minimize + minimalized: minimized + minimalizes: minimizes + minimalizing: minimizing + optimalize: optimize + paralyzation: paralysis + pettifogger: pettifog + proprietous: proper + relative inexpense: relatively low price|affordability + seldomly: seldom + thusly: thus + uncategorically: categorically + undoubtably: undoubtedly|indubitably + unequivocable: unequivocal + unmercilessly: mercilessly + unrelentlessly: unrelentingly|relentlessly diff --git a/docs/.vale/styles/proselint/Oxymorons.yml b/docs/.vale/styles/proselint/Oxymorons.yml new file mode 100644 index 0000000..25fd2aa --- /dev/null +++ b/docs/.vale/styles/proselint/Oxymorons.yml @@ -0,0 +1,22 @@ +extends: existence +message: "'%s' is an oxymoron." +ignorecase: true +level: error +tokens: + - amateur expert + - increasingly less + - advancing backwards + - alludes explicitly to + - explicitly alludes to + - totally obsolescent + - completely obsolescent + - generally always + - usually always + - increasingly less + - build down + - conspicuous absence + - exact estimate + - found missing + - intense apathy + - mandatory choice + - organized mess diff --git a/docs/.vale/styles/proselint/P-Value.yml b/docs/.vale/styles/proselint/P-Value.yml new file mode 100644 index 0000000..8230938 --- /dev/null +++ b/docs/.vale/styles/proselint/P-Value.yml @@ -0,0 +1,6 @@ +extends: existence +message: "You should use more decimal places, unless '%s' is really true." +ignorecase: true +level: suggestion +tokens: + - 'p = 0\.0{2,4}' diff --git a/docs/.vale/styles/proselint/RASSyndrome.yml b/docs/.vale/styles/proselint/RASSyndrome.yml new file mode 100644 index 0000000..deae9c7 --- /dev/null +++ b/docs/.vale/styles/proselint/RASSyndrome.yml @@ -0,0 +1,30 @@ +extends: existence +message: "'%s' is redundant." +level: error +action: + name: edit + params: + - split + - ' ' + - '0' +tokens: + - ABM missile + - ACT test + - ABM missiles + - ABS braking system + - ATM machine + - CD disc + - CPI Index + - GPS system + - GUI interface + - HIV virus + - ISBN number + - LCD display + - PDF format + - PIN number + - RAS syndrome + - RIP in peace + - please RSVP + - SALT talks + - SAT test + - UPC codes diff --git a/docs/.vale/styles/proselint/Skunked.yml b/docs/.vale/styles/proselint/Skunked.yml new file mode 100644 index 0000000..96a1f69 --- /dev/null +++ b/docs/.vale/styles/proselint/Skunked.yml @@ -0,0 +1,13 @@ +extends: existence +message: "'%s' is a bit of a skunked term — impossible to use without issue." +ignorecase: true +level: error +tokens: + - bona fides + - deceptively + - decimate + - effete + - fulsome + - hopefully + - impassionate + - Thankfully diff --git a/docs/.vale/styles/proselint/Spelling.yml b/docs/.vale/styles/proselint/Spelling.yml new file mode 100644 index 0000000..d3c9be7 --- /dev/null +++ b/docs/.vale/styles/proselint/Spelling.yml @@ -0,0 +1,17 @@ +extends: consistency +message: "Inconsistent spelling of '%s'." +level: error +ignorecase: true +either: + advisor: adviser + centre: center + colour: color + emphasise: emphasize + finalise: finalize + focussed: focused + labour: labor + learnt: learned + organise: organize + organised: organized + organising: organizing + recognise: recognize diff --git a/docs/.vale/styles/proselint/Typography.yml b/docs/.vale/styles/proselint/Typography.yml new file mode 100644 index 0000000..60283eb --- /dev/null +++ b/docs/.vale/styles/proselint/Typography.yml @@ -0,0 +1,11 @@ +extends: substitution +message: Consider using the '%s' symbol instead of '%s'. +level: error +nonword: true +swap: + '\.\.\.': … + '\([cC]\)': © + '\(TM\)': ™ + '\(tm\)': ™ + '\([rR]\)': ® + '[0-9]+ ?x ?[0-9]+': × diff --git a/docs/.vale/styles/proselint/Uncomparables.yml b/docs/.vale/styles/proselint/Uncomparables.yml new file mode 100644 index 0000000..9b96f42 --- /dev/null +++ b/docs/.vale/styles/proselint/Uncomparables.yml @@ -0,0 +1,50 @@ +extends: existence +message: "'%s' is not comparable" +ignorecase: true +level: error +action: + name: edit + params: + - split + - ' ' + - '1' +raw: + - \b(?:absolutely|most|more|less|least|very|quite|largely|extremely|increasingly|kind of|mildy|hardly|greatly|sort of)\b\s* +tokens: + - absolute + - adequate + - complete + - correct + - certain + - devoid + - entire + - 'false' + - fatal + - favorite + - final + - ideal + - impossible + - inevitable + - infinite + - irrevocable + - main + - manifest + - only + - paramount + - perfect + - perpetual + - possible + - preferable + - principal + - singular + - stationary + - sufficient + - 'true' + - unanimous + - unavoidable + - unbroken + - uniform + - unique + - universal + - void + - whole diff --git a/docs/.vale/styles/proselint/Very.yml b/docs/.vale/styles/proselint/Very.yml new file mode 100644 index 0000000..e4077f7 --- /dev/null +++ b/docs/.vale/styles/proselint/Very.yml @@ -0,0 +1,6 @@ +extends: existence +message: "Remove '%s'." +ignorecase: true +level: error +tokens: + - very diff --git a/docs/.vale/styles/proselint/meta.json b/docs/.vale/styles/proselint/meta.json new file mode 100644 index 0000000..e3c6580 --- /dev/null +++ b/docs/.vale/styles/proselint/meta.json @@ -0,0 +1,17 @@ +{ + "author": "jdkato", + "description": "A Vale-compatible implementation of the proselint linter.", + "email": "support@errata.ai", + "lang": "en", + "url": "https://github.com/errata-ai/proselint/releases/latest/download/proselint.zip", + "feed": "https://github.com/errata-ai/proselint/releases.atom", + "issues": "https://github.com/errata-ai/proselint/issues/new", + "license": "BSD-3-Clause", + "name": "proselint", + "sources": [ + "https://github.com/amperser/proselint" + ], + "vale_version": ">=1.0.0", + "coverage": 0.0, + "version": "0.1.0" +} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 9d6cc86..0000000 --- a/docs/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Website - -This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. - -### Installation - -``` -$ npm install -``` - -### Local Development - -``` -$ npm run start -``` - -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. - -### Build - -``` -$ npm run build -``` - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - diff --git a/docs/docs/getting-started/configuration-options.md b/docs/docs/getting-started/configuration-options.md index 18daea8..48e32dc 100644 --- a/docs/docs/getting-started/configuration-options.md +++ b/docs/docs/getting-started/configuration-options.md @@ -4,12 +4,21 @@ Kulala can be configured with the following options. ### Full example -Here is a full example of setting up the Kulala plugin with the available `opts`: +Here is a full example of setting up +the Kulala plugin with the available `opts`: ```lua title="kulala.lua" { "mistweaverco/kulala.nvim", opts = { + -- cURL path + -- if you have curl installed in a non-standard path, + -- you can specify it here + curl_path = "curl", + + -- Display mode, possible values: "split", "float" + display_mode = "split", + -- split direction -- possible values: "vertical", "horizontal" split_direction = "vertical", @@ -81,7 +90,7 @@ Here is a full example of setting up the Kulala plugin with the available `opts` winbar = false, -- Specify the panes to be displayed by default - -- Current avaliable pane contains { "body", "headers", "headers_body", "script_output" }, + -- Current available pane contains { "body", "headers", "headers_body", "script_output", "stats" }, default_winbar_panes = { "body", "headers", "headers_body" }, -- enable reading vscode rest client environment variables @@ -90,9 +99,51 @@ Here is a full example of setting up the Kulala plugin with the available `opts` -- disable the vim.print output of the scripts -- they will be still written to disk, but not printed immediately disable_script_print_output = false, + -- set scope for environment and request variables -- possible values: b = buffer, g = global environment_scope = "b", + + -- certificates + certificates = {}, + }, +} +``` + +### curl_path + +cURL path. + +If you have `curl` installed in a non-standard path, you can specify it here. + +Default: `curl` + +Example: + +```lua +{ + "mistweaverco/kulala.nvim", + opts = { + curl_path = "/home/bonobo/.local/bin/curl", + }, +} +``` + +### display_mode + +The display mode. + +Can be either `split` or `float`. + +Default: `split` + +Example: + +```lua +{ + "mistweaverco/kulala.nvim", + opts = { + display_mode = "float", }, } ``` @@ -101,6 +152,8 @@ Here is a full example of setting up the Kulala plugin with the available `opts` Split direction. +Only used when `display_mode` is set to `split`. + Possible values: - `vertical` @@ -128,6 +181,8 @@ Possible values: - `body` - `headers` - `headers_body` +- `script_output` +- `stats` Default: `body` @@ -146,7 +201,7 @@ Example: Default environment. -See: https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-8.0#environment-files +See: [Environment files][see-env-files]. Possible values: @@ -189,7 +244,8 @@ Example: ### contenttypes -Filetypes, formatters and path resolvers are defined for each content-type in an hash array +Filetypes, formatters and path resolvers are +defined for each content-type in an hash array. Default: @@ -253,13 +309,16 @@ Example: #### contenttypes.formatter -Formatters take the response body and produce a beautified / more human readable output. +Formatters take the response body and +produce a beautified / more human readable output. Possible values: - You can define a commandline which processes the body. - The body will be piped as stdin and the output will be used as the formatted body. -- You can define a lua function `formatted_body = function(body)` which returns the formatted body. + The body will be piped as stdin and + the output will be used as the formatted body. +- You can define a lua function `formatted_body = function(body)` + which returns the formatted body. Default: @@ -297,19 +356,27 @@ Example: #### contenttypes.pathresolver You can use Request Variables to read values from requests / responses. -To access a specific value inside a body Kulala gives you the possibility to define a path for it. -This is normally JSONPath for JSON or XPath for XML but can be individually defined for any content type. +To access a specific value inside a body Kulala gives +you the possibility to define a path for it. + +This is normally JSONPath for JSON or XPath for XML, +but can be individually defined for any content type. Possible values: -- You can use an external program which receives the full body as stdin and has to return the selected value in stdout. - The placeholder `{{path}}` can be used in any string of this defintion and will be replaced by the actual path (after `body.`). +- You can use an external program which receives the + full body as stdin and has to return the selected value in stdout. + The placeholder `{{path}}` can be used in any string of + this definition and will be replaced by the actual path (after `body.`). - Alternative you can give a lua function of `value = function(body, path)`. Default: -Kulala has implemented a simple JSONPath parser which supports object traversal including array index access. -For full JSONPath support you need to use an external program like `jsonpath-cli` or `jp`. +Kulala has implemented a basic JSONPath parser which +supports object traversal including array index access. + +For full JSONPath support you need to use an +external program like `jsonpath-cli` or `jp`. ```lua contenttypes = { @@ -423,7 +490,7 @@ Example: Scratchpad default contents. -The contents of the scratchpad when it is opened +The contents of the scratchpad when it's opened via `:lua require('kulala').scratchpad()` command. Possible values: @@ -493,9 +560,13 @@ Example: ### vscode_rest_client_environmentvars -If enabled, Kulala searches for `.vscode/settings.json` or `*.code-workspace` files in the current directory and its parents to read the `rest-client.environmentVariables` definitions (`$shared` will be treated as `_base`). +If enabled, Kulala searches for +`.vscode/settings.json` or `*.code-workspace` +files in the current directory and +its parents to read the `rest-client.environmentVariables` definitions. -If `http-client.env.json` is also present, it will be merged (and overwrites variables from VSCode). +If `http-client.env.json` is also present, +it'll be merged (and overwrites variables from VSCode). Possible values: @@ -514,10 +585,11 @@ Example: }, } ``` + ### environment_scope While using request variables the results will be stored for later use. -As usual variables they are file relevant and should be stored in the buffer. +As usual variables they're file relevant and should be stored in the buffer. If you want to share the variables between buffers you can use the global scope. Possible values: @@ -537,3 +609,48 @@ Example: }, } ``` + + +### certificates + +A hash array of certificates to be used for requests. + +The key is the hostname and optional the port. +If no port is given, the certificate will be used for all ports where no dedicated one is defined. + +Each certificate definition needs + +- `cert` the path to the certificate file +- `key` the path to the key files + +Example: + +```lua +{ +"mistweaverco/kulala.nvim", + opts = { + certificates = { + ["localhost"] = { + cert = vim.fn.stdpath("config") .. "/certs/localhost.crt", + key = vim.fn.stdpath("config") .. "/certs/localhost.key", + }, + ["www.somewhere.com:8443"] = { + cert = "/home/userx/certs/somewhere.crt", + key = "/home/userx/certs/somewhere.key", + }, + }, + }, +} +``` + +Hostnames with prefix `*.` will be used as wildcard certificates for the host itself and all subdomains. + +`*.company.com` will match + +- `company.com` +- `www.company.com` +- `api.company.com` +- `sub.api.company.com` +- etc. + +[see-env-files]: https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-8.0#environment-files diff --git a/docs/docs/getting-started/example-configuration.md b/docs/docs/getting-started/example-configuration.md new file mode 100644 index 0000000..46ea3b5 --- /dev/null +++ b/docs/docs/getting-started/example-configuration.md @@ -0,0 +1,121 @@ +# Example configuration + +This is an example configuration for `kulala`. + +This helps you get started with `kulala` and +provides a basic configuration to use it. + +## Configuration file + +Create `ftplugin/http.lua` in your configuration directory. + +This file will be loaded when you open a file with the `http` filetype. + +### Execute request + +Add the following code to `ftplugin/http.lua` to +run the http request when you press Enter. + +```lua ftplugin/http.lua +vim.api.nvim_buf_set_keymap( + 0, + "n", + "", + "lua require('kulala').run()", + { noremap = true, silent = true, desc = "Execute the request" } +) +``` + +### Jump between requests + +Add the following code to `ftplugin/http.lua` to +jump between requests when you press `]` and `[`. + +```lua ftplugin/http.lua +vim.api.nvim_buf_set_keymap( + 0, + "n", + "[", + "lua require('kulala').jump_prev()", + { noremap = true, silent = true, desc = "Jump to the previous request" } +) +vim.api.nvim_buf_set_keymap( + 0, + "n", + "]", + "lua require('kulala').jump_next()", + { noremap = true, silent = true, desc = "Jump to the next request" } +) +``` + +### Inspect the current request + +Add the following code to `ftplugin/http.lua` to +inspect the current request when you press `i`. + +```lua ftplugin/http.lua +vim.api.nvim_buf_set_keymap( + 0, + "n", + "i", + "lua require('kulala').inspect()", + { noremap = true, silent = true, desc = "Inspect the current request" } +) +``` + +### Toggle body and headers + +Add the following code to `ftplugin/http.lua` to +toggle between body and headers when you press `t`. + +```lua ftplugin/http.lua +vim.api.nvim_buf_set_keymap( + 0, + "n", + "t", + "lua require('kulala').toggle_view()", + { noremap = true, silent = true, desc = "Toggle between body and headers" } +) +``` + +### Copy as curl + +Add the following code to `ftplugin/http.lua` to +copy the current request as a curl command when you press `co`. + +:::tip + +Mnemonic: `co` for `curl out`. + +::: + +```lua ftplugin/http.lua +vim.api.nvim_buf_set_keymap( + 0, + "n", + "co", + "lua require('kulala').copy()", + { noremap = true, silent = true, desc = "Copy the current request as a curl command" } +) +``` + +### Insert from curl + +Add the following code to `ftplugin/http.lua` to insert from a curl command +in your clipboard when you press `ci`. + +:::tip + +Mnemonic: `ci` for `curl in`. + +::: + +```lua ftplugin/http.lua +vim.api.nvim_buf_set_keymap( + 0, + "n", + "ci", + "lua require('kulala').from_curl()", + { noremap = true, silent = true, desc = "Paste curl from clipboard as http request" } +) +``` diff --git a/docs/docs/getting-started/install.md b/docs/docs/getting-started/install.md index fbb7189..2b46b3c 100644 --- a/docs/docs/getting-started/install.md +++ b/docs/docs/getting-started/install.md @@ -10,7 +10,7 @@ Requires Neovim 0.10.0+ Via [lazy.nvim](https://github.com/folke/lazy.nvim): -### Simple configuration +### Basic configuration ```lua title="init.lua" require('lazy').setup({ diff --git a/docs/docs/getting-started/requirements.md b/docs/docs/getting-started/requirements.md index ff53929..d51c437 100644 --- a/docs/docs/getting-started/requirements.md +++ b/docs/docs/getting-started/requirements.md @@ -8,7 +8,7 @@ List of requirements for using kulala. ### Syntax Highlighting -- [Treesitter for HTTP](https://github.com/nvim-treesitter/nvim-treesitter?tab=readme-ov-file#supported-languages) (`:TSInstall http`) +- [Treesitter for HTTP][ts] ## cURL @@ -16,15 +16,21 @@ List of requirements for using kulala. ## jq -- [jq](https://stedolan.github.io/jq/) (tested with 1.7) (Only required for formatted JSON responses) +- [jq](https://stedolan.github.io/jq/) (tested with 1.7) + +(Only required for formatted JSON responses) ## xmllint -- [xmllint](https://packages.ubuntu.com/noble/libxml2-utils) (tested with libxml v20914) (Only required for formatted XML/HTML responses and resolving XML request variables) +- [xmllint][xmllint] (tested with libxml v20914) + +(Only required for formatted XML/HTML responses and +resolving XML request variables) # Optional Requirements -To make things a lot easier, you can put this lua snippet somewhere in your configuration: +To make things a lot easier, +you can put this lua snippet somewhere in your configuration: ```lua vim.filetype.add({ @@ -36,21 +42,5 @@ vim.filetype.add({ This will make Neovim recognize files with the `.http` extension as HTTP files. -> Custom keymappings will make your life easier. -> Here is an example of how you can set it up. - -- Create `ftplugin` directory inside `~/.config/nvim`. -- Inside `ftplugin` directory create a file `http.lua`. -- Inside `http.lua` define a key mapping for running kulala. - -```lua -vim.api.nvim_set_keymap("n", "", ":lua require('kulala').jump_prev()", { noremap = true, silent = true }) -vim.api.nvim_set_keymap("n", "", ":lua require('kulala').jump_next()", { noremap = true, silent = true }) -vim.api.nvim_set_keymap("n", "", ":lua require('kulala').run()", { noremap = true, silent = true }) -``` - -This will allow you to: - -- run `kulala` by pressing `Ctrl + l` in normal mode. -- jump to the previous request by pressing `Ctrl + j` in normal mode. -- jump to the next request by pressing `Ctrl + k` in normal mode. +[ts]: https://github.com/nvim-treesitter/nvim-treesitter?tab=readme-ov-file#supported-languages +[xmllint]: https://packages.ubuntu.com/noble/libxml2-utils diff --git a/docs/docs/scripts/client-reference.md b/docs/docs/scripts/client-reference.md index 9e6c265..8a1cfab 100644 --- a/docs/docs/scripts/client-reference.md +++ b/docs/docs/scripts/client-reference.md @@ -2,5 +2,81 @@ These helper functions are available in the client object in scripts. -- `client.global.set(key, value)` - Set a global variable. Persisted across script runs and Neovim restarts. -- `client.global.get(key)` - Get a global variable. +## client.global.set + +Set a variable. + +Variables are persisted across script runs and Neovim restarts. + +```javascript +client.global.set("SOME_TOKEN", "123"); +``` + +## client.global.get + +Get a variable. + +Variables are persisted across script runs and Neovim restarts. + +```javascript +client.log(client.global.get("SOME_TOKEN")); +``` + +## client.log + +Logs arbitrary data to the console. + +```javascript +client.log("Hello, world!"); +``` + +## client.test + +:::warning + +Not yet implemented. + +::: + +## client.assert + +:::warning + +Not yet implemented. + +::: + +## client.exit + +Terminates execution of the response handler script. + +```javascript +client.exit(); +``` +### client.isEmpty + +Checks whether the `global` object has no variables defined. + +```javascript +const isEmpty = client.isEmpty(); +if (isEmpty) { + client.log("No global variables defined"); +} +``` + + +## client.clear + +Removes the `varName` variable from the global variables storage. + +```javascript +client.clear("SOME_TOKEN"); +``` + +## client.clearAll + +Removes all variables from the global variables storage. + +```javascript +client.clearAll(); +``` diff --git a/docs/docs/scripts/overview.md b/docs/docs/scripts/overview.md index b1a7a50..be322f6 100644 --- a/docs/docs/scripts/overview.md +++ b/docs/docs/scripts/overview.md @@ -30,7 +30,8 @@ Given the following folder structure: The current working directory for `my-script.js` is the `scripts` directory, whereas the current working directory for `example.js` is the `http` directory. -All inline scripts are executed in the current working directory of the HTTP file, +All inline scripts are executed in the +current working directory of the HTTP file, which is the `http` directory in this case. ### Using node modules @@ -53,7 +54,8 @@ You can use the `require` function to import modules in `my-script.js`: const moment = require("moment"); ``` -as long as the module is installed in the same directory as the script, or globally. +as long as the module is installed in the +same directory as the script, or globally. The current working directory for `my-script.js` is the `scripts` directory. @@ -63,7 +65,6 @@ So want to write a file in the `http` directory, you can use a relative path: const fs = require("fs"); fs.writeFileSync("../http/my-file.txt", "Hello, world!"); ``` - ## Pre-request ```http title="./pre-request-example.http" @@ -112,7 +113,8 @@ content-type: application/json :::tip -Variables set via `request.variables.set` are only available in the current request. +Variables set via `request.variables.set` are +only available in the current request. ::: @@ -126,77 +128,9 @@ Variables set via `client.global.set` are available in all requests and persist between neovim restarts. To clear a global variable, -run [`lua require('kulala').scripts_clear_global('BONOBO')`](../usage/public-methods#scripts_clear_global). - -::: - -```plaintext title="./TOKEN.txt" -THIS_IS_SOME_TOKEN_VALUE_123 -``` - -## Pre-request - -```http title="./pre-request-example.http" -# @name REQUEST_ONE -< {% - var crypto = require('crypto'); - var fs = require('fs'); - var TOKEN = fs.readFileSync('TOKEN.txt', 'utf8').trim(); - request.variables.set('GORILLA', TOKEN); - request.variables.set('PASSWORD', crypto.randomBytes(16).toString('hex')); -%} -< ./pre-request.js -POST https://httpbin.org/post HTTP/1.1 -Accept: application/json -Content-Type: application/json -Authorization: Bearer Foo:bar +run `lua require('kulala').scripts_clear_global('BONOBO')`. -{ - "token": "{{GORILLA}}", - "password": "{{PASSWORD}}", - "deep": { - "nested": [ - { - "key": "foo" - }, - { - "key": "{{BONOBO}}" - } - ] - } -} - -### - -# @name REQUEST_TWO -POST https://httpbin.org/post HTTP/1.1 -accept: application/json -content-type: application/json - -{ - "token": "{{REQUEST_ONE.response.body.$.json.token}}", - "nested": "{{REQUEST_ONE.response.body.$.json.deep.nested[1].key}}", - "gorilla": "{{GORILLA}}" -} -``` - -:::tip - -Variables set via `request.variables.set` are only available in the current request. - -::: - -```javascript title="./pre-request.js" -client.global.set("BONOBO", "bar"); -``` - -:::tip - -Variables set via `client.global.set` are available in all requests and -persist between neovim restarts. - -To clear a global variable, -run [`lua require('kulala').scripts_clear_global('BONOBO')`](../usage/public-methods#scripts_clear_global). +See: [scripts_clear_global](../usage/public-methods#scripts_clear_global). ::: diff --git a/docs/docs/scripts/request-reference.md b/docs/docs/scripts/request-reference.md index 2512f01..ae1030d 100644 --- a/docs/docs/scripts/request-reference.md +++ b/docs/docs/scripts/request-reference.md @@ -2,5 +2,191 @@ These helper functions are available in the request object in scripts. -- `request.variables.get(key)` - Get a request variable. Request variables are only available for the duration of the request. -- `request.variables.set(key, value)` - Set a request variable. Request variables are only available for the duration of the request. +## request.variables.get + +Get a request variable. + +Request variables are only available for the duration of the request. + +```javascript +client.log(request.variables.get("SOME_TOKEN")); +``` + +## request.variables.set + +Set a request variable. + +Request variables are only available for the duration of the request. + +```javascript +request.variables.set("SOME_TOKEN, "123"); +client.log(request.variables.get("SOME_TOKEN")); +``` + +## request.body.getRaw + +Returns the request body `string` in +the raw format (or `undefined` if there is no body). + +If the body contains variables, +their names are displayed instead of their values. +For example: + +```javascript +client.log(request.body.getRaw()); +``` + +## request.body.tryGetSubstituted + +Returns the request body with variables substituted +(or `undefined` if there is no body). + +```javascript +client.log(request.body.tryGetSubstituted()); +``` + +## request.body.getComputed + +Returns the `string` request body as sent via curl; with variables substituted, +or `undefined` if there is no body. + +:::tip + +Useful if you want to see the request body as it was sent to the server. + +The `tryGetSubstituted` method will substitute variables with their values, +but leave the rest of the body as is. + +If you have a GraphQL query in the body, for example, the `getComputed` +method will show the query as it was sent to the server, +which is quite different from the substituted version. + +::: + +As an example, if you have a request body like this: + +```graphql +query getRestClient($name: String!) { + restclient(name: $name) { + id + name + editorsSupported { + name + } + } +} + +{ + "variables": { + "name": "{{ENV_VAR_CLIENT_NAME}}" + } +} +``` + +Then the `getComputed` method will +return the body as it was sent to the server: + +```json +{"query": "query getRestClient($name: String!) { restclient(name: $name) { id name editorsSupported { name } } } ", "variables": {"variables": {"name": "kulala"}}} +``` + +whereas the `tryGetSubstituted` method will +return the body with variables substituted as seen in your script: + +```graphql +query getRestClient($name: String!) { + restclient(name: $name) { + id + name + editorsSupported { + name + } + } +} + +{ + "variables": { + "name": "kulala" + } +} +``` + +:::warning + +The `getComputed` method is always `undefined` for binary bodies. + +::: + +## request.environment.get + + +Retrieves a value of the environment variable identified +by its name or returns null if it doesn't exist. + +```javascript +client.log(request.environment.get("SOME_ENV_VAR")); +``` + +## request.headers.all + +Returns all request header objects. + +Each header object has the following methods: + +- `name()` - Returns the name of the header. +- `getRawValue()` - Returns the value of the header in the raw format. +- `tryGetSubstituted()` - Returns the value of the header with variables substituted. + +```javascript +const headers = request.headers.all(); +for (const header of headers) { + client.log(header.name()); + client.log(header.getRawValue()); + client.log(header.tryGetSubstituted()); +} +``` + +## request.headers.findByName + +Returns a request header object identified by its name. + +The header object has the following methods: + +- `name()` - Returns the name of the header. +- `getRawValue()` - Returns the value of the header in the raw format. +- `tryGetSubstituted()` - Returns the value of the header with variables substituted. + +```javascript +const contentTypeHeader = request.headers.findByName("Content-Type"); +if (contentTypeHeader) { + client.log(contentTypeHeader.name()); + client.log(contentTypeHeader.getRawValue()); + client.log(contentTypeHeader.tryGetSubstituted()); +} +``` + +## request.method + +Returns the request method. + +Such as `GET`, `POST`, `PUT`, `DELETE`, etc. + +```javascript +client.log(request.method()); +``` + +## request.url.getRaw + +Returns the request URL in the raw format, without any substitutions. + +```javascript +client.log(request.url.getRaw()); +``` + +## request.url.tryGetSubstituted + +Returns the request URL with variables substituted. + +```javascript +client.log(request.url.tryGetSubstituted()); +``` diff --git a/docs/docs/scripts/response-reference.md b/docs/docs/scripts/response-reference.md index e1b8350..4e4850b 100644 --- a/docs/docs/scripts/response-reference.md +++ b/docs/docs/scripts/response-reference.md @@ -2,6 +2,24 @@ These helper functions are available in the response object in scripts. -- `reponse.body` - The response body, as a string, or json object if the response is json. -- `response.headers.valueOf(headerName)` - Get the value of a header. -- `response.headers.valuesOf(headerName)` - Retrieves the array containing all values of the headerName response header. Returns an empty array if the headerName response header does not exist. +## response.body + +The response body, as a string, or json object if the response is json. + +```javascript +client.log(response.body); +``` + +## response.headers + +Returns all response header objects. + +Each header object has the following methods: + +- `header.valueOf(headerName)` - Get the value of a header. +- `header.valuesOf(headerName)` - Retrieves the object containing all values of the headerName response header. Returns null if the headerName response header doesn't exist. + +```javascript +client.log(response.headers.valueOf("Content-Type")); +``` + diff --git a/docs/docs/usage/authentication.md b/docs/docs/usage/authentication.md index 14c2894..ed1214f 100644 --- a/docs/docs/usage/authentication.md +++ b/docs/docs/usage/authentication.md @@ -2,28 +2,46 @@ How to handle authentication in Kulala. -In general, you can use the `Authorization` header to send an authentication token to the server. +In general, you can use the `Authorization` header to +send an authentication token to the server. The content of the header depends on the type of authentication you are using. See these topics for more information: - [Sending form data](sending-form-data.md) -- [Dynamic environment variables](dynamically-setting-environment-variables-based-on-response-json.md) +- [Dynamic environment variables][dyn-env] - [Dotenv and environment files](dotenv-and-http-client.env.json-support) - [Request variables](request-variables.md) +## Supported Authentication Types + +Amazon Web Services (AWS) Signature version 4 is +a protocol for authenticating requests to AWS services. + +New Technology LAN Manager (NTLM), +is a suite of Microsoft security protocols that +provides authentication, integrity, and confidentiality to users. + +Basic, Digest, NTLM, Negotiate, Bearer Token, +AWS Signature V4 and SSL Client Certificates are supported. + ## Basic Authentication -Basic authentication needs a Base64 encoded string of `username:password` as the value of the `Authorization` header. +Basic authentication needs a +Base64 encoded string of `username:password` as +the value of the `Authorization` header. -If given it will be directly used in the HTTP request: +If given it'll be directly used in the HTTP request: ```http GET https://www/api HTTP/1.1 Authorization: Basic TXlVc2VyOlByaXZhdGU= ``` -Futhermore you can enter username and password in plain text in the `Authorization` header field, Kulala will automatically encode it for you. +Furthermore you can enter username and password in +plain text in the `Authorization` header field, +Kulala will automatically encode it for you. + There will be two possible ways to enter the credentials: ```http @@ -46,35 +64,45 @@ You can enter the `username:password` in plain text ```http GET https://www/api HTTP/1.1 -Authorization: Basic {{Username}}:{{Password}} +Authorization: Digest {{Username}}:{{Password}} ``` or `username password` ```http GET https://www/api HTTP/1.1 -Authorization: Basic {{Username}} {{Password}} +Authorization: Digest {{Username}} {{Password}} ``` ## NTLM Authentication -For NTLM authentication, you need to provide the username and password the same way: +For NTLM authentication, +you need to provide the username and password the same way: ```http GET https://www/api HTTP/1.1 -Authorization: Basic {{Username}}:{{Password}} +Authorization: NTLM {{Username}}:{{Password}} ``` or ```http GET https://www/api HTTP/1.1 -Authorization: Basic {{Username}} {{Password}} +Authorization: NTLM {{Username}} {{Password}} +``` + +or without any username where the current user is been used + +```http +GET https://www/api HTTP/1.1 +Authorization: NTLM ``` ## Negotiate -This is a SPNEGO-based implementation, which does not need username and password but uses the default credentials. +This is a SPNEGO-based implementation, +which doesn't need username and password, +but uses the default credentials. ```http GET https://www/api HTTP/1.1 @@ -83,7 +111,9 @@ Authorization: Negotiate ## Bearer Token -For a Bearer Token you need to send your credentials to an authentication endpoint and receive a token in return. +For a Bearer Token you need to send your credentials to +an authentication endpoint and receive a token in return. + This token is then used in the `Authorization` header for further requests. ### Sending the credentials @@ -97,7 +127,8 @@ Accept: application/json client_id={{ClientId}}&client_secret={{ClientSecret}}&grant_type=client_credentials&scope={{Scope}} ``` -This is a `login` named request with the credentials and the result may look like +This is a `login` named request with the credentials and +the result may look like ```json { @@ -107,7 +138,8 @@ This is a `login` named request with the credentials and the result may look lik } ``` -with the request variables feature from Kulala you can now access the `access_token` and use it in the next requests. +with the request variables feature from Kulala you +can now access the `access_token` and use it in the next requests. ```http GET {{apiURL}}/items HTTP/1.1 @@ -117,8 +149,12 @@ Authorization: Bearer {{login.response.body.$.access_token}} ## AWS Signature V4 +Amazon Web Services (AWS) Signature version 4 is a +protocol for authenticating requests to AWS services. + AWS Signature version 4 authenticates requests to AWS services. -To use it you need to set the Authorization header schema to AWS and provide your AWS credentials separated by spaces: +To use it you need to set the Authorization header schema to +AWS and provide your AWS credentials separated by spaces: ```plaintext : AWS Access Key Id @@ -132,3 +168,29 @@ service:: AWS Service GET {{apiUrl}}/ HTTP/1.1 Authorization: AWS token: region: service: ``` + +## SSL Client Certificate + +This is described in the configuration section and is done on a per-host basis. + +Example: + +```lua +{ +"mistweaverco/kulala.nvim", + opts = { + certificates = { + ["localhost"] = { + cert = vim.fn.stdpath("config") .. "/certs/localhost.crt", + key = vim.fn.stdpath("config") .. "/certs/localhost.key", + }, + ["www.somewhere.com:8443"] = { + cert = "/home/userx/certs/somewhere.crt", + key = "/home/userx/certs/somewhere.key", + }, + }, + }, +} +``` + +[dyn-env]: dynamically-setting-environment-variables-based-on-response-json.md diff --git a/docs/docs/usage/automatic-response-formatting.md b/docs/docs/usage/automatic-response-formatting.md index f7575d4..341488f 100644 --- a/docs/docs/usage/automatic-response-formatting.md +++ b/docs/docs/usage/automatic-response-formatting.md @@ -3,7 +3,11 @@ You can automatically format the response of an HTTP request. The response header will be parsed for the `Content-Type` value. -If the content type has been defined in the `contentypes` section of the configuration and there is a `formatter` available, the response will be processed by the given beautifier. +If the content type has been defined in +the `contentypes` section of the configuration. + +If there is a `formatter` available, +the response will be processed by the given beautifier. :::info diff --git a/docs/docs/usage/dotenv-and-http-client.env.json-support.md b/docs/docs/usage/dotenv-and-http-client.env.json-support.md index 1d0b8db..e051b82 100644 --- a/docs/docs/usage/dotenv-and-http-client.env.json-support.md +++ b/docs/docs/usage/dotenv-and-http-client.env.json-support.md @@ -3,9 +3,11 @@ Kulala supports environment variables in `.http` files. It allows you to define environment variables in a `.env` file or -in a `http-client.env.json` file (preferred) and reference them in your HTTP requests. +in a `http-client.env.json` file (preferred) and +reference them in your HTTP requests. -If you define the same environment variable in both the `.env` and the `http-client.env.json` file, +If you define the same environment variable in +both the `.env` and the `http-client.env.json` file, the value from the `http-client.env.json` file will be used. The order of the environment variables resolution is as follows: @@ -20,8 +22,8 @@ The usage of environment variables is optional, but if you want to use them, we would advise you to use the `http-client.env.json` file. -DotEnv is still supported, but it is not recommended, -because it is not as flexible as the `http-client.env.json` file. +DotEnv is still supported, but it's not recommended, +because it's not as flexible as the `http-client.env.json` file. ::: @@ -29,22 +31,28 @@ because it is not as flexible as the `http-client.env.json` file. You can also define environment variables via the `http-client.env.json` file. -Create a file `http-client.env.json` in the root of your `.http` files directory and +Create a file `http-client.env.json` in the root +of your `.http` files directory and define environment variables in it. ```json title="http-client.env.json" { + "$schema": "https://raw.githubusercontent.com/mistweaverco/kulala.nvim/main/schemas/http-client.env.schema.json", "dev": { - "API_KEY": "your-api-key" + "API_URL": "https://httpbin.org/post?env=dev", + "API_KEY": "" }, "testing": { - "API_KEY": "your-api-key" + "API_URL": "https://httpbin.org/post?env=testing", + "API_KEY": "" }, "staging": { - "API_KEY": "your-api-key" + "API_URL": "https://httpbin.org/post?env=staging", + "API_KEY": "" }, "prod": { - "API_KEY": "your-api-key" + "API_URL": "https://httpbin.org/post?env=prod", + "API_KEY": "" } } ``` @@ -57,9 +65,11 @@ You can freely define your own environment names. By default the `dev` environment is used. -This can be overridden by [setting the `default_env` configuration option](../getting-started/configuration-options). +This can be overridden by +[setting the `default_env` configuration option][config]. -To change the environment, you can use the `:lua require('kulala').set_selected_env('prod')` command. +To change the environment, +you can use the `:lua require('kulala').set_selected_env('prod')` command. :::tip @@ -68,10 +78,44 @@ command to select an environment using a telescope prompt. ::: -Then, you can reference the environment variables in your HTTP requests like this: +As you can see in the example above, +we defined the `API_URL` and `API_KEY` environment variables, +but left the `API_KEY` empty. + +This is by intention, because we can define the `API_KEY` in the +`http-client.private.env.json` file. + +:::danger + +You should never commit sensitive data like API keys to your repository. +So always use the `http-client.private.env.json` file for that and +add it to your `.gitignore` file. + +::: + +```json title="http-client.private.env.json" +{ + "$schema": "https://raw.githubusercontent.com/mistweaverco/kulala.nvim/main/schemas/http-client.private.env.schema.json", + "dev": { + "API_KEY": "d3v" + }, + "testing": { + "API_KEY": "t3st1ng" + }, + "staging": { + "API_KEY": "st4g1ng" + }, + "prod": { + "API_KEY": "pr0d" + } +} +``` + +Then, you can reference the environment variables +in your HTTP requests like this: ```http title="examples.http" -POST https://httpbin.org/post HTTP/1.1 +POST {{API_URL}} HTTP/1.1 Content-Type: application/json Authorization: Bearer {{API_KEY}} @@ -84,23 +128,26 @@ Authorization: Bearer {{API_KEY}} You can define default HTTP headers in the `http-client.env.json` file. -You need to put them in the special `_base` key and -the `DEFAULT_HEADERS` will be merged with the headers from the HTTP requests. +You need to put them in the special `$shared` property and +the `$default_headers` will be merged with the headers from the HTTP requests. ```json title="http-client.env.json" { - "_base": { - "DEFAULT_HEADERS": { + "$schema": "https://raw.githubusercontent.com/mistweaverco/kulala.nvim/main/schemas/http-client.env.schema.json", + "$shared": { + "$default_headers": { "Content-Type": "application/json", "Accept": "application/json" + }, }, "dev": { - "API_KEY": "your-api-key" + "API_URL": "https://httpbin.org/post?env=dev", + "API_KEY": "" } } ``` -Then, they are automatically added to the HTTP requests, +Then, they're automatically added to the HTTP requests, unless you override them. ```http title="examples.http" @@ -120,13 +167,15 @@ define environment variables in it. The file should look like this: ```env title=".env" +API_URL=https://httpbin.org/post API_KEY=your-api-key ``` -Then, you can reference the environment variables in your HTTP requests like this: +Then, you can reference the environment variables +in your HTTP requests like this: ```http title="examples.http" -POST https://httpbin.org/post HTTP/1.1 +POST {{API_URL}} HTTP/1.1 Content-Type: application/json Authorization: Bearer {{API_KEY}} @@ -135,3 +184,5 @@ Authorization: Bearer {{API_KEY}} } ``` +[config]: ../getting-started/configuration-options.md + diff --git a/docs/docs/usage/dynamically-setting-environment-variables-based-on-headers.md b/docs/docs/usage/dynamically-setting-environment-variables-based-on-headers.md index d511562..3fcfd81 100644 --- a/docs/docs/usage/dynamically-setting-environment-variables-based-on-headers.md +++ b/docs/docs/usage/dynamically-setting-environment-variables-based-on-headers.md @@ -4,11 +4,12 @@ You can set environment variables based on the headers of a HTTP request. Create a file with the `.http` extension and write your HTTP requests in it. -## Simple example +## Example The headers of the first request can be obtained and used in the second request. -In this example, the `Content-Type` and `Date` headers are received in the first request. +In this example, the `Content-Type` and `Date` headers are +received in the first request. ```http title="simple.http" # @name REQUEST_ONE diff --git a/docs/docs/usage/dynamically-setting-environment-variables-based-on-response-json.md b/docs/docs/usage/dynamically-setting-environment-variables-based-on-response-json.md index 8b87b6b..461270d 100644 --- a/docs/docs/usage/dynamically-setting-environment-variables-based-on-response-json.md +++ b/docs/docs/usage/dynamically-setting-environment-variables-based-on-response-json.md @@ -6,8 +6,9 @@ Create a file with the `.http` extension and write your HTTP requests in it. ## With built-in parser -If the response is a *simple* JSON object, -you can set environment variables using the [request variables](request-variables) feature. +If the response is a *uncomplicated* JSON object, +you can set environment variables using +the [request variables](request-variables) feature. ```http title="with-builtin-parser.http" # Setting the environment variables to be used in the next request. @@ -41,6 +42,8 @@ If the response is a *complex* JSON object, you can use the `@env-stdin-cmd` directive to set environment variables using an external command (e.g., `jq`). +JSON Web Tokens (JWT) are a common example where the response JSON is complex. + In this example `jq` is used to extract the `ctx` string from a JWT token. ```http title="with-external-jq.http" diff --git a/docs/docs/usage/file-to-variable.md b/docs/docs/usage/file-to-variable.md index 4ad8f08..0b35d13 100644 --- a/docs/docs/usage/file-to-variable.md +++ b/docs/docs/usage/file-to-variable.md @@ -1,11 +1,15 @@ # File to variable -You can use the `@file-to-variable` directive to read the content of a file and assign it to a variable. +You can use the `@file-to-variable` directive to +read the content of a file and assign it to a variable. -Create a file with the `.http` extension and write your JSON request in it. +Create a file with the `.http` extension and +write your JSON request in it. Then, use the `@file-to-variable` directive to specify the variable name -that you want to use in the request. The second argument is the path to the file. +that you want to use in the request. + +The second argument is the path to the file. ```http title="file-to-variable.http" @@ -19,7 +23,8 @@ Accept: application/json } ``` -The `TEST_INCLUDE` variable will be replaced with the content of the `test-include.json` file. +The `TEST_INCLUDE` variable will be replaced with +the content of the `test-include.json` file. ```json title="test-include.json" { diff --git a/docs/docs/usage/graphql.md b/docs/docs/usage/graphql.md index 69acb59..9045280 100644 --- a/docs/docs/usage/graphql.md +++ b/docs/docs/usage/graphql.md @@ -56,11 +56,13 @@ You can download the schema of a GraphQL server with: You need to have your cursor on a line with a GraphQL request. -The file will be downloaded to the the directory where the current file is located. +The file will be downloaded to the the +directory where the current file is located. The filename will be `[http-file-name-without-extension].graphql-schema.json`. -This file can be used in conjunction with the [kulala-cmp-graphql][kulala-cmp-graphql] +This file can be used in conjunction with +the [kulala-cmp-graphql][kulala-cmp-graphql] plugin to provide autocompletion and type checking. [kulala-cmp-graphql]: https://github.com/mistweaverco/kulala-cmp-graphql.nvim diff --git a/docs/docs/usage/http-file-spec.md b/docs/docs/usage/http-file-spec.md index fccd0db..8cd08ea 100644 --- a/docs/docs/usage/http-file-spec.md +++ b/docs/docs/usage/http-file-spec.md @@ -7,7 +7,8 @@ ### Requests -The format for an HTTP request is `HTTPMethod` `URL` `HTTPVersion`, all on one line, where: +The format for an HTTP request is `HTTPMethod` `URL` `HTTPVersion`, +all on one line, where: - `HTTPMethod` is the HTTP method to use, for example: - `OPTIONS` @@ -19,8 +20,12 @@ The format for an HTTP request is `HTTPMethod` `URL` `HTTPVersion`, all on one l - `DELETE` - `TRACE` - `CONNECT` -- `URL` is the URL to send the request to. The URL can include query string parameters. The URL doesn't have to point to a local web project. It can point to any URL that Visual Studio can access. -- `HTTPVersion` is optional and specifies the HTTP version that should be used, that is, `HTTP/1.1`, `HTTP/2`, or `HTTP/3`. +- `URL` is the URL to send the request to. + The URL can include query string parameters. + The URL doesn't have to point to a local web project. + It can point to any URL that Visual Studio can access. +- `HTTPVersion` is optional and specifies the HTTP version that should be used, + that's, `HTTP/1.1`, `HTTP/2`, or `HTTP/3`. A file can contain multiple requests by using lines with `###` as delimiters. The following example showing three requests in a file illustrates this syntax: @@ -42,7 +47,8 @@ GET https://localhost:7220/weatherforecast HTTP/3 To add one or more headers, add each header on its own line immediately after the request line. -Don't include any blank lines between the request line and the first header or between subsequent header lines. +Don't include any blank lines between the request line and +the first header or between subsequent header lines. The format is `header-name`: `value`, as shown in the following examples: ```http @@ -58,8 +64,7 @@ Age: 100 ### ``` -> When calling an API that authenticates with headers, -> do not commit any secrets to a source code repository. +> Don't add any secrets to a source code repository. ### Request body diff --git a/docs/docs/usage/huge-request-body.md b/docs/docs/usage/huge-request-body.md new file mode 100644 index 0000000..6aa611c --- /dev/null +++ b/docs/docs/usage/huge-request-body.md @@ -0,0 +1,36 @@ +# Huge Request Body + +If you try to create a request with a large body, + +an error might occur due to a shell limitation of arg list size. + +To avoid this error, you can use the `@write-body-to-temporary-file` +meta tag in the request section. + +This tells Kulala to write the request body to a +temporary file and use the file as the request body. + +:::note + +For `Content-Type: multipart/form-data` this isn't not necessary, +because Kulala enforces the use of temporary files for this content type. + +::: + +```http title="huge-request-body.http" + +# @write-body-to-temporary-file +POST https://httpbin.org/post HTTP/1.1 +Content-Type: application/json +Accept: application/json + +{ + "name": "John", + "age": 30, + "address": "123 Main St, Springfield, IL 62701", + "phone": "555-555-5555", + "email": "" +} +``` + +In the example above, the request body is written to a temporary file. diff --git a/docs/docs/usage/magic-variables.md b/docs/docs/usage/magic-variables.md index cb42fd7..20fd1c6 100644 --- a/docs/docs/usage/magic-variables.md +++ b/docs/docs/usage/magic-variables.md @@ -1,15 +1,20 @@ # Magic Variables -There is a predefined set of magic variables that you can use in your HTTP requests. +There is a predefined set of magic variables that +you can use in your HTTP requests. They all start with a `$` sign. -- `{{$uuid}}` - Generates a random UUID. +A Unique User Identifier (UUID) is a 128-bit number used to +identify information in computer systems. + +- `{{$uuid}}` - Generates a UUID. - `{{$timestamp}}` - Generates a timestamp. -- `{{$date}}` - Generates a date (YYYY-MM-DD). +- `{{$date}}` - Generates a date (yyyy-mm-dd). - `{{$randomInt}}` - Generates a random integer (between 0 and 9999999). -To test this feature, create a file with the `.http` extension and write your HTTP requests in it. +To test this feature, +create a file with the `.http` extension and write your HTTP requests in it. ```http title="magic-variables.http" POST https://httpbin.org/post HTTP/1.1 diff --git a/docs/docs/usage/public-methods.md b/docs/docs/usage/public-methods.md index e7bfbbd..fee4993 100644 --- a/docs/docs/usage/public-methods.md +++ b/docs/docs/usage/public-methods.md @@ -30,10 +30,12 @@ It opens up a floating window with the parsed request. The scratchpad is a (throwaway) buffer where you can write your requests. -It is useful for quick testing. It is useful for requests that you don't want to save. +It's useful for quick testing. +It's useful for requests that you don't want to save. It's default contents can be configured via the -[`scratchpad_default_contents`][scratchpad_default_contents] configuration option. +[`scratchpad_default_contents`][scratchpad_default_contents] +configuration option. ### copy @@ -43,28 +45,39 @@ It's default contents can be configured via the ### from_curl `require('kulala').from_curl()` parse the cURL command from the clipboard and -write the HTTP spec into current buffer. It is useful for importing requests -from other tools like browsers. +write the HTTP spec into current buffer. +It's useful for importing requests from other tools like browsers. ### close -`require('kulala').close()` closes the kulala window and also the current buffer. +`require('kulala').close()` closes the kulala window and +also the current buffer. -> (it will not close the current buffer, if it is not a `.http` or `.rest` file) +> (it'll not close the current buffer, if it's not a `.http` or `.rest` file) ### toggle_view `require('kulala').toggle_view()` toggles between the `body` and `headers` view of the last run request. -Persists across restarts. - ### search -`require('kulala').search()` searches for all `.http` and `.rest` files -in the current working directory. +`require('kulala').search()` searches for all *named* requests in the current buffer. + +:::tip + +Named requests are those that have a name like so: + +```http +# @name MY_REQUEST_NAME +GET http://example.com +``` + +::: + -It tries to load up a telescope prompt to select a file or fallback to using `vim.ui.select`. +It tries to load up a telescope prompt to select a +file or fallback to using `vim.ui.select`. ### jump_prev @@ -77,7 +90,8 @@ It tries to load up a telescope prompt to select a file or fallback to using `vi ### scripts_clear_global `require('kulala').scripts_clear_global('variable_name')` -clears a global variable set via [`client.global.set`](../scripts/client-reference). +clears a global variable set via +[`client.global.set`](../scripts/client-reference). You can clear all globals by omitting the `variable_name` like so: `require('kulala').scripts_clear_global()`. @@ -86,6 +100,19 @@ Additionally, you can clear a list of global variables by passing a table of variable names like so: `require('kulala').scripts_clear_global({'variable_name1', 'variable_name2'})`. +### clear_cached_files + +`require('kulala').clear_cached_files()` +clears all cached files. + +These files include: + +- last response body +- last response headers +- last request data +- global variables set via scripts +- compiled pre- and post-request scripts + ### download_graphql_schema You can download the schema of a GraphQL server with: @@ -96,9 +123,11 @@ You can download the schema of a GraphQL server with: You need to have your cursor on a line with a GraphQL request. -The file will be downloaded to the the directory where the current file is located. +The file will be downloaded to +the the directory where the current file is located. -The filename will be `[http-file-name-without-extension].graphql-schema.json`. +The filename will be +`[http-file-name-without-extension].graphql-schema.json`. This file can be used in conjunction with the [kulala-cmp-graphql][kulala-cmp-graphql] plugin to @@ -106,7 +135,7 @@ provide autocompletion and type checking. ### get_selected_env -::: warning ::: +:::warning This function is only available if you are using a `http-client.env.json` file. @@ -117,7 +146,7 @@ returns the selected environment. ### set_selected_env -::: warning ::: +:::warning This function is only available if you are using a `http-client.env.json` file. @@ -126,10 +155,12 @@ This function is only available if you are using a `http-client.env.json` file. `require('kulala').set_selected_env(env_key)` sets the selected environment. -See: https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-8.0#environment-files +See: [Environment Files][env-files]. If you omit the `env_key`, -it will try to load up a telescope prompt to select an environment or fallback to using `vim.ui.select`. +it'll try to load up a telescope prompt to +select an environment or fallback to using `vim.ui.select`. [scratchpad_default_contents]: ../getting-started/configuration-options#scratchpad_default_contents [kulala-cmp-graphql]: https://github.com/mistweaverco/kulala-cmp-graphql.nvim +[env-files]: https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-8.0#environment-files diff --git a/docs/docs/usage/redirect-the-response.md b/docs/docs/usage/redirect-the-response.md index 04c367b..d0f0cf9 100644 --- a/docs/docs/usage/redirect-the-response.md +++ b/docs/docs/usage/redirect-the-response.md @@ -3,11 +3,13 @@ You can redirect the response to a file. -## Do not overwrite file +## Don't overwrite file -By using the `>>` operator followed by the file name, the response will be saved to the file. +By using the `>>` operator followed by the file name, +the response will be saved to the file. -If the file already exists, a warning will be displayed, and the file will not be overwritten. +If the file already exists, +a warning will be displayed, and the file won't be overwritten. To overwrite the file, use the `>>!` operator. diff --git a/docs/docs/usage/request-variables.md b/docs/docs/usage/request-variables.md index 82350bc..b3f8866 100644 --- a/docs/docs/usage/request-variables.md +++ b/docs/docs/usage/request-variables.md @@ -11,33 +11,49 @@ Content-Type: application/x-www-form-urlencoded name=foo&password=bar ``` -You can think of request variable as attaching a name metadata to the underlying request, -and this kind of requests can be called with Named Request, +You can think of request variable as attaching +a name metadata to the underlying request. + +This kind of requests can be called with Named Request. + Other requests can use `THIS_IS_AN_EXAMPLE_REQUEST_NAME` as an -identifier to reference the expected part of the named request or its latest response. +identifier to reference the expected part of +the named request or its latest response. :::warning If you want to refer the response of a named request, -you need to manually trigger the named request to retrieve its response first, -otherwise the plain text of -variable reference like `{{THIS_IS_AN_EXAMPLE_REQUEST_NAME.response.body.$.id}}` will be sent instead. +you need to manually trigger the named request to retrieve its response first. +Otherwise the plain text of +variable reference like +`{{THIS_IS_AN_EXAMPLE_REQUEST_NAME.response.body.$.id}}` +will be sent instead. ::: -The reference syntax of a request variable is a bit more complex than other kinds of custom variables. +The reference syntax of a request variable is a +bit more complex than other kinds of custom variables. ## Request Variable Reference Syntax -The request variable reference syntax follows `{{REQUEST_NAME.(response|request).(body|headers).(*|JSONPath|XPath|Header Name)}}`. +The request variable reference syntax follows + +``` +{{REQUEST_NAME.(response|request).(body|headers).(*|JSONPath|XPath|Header Name)}}` +``` -You have two reference part choices of the `response` or `request`: `body` and `headers`. +You have two reference part choices of +the `response` or `request`: `body` and `headers`. -For `body` part, you can use JSONPath and XPath to extract specific property or attribute. +For `body` part, +you can use JSONPath and XPath to extract specific property or attribute. ### Special case for cookies -The response cookies can be referenced by `{{REQUEST_NAME.response.cookies.CookieName.(value|domain|flag|path|secure|expires)}}`. +The response cookies can be referenced by +``` +{{REQUEST_NAME.response.cookies.CookieName.(value|domain|flag|path|secure|expires)}}` +``` ```http # @name REQUEST_GH @@ -69,23 +85,29 @@ GET https://github.com HTTP/1.1 ## Example -if a JSON response returns `body` `{"id": "mock"}`, you can set the JSONPath part to `$.id` to reference the `id`. +if a JSON response returns `body` `{"id": "mock"}`, +you can set the JSONPath part to `$.id` to reference the `id`. For `headers` part, you can specify the header name to extract the header value. -The header name is case-sensitive for `response` part, and all lower-cased for `request` part. +The header name is case-sensitive for `response` part, +and all lower-cased for `request` part. -If the *JSONPath* or *XPath* of `body`, or *Header Name* of `headers` can't be resolved, +If the *JSONPath* or *XPath* of `body`, +or *Header Name* of `headers` can't be resolved, the plain text of variable reference will be sent instead. + And in this case, diagnostic information will be displayed to help you to inspect this. -Below is a sample of request variable definitions and references in an http file. +Below is a sample of request variable definitions and +references in an http file. ```http # @name REQUEST_ONE POST https://httpbin.org/post HTTP/1.1 Accept: application/json +Content-Type: application/json { "token": "foobar" @@ -96,6 +118,7 @@ Accept: application/json # @name REQUEST_TWO POST https://httpbin.org/post HTTP/1.1 Accept: application/json +Content-Type: application/json { "token": "{{REQUEST_ONE.response.body.$.json.token}}" @@ -105,6 +128,7 @@ Accept: application/json POST https://httpbin.org/post HTTP/1.1 Accept: application/json +Content-Type: application/json { "date_header": "{{REQUEST_TWO.response.headers['Date']}}" diff --git a/docs/docs/usage/sending-form-data.md b/docs/docs/usage/sending-form-data.md index 187c085..c77a4d8 100644 --- a/docs/docs/usage/sending-form-data.md +++ b/docs/docs/usage/sending-form-data.md @@ -1,6 +1,7 @@ # Sending Form Data -You can send form data in Kulala by using the `application/x-www-form-urlencoded` content type. +You can send form data in Kulala by +using the `application/x-www-form-urlencoded` content type. Create a file with the `.http` extension and write your HTTP requests in it. @@ -16,3 +17,47 @@ name={{name}}& age={{age}} ``` +## Sending multipart form data + +You can send multipart form data in Kulala by +using the `multipart/form-data` content type. + +:::warning + +When sending a file, +(other than text files) +you need to use the `binary` directive to read the file as binary data. + +You can omit the `binary` directive when sending text files. + +::: + +```http title="multipart.http" +# @file-to-variable LOGO_FILE_VAR ./../../logo.png binary +POST https://httpbin.org/post HTTP/1.1 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundary{{$timestamp}} + +------WebKitFormBoundary{{$timestamp}} +Content-Disposition: form-data; name="logo"; filename="logo.png" +Content-Type: image/jpeg + +{{LOGO_FILE_VAR}} + +------WebKitFormBoundary{{$timestamp}} +Content-Disposition: form-data; name="x" + +0 +------WebKitFormBoundary{{$timestamp}} +Content-Disposition: form-data; name="y" + +1.4333333333333333 +------WebKitFormBoundary{{$timestamp}} +Content-Disposition: form-data; name="w" + +514.5666666666667 +------WebKitFormBoundary{{$timestamp}} +Content-Disposition: form-data; name="h" + +514.5666666666667 +------WebKitFormBoundary{{$timestamp}}-- +``` diff --git a/docs/docs/usage/using-variables.md b/docs/docs/usage/using-variables.md index 6bd60fb..6653f85 100644 --- a/docs/docs/usage/using-variables.md +++ b/docs/docs/usage/using-variables.md @@ -23,7 +23,8 @@ These variables are available in all requests in the file. ## Prompt variables -You can also use prompt variables. These are variables that you can set when you run the request. +You can also use prompt variables. +These are variables that you can set when you run the request. ```http title="examples.http" # @prompt pokemon @@ -31,6 +32,8 @@ GET https://pokeapi.co/api/v2/pokemon/{{pokemon}} HTTP/1.1 Accept: application/json ``` -When you run this request, you will be prompted to enter a value for `pokemon`. +When you run this request, +you will be prompted to enter a value for `pokemon`. -These variables are available for the current request and all subsequent requests in the file. +These variables are available for the current request and +all subsequent requests in the file. diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index e3464ef..4deb9a9 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -105,12 +105,12 @@ const config: Config = { ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} mistweaver.co.`, + copyright: `Copyright © ${new Date().getFullYear()} mistweaverco.`, }, prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula, - additionalLanguages: ['lua', 'http'], + additionalLanguages: ['lua', 'http', 'json'], }, } satisfies Preset.ThemeConfig, }; diff --git a/docs/package-lock.json b/docs/package-lock.json index eff63ce..66c9200 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,8 +8,8 @@ "name": "docs-new", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/preset-classic": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/preset-classic": "3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", @@ -17,10 +17,10 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/tsconfig": "3.4.0", - "@docusaurus/types": "3.4.0", - "docusaurus-lunr-search": "^3.4.0", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/tsconfig": "3.5.2", + "@docusaurus/types": "3.5.2", + "docusaurus-lunr-search": "3.5.0", "eslint": "^9.7.0", "typescript": "~5.2.2" }, @@ -100,6 +100,25 @@ "@algolia/transporter": "4.24.0" } }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, "node_modules/@algolia/client-analytics": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", @@ -111,7 +130,7 @@ "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-common": { + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", @@ -120,6 +139,25 @@ "@algolia/transporter": "4.24.0" } }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.3.2.tgz", + "integrity": "sha512-4OxrO3q2FNgXCuOO8hUMt5saPwmY1efNLd9zzXbADnSwQHQeuAKLC/b2DZC+i9bCXXk3uENm7dKf4nm29/H0gw==", + "peer": true, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/client-personalization": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", @@ -130,16 +168,29 @@ "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-search": { + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", "dependencies": { - "@algolia/client-common": "4.24.0", "@algolia/requester-common": "4.24.0", "@algolia/transporter": "4.24.0" } }, + "node_modules/@algolia/client-search": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.3.2.tgz", + "integrity": "sha512-iJN3nylaSh9XuqUK0DRrRi/qwVqvA4PiWPydUB1a5NaceH7ng254GL4xwntAsX7RPIb8gQ2t65Gqoy5ToLs0kA==", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.3.2", + "@algolia/requester-browser-xhr": "5.3.2", + "@algolia/requester-node-http": "5.3.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/events": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", @@ -176,7 +227,26 @@ "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/requester-browser-xhr": { + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", @@ -184,17 +254,41 @@ "@algolia/requester-common": "4.24.0" } }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.3.2.tgz", + "integrity": "sha512-5DVcWHuZZLbso2mgIsZ2KgPZGCQbgzgpjkAZZGlOc9xEEqUe3K7ZurqYKkfhYJmo7OQGoyEiqFNa71b3Yyparw==", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.3.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/requester-common": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==" }, "node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.3.2.tgz", + "integrity": "sha512-SgBoGAvPZRkTUkBvb4GU3qT7zVWJ947MQCdLBmZ4gkYwYkTq56NMmthMgpQY/Hfl08oOPQOILofSZnmEprqHng==", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.3.2" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { @@ -1587,11 +1681,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz", - "integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.1.tgz", + "integrity": "sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2108,18 +2202,18 @@ } }, "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", + "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==" }, "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", + "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", + "@docsearch/css": "3.6.1", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -2144,9 +2238,9 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz", - "integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", + "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", "dependencies": { "@babel/core": "^7.23.3", "@babel/generator": "^7.23.3", @@ -2158,12 +2252,12 @@ "@babel/runtime": "^7.22.6", "@babel/runtime-corejs3": "^7.22.6", "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/cssnano-preset": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "autoprefixer": "^10.4.14", "babel-loader": "^9.1.3", "babel-plugin-dynamic-import-node": "^2.3.3", @@ -2224,14 +2318,15 @@ "node": ">=18.0" }, "peerDependencies": { + "@mdx-js/react": "^3.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz", - "integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", + "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", "dependencies": { "cssnano-preset-advanced": "^6.1.2", "postcss": "^8.4.38", @@ -2243,9 +2338,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz", - "integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", + "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" @@ -2255,13 +2350,13 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz", - "integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", + "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -2293,11 +2388,11 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", - "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", + "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", "dependencies": { - "@docusaurus/types": "3.4.0", + "@docusaurus/types": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2311,18 +2406,19 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", - "integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", - "cheerio": "^1.0.0-rc.12", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", + "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", + "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", @@ -2337,23 +2433,25 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz", - "integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", + "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2372,15 +2470,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", - "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", + "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2394,13 +2492,13 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz", - "integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", + "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2414,13 +2512,13 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz", - "integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", + "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2432,13 +2530,13 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz", - "integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", + "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2451,13 +2549,13 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz", - "integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", + "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2469,16 +2567,16 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz", - "integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", + "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2492,23 +2590,23 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz", - "integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/plugin-debug": "3.4.0", - "@docusaurus/plugin-google-analytics": "3.4.0", - "@docusaurus/plugin-google-gtag": "3.4.0", - "@docusaurus/plugin-google-tag-manager": "3.4.0", - "@docusaurus/plugin-sitemap": "3.4.0", - "@docusaurus/theme-classic": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-search-algolia": "3.4.0", - "@docusaurus/types": "3.4.0" + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", + "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/plugin-debug": "3.5.2", + "@docusaurus/plugin-google-analytics": "3.5.2", + "@docusaurus/plugin-google-gtag": "3.5.2", + "@docusaurus/plugin-google-tag-manager": "3.5.2", + "@docusaurus/plugin-sitemap": "3.5.2", + "@docusaurus/theme-classic": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-search-algolia": "3.5.2", + "@docusaurus/types": "3.5.2" }, "engines": { "node": ">=18.0" @@ -2519,26 +2617,26 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", - "integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", + "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.43", + "infima": "0.2.0-alpha.44", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2558,17 +2656,14 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", - "integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==", - "dependencies": { - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", + "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", + "dependencies": { + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2582,23 +2677,24 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", - "integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", + "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", "dependencies": { "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "algoliasearch": "^4.18.0", "algoliasearch-helper": "^3.13.3", "clsx": "^2.0.0", @@ -2617,9 +2713,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz", - "integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", + "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -2629,15 +2725,15 @@ } }, "node_modules/@docusaurus/tsconfig": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.4.0.tgz", - "integrity": "sha512-0qENiJ+TRaeTzcg4olrnh0BQ7eCxTgbYWBnWUeQDc84UYkt/T3pDNnm3SiQkqPb+YQ1qtYFlC0RriAElclo8Dg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.5.2.tgz", + "integrity": "sha512-rQ7toURCFnWAIn8ubcquDs0ewhPwviMzxh6WpRjBW7sJVCXb6yzwUaY3HMNa0VXCFw+qkIbFywrMTf+Pb4uHWQ==", "dev": true }, "node_modules/@docusaurus/types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", - "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", + "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", @@ -2655,12 +2751,12 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz", - "integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", + "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@svgr/webpack": "^8.1.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", @@ -2693,9 +2789,9 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz", - "integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", + "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", "dependencies": { "tslib": "^2.6.0" }, @@ -2712,13 +2808,13 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz", - "integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", + "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -4047,9 +4143,9 @@ } }, "node_modules/algoliasearch-helper": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.2.tgz", - "integrity": "sha512-3YQ6eo7uYOCHeQ2ZpD+OoT3aJJwMNKEnwtu8WMzm81XmBOSCwRjQditH9CeSOQ38qhHkuGw23pbq+kULkIJLcw==", + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz", + "integrity": "sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw==", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -4057,6 +4153,41 @@ "algoliasearch": ">= 3.1 < 6" } }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -4183,9 +4314,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "funding": [ { "type": "opencollective", @@ -4201,11 +4332,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4335,9 +4466,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "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==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4347,7 +4478,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4378,6 +4509,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -4434,9 +4579,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -4452,9 +4597,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, "bin": { @@ -4560,9 +4705,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001641", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz", - "integrity": "sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==", + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", "funding": [ { "type": "opencollective", @@ -5759,9 +5904,9 @@ } }, "node_modules/docusaurus-lunr-search": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-3.4.0.tgz", - "integrity": "sha512-GfllnNXCLgTSPH9TAKWmbn8VMfwpdOAZ1xl3T2GgX8Pm26qSDLfrrdVwjguaLfMJfzciFL97RKrAJlgrFM48yw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-3.5.0.tgz", + "integrity": "sha512-k3zN4jYMi/prWInJILGKOxE+BVcgYinwj9+gcECsYm52tS+4ZKzXQzbPnVJAEXmvKOfFMcDFvS3MSmm6cEaxIQ==", "dev": true, "dependencies": { "autocomplete.js": "^0.37.0", @@ -6009,9 +6154,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.827", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.827.tgz", - "integrity": "sha512-VY+J0e4SFcNfQy19MEoMdaIcZLmDCprqvBtkii1WTCTQHpRvf5N8+3kTYCgL/PcntvwQvmMJWTuDPsq+IlhWKQ==" + "version": "1.5.18", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.18.tgz", + "integrity": "sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -6032,9 +6177,9 @@ } }, "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6588,36 +6733,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6647,15 +6792,23 @@ "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==", + "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.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -8319,9 +8472,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.43", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", - "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "version": "0.2.0-alpha.44", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", + "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", "engines": { "node": ">=12" } @@ -9179,9 +9332,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", @@ -9452,9 +9605,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -9598,9 +9754,9 @@ ] }, "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.1.tgz", + "integrity": "sha512-VGV2uxUzhEZmaP7NSFo2vtq7M2nUD+WfmYQD+d8i/1nHbzE+rMy9uzTvUybBbNiVbrhOZibg3gbyoARGqgDWyg==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -11355,9 +11511,9 @@ } }, "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==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/nopt": { "version": "1.0.10", @@ -12894,9 +13050,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", - "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz", + "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", "engines": { "node": ">=14" }, @@ -13646,9 +13802,9 @@ "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -13740,9 +13896,9 @@ } }, "node_modules/search-insights": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.15.0.tgz", - "integrity": "sha512-ch2sPCUDD4sbPQdknVl9ALSi9H7VyoeVbsxznYz6QV55jJ8CI3EtwpO1i84keN4+hF5IeHWIeGvc08530JkVXQ==", + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.1.tgz", + "integrity": "sha512-HHFjYH/0AqXacETlIbe9EYc3UNlQYGNNTY0fZ/sWl6SweX+GDxq9NB5+RVoPLgEFuOtCz7M9dhYxqDnhbbF0eQ==", "peer": true }, "node_modules/section-matter": { @@ -13800,9 +13956,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13947,9 +14103,9 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -13960,6 +14116,55 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/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/serve-static/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==" + }, + "node_modules/serve-static/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/serve-static/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "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/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -15268,9 +15473,9 @@ } }, "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" diff --git a/docs/package.json b/docs/package.json index 945bdb4..f918f45 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,8 +15,8 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/preset-classic": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/preset-classic": "3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", @@ -24,10 +24,10 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/tsconfig": "3.4.0", - "@docusaurus/types": "3.4.0", - "docusaurus-lunr-search": "^3.4.0", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/tsconfig": "3.5.2", + "@docusaurus/types": "3.5.2", + "docusaurus-lunr-search": "3.5.0", "eslint": "^9.7.0", "typescript": "~5.2.2" }, diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 9e8043c..351eca3 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -15,6 +15,7 @@ const sidebars: SidebarsConfig = { 'getting-started/install', 'getting-started/requirements', 'getting-started/configuration-options', + 'getting-started/example-configuration', ], }, { @@ -29,6 +30,7 @@ const sidebars: SidebarsConfig = { items: [ "usage/public-methods", "usage/api", + "usage/huge-request-body", "usage/authentication", "usage/automatic-response-formatting", "usage/dotenv-and-http-client.env.json-support", diff --git a/docs/src/pages/markdown-page.md b/docs/src/pages/markdown-page.md deleted file mode 100644 index 9756c5b..0000000 --- a/docs/src/pages/markdown-page.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Markdown page example ---- - -# Markdown page example - -You don't need React to write simple standalone pages. diff --git a/docs/vale.ini b/docs/vale.ini new file mode 100644 index 0000000..63ddb75 --- /dev/null +++ b/docs/vale.ini @@ -0,0 +1,6 @@ +StylesPath = .vale/styles + +MinAlertLevel = suggestion + +[*.{md}] +BasedOnStyles = proselint, 18F, documentation diff --git a/lua/kulala/augroups/init.lua b/lua/kulala/augroups/init.lua index 210da68..3f453cf 100644 --- a/lua/kulala/augroups/init.lua +++ b/lua/kulala/augroups/init.lua @@ -1,21 +1,34 @@ local Config = require("kulala.config") -local Parser = require("kulala.parser") local Float = require("kulala.ui.float") -local Db = require("kulala.db") -local M = {} +local TS = require("kulala.parser.treesitter") +local DB = require("kulala.db") +local ENV_PARSER = require("kulala.parser.env") + +local VV_NS_NAME = "virtual_variable_text_namespace" +local M = { + show_virtual_variable_text = Config.get().show_variable_info_text == "virtual", +} local show_variable_info_text = function() local line = vim.api.nvim_get_current_line() - local db_env = Db.find_unique("env") - if db_env == nil then - return nil + local default_headers = nil + if DB.find_unique("http_client_env_shared") then + local headers = DB.find_unique("http_client_env_shared")["$default_headers"] + if headers then + default_headers = headers + end end - local variables = Parser.get_document() - if variables == nil then + local variables = vim.tbl_extend( + "force", + TS == nil and {} or TS.get_document_variables() or {}, + ENV_PARSER.get_env() or {}, + default_headers or {} + ) + if vim.tbl_isempty(variables) then return nil end - variables = vim.tbl_extend("force", variables, db_env) + -- get variable under cursor -- a variable is a string that starts with two {{ and ends with two }} local cursor = vim.api.nvim_win_get_cursor(0) @@ -31,15 +44,95 @@ local show_variable_info_text = function() return nil end local variable = line:sub(start_col + 1, end_col - 1) - local variable_value = variables[variable] or "{{" .. variable .. "}}" + local variable_value = variables[variable] + if not variable_value then + variable_value = "{{" .. variable .. "}}" + end + local max_len = Config.get().virtual_variable_max_length or 100 + if #variable_value > max_len then + variable_value = variable_value:sub(1, max_len) .. "..." + end return Float.create({ contents = { variable_value }, position = "cursor", }) end +-- Function to add virtual text to patterns like {{host}} +local add_virtual_variable_text = function() + if (M.show_virtual_variable_text or false) == false then + return + end + + local bufnr = vim.api.nvim_get_current_buf() + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + + local ns_id = vim.api.nvim_create_namespace(VV_NS_NAME) + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) + + local pattern = "{{(.-)}}" + local max_len = Config.get().virtual_variable_max_length or 100 + + local default_headers = nil + if DB.find_unique("http_client_env_shared") then + local headers = DB.find_unique("http_client_env_shared")["$default_headers"] + if headers then + default_headers = headers + end + end + + local variables = vim.tbl_extend( + "force", + TS == nil and {} or TS.get_document_variables() or {}, + ENV_PARSER.get_env() or {}, + default_headers or {} + ) + + for lineno, line in ipairs(lines) do + if line ~= nil and line:match("%S") ~= nil then + for start_idx, match in line:gmatch("()(" .. pattern .. ")()") do + local label = match:gsub("{{", ""):gsub("}}", "") + local value = variables[label] + if value ~= nil then + -- Calculate the position to place virtual text + local end_idx = start_idx + #match - 1 + + if #value > max_len then + value = value:sub(1, max_len) .. "..." + end + -- Add virtual text before the closing braces of the match + vim.api.nvim_buf_set_extmark(bufnr, ns_id, lineno - 1, end_idx - 2, { + virt_text = { { ":" .. value, "Comment" } }, -- You can change the highlight group "Comment" as needed + virt_text_pos = "inline", + }) + end + end + end + end +end + +M.toggle_virtual_variable = function() + M.show_virtual_variable_text = not M.show_virtual_variable_text + + local group_name = "kulala_virtual_variable" + if M.show_virtual_variable_text then + add_virtual_variable_text() + vim.api.nvim_create_augroup(group_name, { clear = true }) + vim.api.nvim_create_autocmd({ "BufEnter", "TextChanged", "TextChangedI" }, { + group = group_name, + callback = add_virtual_variable_text, + }) + else + local ns_id = vim.api.nvim_create_namespace(VV_NS_NAME) + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) + vim.api.nvim_clear_autocmds({ group = group_name }) + end +end + M.setup = function() - if Config.get().show_variable_info_text == "float" then + local type = Config.get().show_variable_info_text + if type == "float" then local augroup = vim.api.nvim_create_augroup("kulala_show_float_variable_info_text", { clear = true }) local float_win_id = nil local timer = nil @@ -61,6 +154,8 @@ M.setup = function() end) end, }) + elseif type == "virtual" then + M.toggle_virtual_variable() end end diff --git a/lua/kulala/cmd/init.lua b/lua/kulala/cmd/init.lua index ccb3cf8..37d08e5 100644 --- a/lua/kulala/cmd/init.lua +++ b/lua/kulala/cmd/init.lua @@ -4,7 +4,6 @@ local PARSER = require("kulala.parser") local EXT_PROCESSING = require("kulala.external_processing") local INT_PROCESSING = require("kulala.internal_processing") local Api = require("kulala.api") -local Scripts = require("kulala.scripts") local INLAY = require("kulala.inlay") local UV = vim.loop local Logger = require("kulala.logger") @@ -61,7 +60,7 @@ end ---Runs the command and returns the result ---@param cmd table command to run ----@param callback function callback function +---@param callback function|nil callback function M.run = function(cmd, callback) vim.fn.jobstart(cmd, { on_stderr = function(_, datalist) @@ -124,7 +123,12 @@ M.run_parser = function(req, callback) end end INT_PROCESSING.redirect_response_body_to_file(result.redirect_response_body_to_files) - Scripts.javascript.run("post_request", result.scripts.post_request) + + local has_post_request_scripts = #result.scripts.post_request.inline > 0 + or #result.scripts.pre_request.files > 0 + if has_post_request_scripts then + PARSER.scripts.javascript.run("post_request", result.scripts.post_request) + end Api.trigger("after_request") end Fs.delete_request_scripts_files() @@ -179,7 +183,7 @@ M.run_parser_all = function(doc, callback) end end INT_PROCESSING.redirect_response_body_to_file(result.redirect_response_body_to_files) - Scripts.javascript.run("post_request", result.scripts.post_request) + PARSER.scripts.javascript.run("post_request", result.scripts.post_request) Api.trigger("after_request") end Fs.delete_request_scripts_files() diff --git a/lua/kulala/config/init.lua b/lua/kulala/config/init.lua index 9c41061..e5ffa57 100644 --- a/lua/kulala/config/init.lua +++ b/lua/kulala/config/init.lua @@ -2,6 +2,13 @@ local FS = require("kulala.utils.fs") local M = {} M.defaults = { + -- cURL path + -- if you have curl installed in a non-standard path, + -- you can specify it here + curl_path = "curl", + -- Display mode + -- possible values: "split", "float" + display_mode = "split", -- split direction -- possible values: "vertical", "horizontal" split_direction = "vertical", @@ -59,7 +66,7 @@ M.defaults = { -- enable winbar winbar = false, -- Specify the panes to be displayed by default - -- Current avaliable pane contains { "body", "headers", "headers_body", "script_output" }, + -- Current available pane contains { "body", "headers", "headers_body", "script_output", "stats" }, default_winbar_panes = { "body", "headers", "headers_body" }, -- enable reading vscode rest client environment variables vscode_rest_client_environmentvars = false, @@ -75,6 +82,10 @@ M.defaults = { -- this will show the variable name and value either as virtual text or float -- possible values: false, "virtual", "float" show_variable_info_text = false, + -- The maximum length of the virtual text display + virtual_variable_max_length = 100, + -- certificates + certificates = {}, } M.default_contenttype = { diff --git a/lua/kulala/db/init.lua b/lua/kulala/db/init.lua index 83566c5..d135b54 100644 --- a/lua/kulala/db/init.lua +++ b/lua/kulala/db/init.lua @@ -9,7 +9,7 @@ local function default_data() return { selected_env = nil, -- string - name of selected env http_client_env = nil, -- table of envs from http-client.env.json - http_client_env_base = nil, -- table of base env values which should be applied to all requests + http_client_env_shared = nil, -- table of base env values which should be applied to all requests env = {}, -- table of envs from document sources scope_nr = nil, -- number - buffer number of the current scope } diff --git a/lua/kulala/globals/init.lua b/lua/kulala/globals/init.lua index 889ebf9..5382eba 100644 --- a/lua/kulala/globals/init.lua +++ b/lua/kulala/globals/init.lua @@ -4,12 +4,13 @@ local M = {} local plugin_tmp_dir = FS.get_plugin_tmp_dir() -M.VERSION = "3.7.0" +M.VERSION = "4.4.1" M.UI_ID = "kulala://ui" M.SCRATCHPAD_ID = "kulala://scratchpad" M.HEADERS_FILE = plugin_tmp_dir .. "/headers.txt" M.BODY_FILE = plugin_tmp_dir .. "/body.txt" M.STATS_FILE = plugin_tmp_dir .. "/stats.json" +M.REQUEST_FILE = plugin_tmp_dir .. "/request.json" M.COOKIES_JAR_FILE = plugin_tmp_dir .. "/cookies.txt" M.SCRIPT_PRE_OUTPUT_FILE = plugin_tmp_dir .. "/pre-script-output.txt" M.SCRIPT_POST_OUTPUT_FILE = plugin_tmp_dir .. "/post-script-output.txt" diff --git a/lua/kulala/graphql/init.lua b/lua/kulala/graphql/init.lua index bb78687..eeaf33e 100644 --- a/lua/kulala/graphql/init.lua +++ b/lua/kulala/graphql/init.lua @@ -1,3 +1,4 @@ +local Config = require("kulala.config") local Parser = require("kulala.parser") local Parserutils = require("kulala.parser.utils") local Cmd = require("kulala.cmd") @@ -16,9 +17,12 @@ M.download_schema = function() Logger.warn("Not a GraphQL request") return end + if not Parserutils.contains_header(req.headers, "content-type", "application/json") then + req.headers["Content-Type"] = "application/json" + end local filename = vim.fn.expand("%:t:r") .. ".graphql-schema.json" local c = { - "curl", + Config.get().curl_path, "-s", "-o", filename, diff --git a/lua/kulala/health.lua b/lua/kulala/health.lua index 5794d8c..2c66c2e 100644 --- a/lua/kulala/health.lua +++ b/lua/kulala/health.lua @@ -13,10 +13,13 @@ local M = {} M.check = function() info("{kulala.nvim} version " .. GLOBALS.VERSION) - if FS.command_exists("curl") then - ok("{curl} found") + local curl = CONFIG.get().curl_path + if FS.command_exists(curl) then + local curl_path = FS.command_path(curl) + local curl_version = vim.fn.system({ curl_path, "--version" }) + ok(string.format("{curl} found: %s (version: %s)", curl_path, curl_version:gsub("^curl ([^ ]+).*", "%1"))) else - error("{curl} not found") + error(string.format("{%s} not found", curl)) end start("Checking formatters") diff --git a/lua/kulala/init.lua b/lua/kulala/init.lua index c091761..f1587e9 100644 --- a/lua/kulala/init.lua +++ b/lua/kulala/init.lua @@ -6,8 +6,10 @@ local CONFIG = require("kulala.config") local JUMPS = require("kulala.jumps") local Graphql = require("kulala.graphql") local Logger = require("kulala.logger") -local ScriptsUtils = require("kulala.scripts.utils") local Augroups = require("kulala.augroups") +local ScriptsUtils = require("kulala.parser.scripts.utils") +local Fs = require("kulala.utils.fs") + local M = {} M.setup = function(config) @@ -103,4 +105,14 @@ M.set_selected_env = function(env) vim.g.kulala_selected_env = env end +M.toggle_virtual_variable = function() + Augroups.toggle_virtual_variable() +end + +---Clears all cached files +---Useful when you want to clear all cached files +M.clear_cached_files = function() + Fs.clear_cached_files() +end + return M diff --git a/lua/kulala/internal_processing/init.lua b/lua/kulala/internal_processing/init.lua index d62bbe3..18f38e2 100644 --- a/lua/kulala/internal_processing/init.lua +++ b/lua/kulala/internal_processing/init.lua @@ -3,6 +3,7 @@ local FS = require("kulala.utils.fs") local GLOBALS = require("kulala.globals") local DB = require("kulala.db") local CONFIG = require("kulala.config") +local STRING_UTILS = require("kulala.utils.string") local M = {} -- Function to access a nested key in a table dynamically @@ -18,18 +19,39 @@ local function get_nested_value(t, key) return value end -local get_headers_as_table = function() +---Function to get the last headers as a table +---@description Reads the headers file and returns the headers as a table. +---In some cases the headers file might contain multiple header sections, +---e.g. if you have follow-redirections enabled. +---This function will return the headers of the last response. +---@return table +local get_last_headers_as_table = function() local headers_file = FS.read_file(GLOBALS.HEADERS_FILE):gsub("\r\n", "\n") local lines = vim.split(headers_file, "\n") local headers_table = {} + -- INFO: + -- We only want the headers of the last response + -- so we reset the headers_table only each time the previous line was empty + -- and we also have new headers data + local previously_empty = false for _, header in ipairs(lines) do - if header:find(":") ~= nil then - local kv = vim.split(header, ":") - local key = kv[1] - -- the value should be everything after the first colon - -- but we can't use slice and join because the value might contain colons - local value = header:sub(#key + 2) - headers_table[key] = vim.trim(value) + local empty_line = header == "" + if empty_line then + previously_empty = true + else + if previously_empty then + headers_table = {} + end + previously_empty = false + if header:find(":") ~= nil then + local kv = vim.split(header, ":") + local key = kv[1] + -- INFO: + -- the value should be everything after the first colon + -- but we can't use slice and join because the value might contain colons + local value = header:sub(#key + 2) + headers_table[key] = vim.trim(value) + end end end return headers_table @@ -78,7 +100,7 @@ local get_cookies_as_table = function() end local get_lower_headers_as_table = function() - local headers = get_headers_as_table() + local headers = get_last_headers_as_table() local headers_table = {} for key, value in pairs(headers) do headers_table[key:lower()] = value @@ -90,6 +112,7 @@ M.get_config_contenttype = function() local headers = get_lower_headers_as_table() if headers["content-type"] then local content_type = vim.split(headers["content-type"], ";")[1] + content_type = STRING_UTILS.trim(content_type) local config = CONFIG.get().contenttypes[content_type] if config then return config @@ -101,7 +124,7 @@ end M.set_env_for_named_request = function(name, body) local named_request = { response = { - headers = get_headers_as_table(), + headers = get_last_headers_as_table(), body = body, cookies = get_cookies_as_table(), }, diff --git a/lua/kulala/lib/shlex/init.lua b/lua/kulala/lib/shlex/init.lua index c50c01f..ad2f426 100644 --- a/lua/kulala/lib/shlex/init.lua +++ b/lua/kulala/lib/shlex/init.lua @@ -101,6 +101,9 @@ function M.shlex:read_token() print("shlex: in state '" .. (self.state or "nil") .. "' I see character: '" .. (nextchar or "nil") .. "'") end + -- lua doesn't have continue statements and Lua 5.1 sadly don't have goto + local continue = false + if none(self.state) then self.token = "" break @@ -115,37 +118,37 @@ function M.shlex:read_token() if some(self.token) or (self.posix and quoted) then break else - break + continue = true end - elseif self.commenters:find(nextchar, 1, true) then + elseif not continue and self.commenters:find(nextchar, 1, true) then self.sr:readline() self.lineno = self.lineno + 1 - elseif self.posix and self.escape:find(nextchar, 1, true) then + elseif not continue and self.posix and self.escape:find(nextchar, 1, true) then escapedstate = "a" self.state = nextchar - elseif self.wordchars:find(nextchar, 1, true) then + elseif not continue and self.wordchars:find(nextchar, 1, true) then self.token = nextchar self.state = "a" - elseif self.punctuation_chars:find(nextchar, 1, true) then + elseif not continue and self.punctuation_chars:find(nextchar, 1, true) then self.token = nextchar self.state = "c" - elseif self.quotes:find(nextchar, 1, true) then + elseif not continue and self.quotes:find(nextchar, 1, true) then if not self.posix then self.token = nextchar end self.state = nextchar - elseif self.whitespace_split then + elseif not continue and self.whitespace_split then self.token = nextchar self.state = "a" - else + elseif not continue then self.token = nextchar if some(self.token) or (self.posix and quoted) then break else - break + continue = true end end - elseif self.quotes:find(self.state, 1, true) then + elseif not continue and self.quotes:find(self.state, 1, true) then quoted = true if none(nextchar) then if self.debug >= 2 then @@ -167,7 +170,7 @@ function M.shlex:read_token() else self.token = self.token .. nextchar end - elseif self.escape:find(self.state, 1, true) then + elseif not continue and self.escape:find(self.state, 1, true) then if none(nextchar) then if self.debug >= 2 then print("shlex: I see EOF in escape state") @@ -179,7 +182,7 @@ function M.shlex:read_token() end self.token = self.token .. nextchar self.state = escapedstate - elseif self.state == "a" or self.state == "c" then + elseif not continue and self.state == "a" or self.state == "c" then if none(nextchar) then self.state = nil break @@ -191,9 +194,9 @@ function M.shlex:read_token() if some(self.token) or (self.posix and quoted) then break else - break + continue = true end - elseif self.commenters:find(nextchar, 1, true) then + elseif not continue and self.commenters:find(nextchar, 1, true) then self.sr:readline() self.lineno = self.lineno + 1 if self.posix then @@ -201,10 +204,10 @@ function M.shlex:read_token() if some(self.token) or (self.posix and quoted) then break else - break + continue = true end end - elseif self.state == "c" then + elseif not continue and self.state == "c" then if self.punctuation_chars:find(nextchar, 1, true) then self.token = self.token .. nextchar else @@ -214,18 +217,21 @@ function M.shlex:read_token() self.state = " " break end - elseif self.posix and self.quotes:find(nextchar, 1, true) then + elseif not continue and self.posix and self.quotes:find(nextchar, 1, true) then self.state = nextchar - elseif self.posix and self.escape:find(nextchar, 1, true) then + elseif not continue and self.posix and self.escape:find(nextchar, 1, true) then escapedstate = "a" self.state = nextchar elseif - self.wordchars:find(nextchar, 1, true) - or self.quotes:find(nextchar, 1, true) - or (self.whitespace_split and not self.punctuation_chars:find(nextchar, 1, true)) + not continue + and ( + self.wordchars:find(nextchar, 1, true) + or self.quotes:find(nextchar, 1, true) + or (self.whitespace_split and not self.punctuation_chars:find(nextchar, 1, true)) + ) then self.token = self.token .. nextchar - else + elseif not continue then if some(self.punctuation_chars) then table.insert(self._pushback_chars, nextchar) else @@ -235,7 +241,7 @@ function M.shlex:read_token() if some(self.token) or (self.posix and quoted) then break else - break + continue = true end end end @@ -292,6 +298,7 @@ function M.split(str, comments, posix) end local lex = M.shlex(str) lex.posix = posix + lex.whitespace_split = true if comments == false then lex.commenters = "" end diff --git a/lua/kulala/parser/curl.lua b/lua/kulala/parser/curl.lua index 1adf729..4059bd7 100644 --- a/lua/kulala/parser/curl.lua +++ b/lua/kulala/parser/curl.lua @@ -1,3 +1,4 @@ +local Config = require("kulala.config") local Shlex = require("kulala.lib.shlex") local Stringutils = require("kulala.utils.string") @@ -27,9 +28,9 @@ function M.parse(curl) curl = string.gsub(curl, "%s+", " ") local parts = Shlex.split(curl) - -- if string doesn't start with curl, return nil + -- if string doesn't start with curl or different from curl_path, return nil -- it could also be curl-7.68.0 or something like that - if string.find(parts[1], "^curl.*") == nil then + if string.find(parts[1], "^curl.*") == nil and parts[1] ~= Config.get().curl_path then return nil, nil end local res = { diff --git a/lua/kulala/parser/env.lua b/lua/kulala/parser/env.lua index 12f615e..46addd6 100644 --- a/lua/kulala/parser/env.lua +++ b/lua/kulala/parser/env.lua @@ -6,6 +6,7 @@ local M = {} M.get_env = function() local http_client_env_json = FS.find_file_in_parent_dirs("http-client.env.json") + local http_client_private_env_json = FS.find_file_in_parent_dirs("http-client.private.env.json") local dotenv = FS.find_file_in_parent_dirs(".env") local env = {} @@ -13,7 +14,7 @@ M.get_env = function() env[key] = value end - DB.update().http_client_env_base = {} + DB.update().http_client_env_shared = {} DB.update().http_client_env = {} if Config.get().vscode_rest_client_environmentvars then @@ -29,8 +30,8 @@ M.get_env = function() if settings and settings["rest-client.environmentVariables"] then local f = settings["rest-client.environmentVariables"] if f["$shared"] then - DB.update().http_client_env_base = - vim.tbl_deep_extend("force", DB.find_unique("http_client_env_base"), f["$shared"]) + DB.update().http_client_env_shared = + vim.tbl_deep_extend("force", DB.find_unique("http_client_env_shared"), f["$shared"]) end f["$shared"] = nil DB.update().http_client_env = vim.tbl_deep_extend("force", DB.find_unique("http_client_env"), f) @@ -44,8 +45,8 @@ M.get_env = function() if settings and settings["rest-client.environmentVariables"] then local f = settings["rest-client.environmentVariables"] if f["$shared"] then - DB.update().http_client_env_base = - vim.tbl_deep_extend("force", DB.find_unique("http_client_env_base"), f["$shared"]) + DB.update().http_client_env_shared = + vim.tbl_deep_extend("force", DB.find_unique("http_client_env_shared"), f["$shared"]) end f["$shared"] = nil DB.update().http_client_env = vim.tbl_deep_extend("force", DB.find_unique("http_client_env"), f) @@ -56,7 +57,7 @@ M.get_env = function() if dotenv then local dotenv_env = vim.fn.readfile(dotenv) for _, line in ipairs(dotenv_env) do - -- if the line is not empy and not a comment, then + -- if the line is not empty and not a comment, then if not line:match("^%s*$") and not line:match("^%s*#") then local key, value = line:match("^%s*([^=]+)%s*=%s*(.*)%s*$") if key and value then @@ -68,16 +69,27 @@ M.get_env = function() if http_client_env_json then local f = vim.fn.json_decode(vim.fn.readfile(http_client_env_json)) - if f._base then - DB.update().http_client_env_base = vim.tbl_deep_extend("force", DB.find_unique("http_client_env_base"), f._base) + if f["$shared"] then + DB.update().http_client_env_shared = + vim.tbl_deep_extend("force", DB.find_unique("http_client_env_shared"), f["$shared"]) end - f._base = nil + f["$shared"] = nil DB.update().http_client_env = vim.tbl_deep_extend("force", DB.find_unique("http_client_env"), f) end - local http_client_env_base = DB.find_unique("http_client_env_base") or {} - for key, value in pairs(http_client_env_base) do - if key ~= "DEFAULT_HEADERS" then + if http_client_private_env_json then + local f = vim.fn.json_decode(vim.fn.readfile(http_client_private_env_json)) + if f["$shared"] then + DB.update().http_client_env_shared = + vim.tbl_deep_extend("force", DB.find_unique("http_client_env_shared"), f["$shared"]) + end + f["$shared"] = nil + DB.update().http_client_env = vim.tbl_deep_extend("force", DB.find_unique("http_client_env"), f) + end + + local http_client_env_shared = DB.find_unique("http_client_env_shared") or {} + for key, value in pairs(http_client_env_shared) do + if key ~= "$default_headers" then env[key] = value end end diff --git a/lua/kulala/parser/graphql.lua b/lua/kulala/parser/graphql.lua index 08553aa..00ad079 100644 --- a/lua/kulala/parser/graphql.lua +++ b/lua/kulala/parser/graphql.lua @@ -1,6 +1,9 @@ local M = {} local function parse(body) + local query_string + local variables_string + -- Split the body into lines local lines = vim.split(body, "\r\n") local in_query = false local in_variables = false @@ -22,6 +25,7 @@ local function parse(body) in_query = false in_variables = true end + line = vim.trim(line) if in_query then table.insert(query, line) elseif in_variables then @@ -30,18 +34,18 @@ local function parse(body) end if #query == 0 then - query = nil + query_string = nil else - query = table.concat(query, " ") + query_string = table.concat(query, " ") end if #variables == 0 then - variables = nil + variables_string = nil else - variables = table.concat(variables, " ") + variables_string = table.concat(variables, " ") end - return query, variables + return query_string, variables_string end M.get_json = function(body) diff --git a/lua/kulala/parser/init.lua b/lua/kulala/parser/init.lua index b68b2d8..af8580e 100644 --- a/lua/kulala/parser/init.lua +++ b/lua/kulala/parser/init.lua @@ -1,3 +1,5 @@ +local M = {} +M.scripts = {} local CONFIG = require("kulala.config") local DB = require("kulala.db") local DYNAMIC_VARS = require("kulala.parser.dynamic_vars") @@ -9,15 +11,19 @@ local REQUEST_VARIABLES = require("kulala.parser.request_variables") local STRING_UTILS = require("kulala.utils.string") local PARSER_UTILS = require("kulala.parser.utils") local TS = require("kulala.parser.treesitter") -local PLUGIN_TMP_DIR = FS.get_plugin_tmp_dir() local CURL_FORMAT_FILE = FS.get_plugin_path({ "parser", "curl-format.json" }) -local Scripts = require("kulala.scripts") local Logger = require("kulala.logger") -local M = {} -local function parse_string_variables(str, variables, env) +M.scripts.javascript = require("kulala.parser.scripts.javascript") + +---Parse the variables in a string +---@param str string -- The string to parse +---@param variables table -- The variables defined in the document +---@param env table -- The environment variables +---@param silent boolean|nil -- Whether to suppress not found variable warnings +local function parse_string_variables(str, variables, env, silent) local function replace_placeholder(variable_name) - local value = "" + local value -- If the variable name contains a `$` symbol then try to parse it as a dynamic variable if variable_name:find("^%$") then local variable_value = DYNAMIC_VARS.read(variable_name) @@ -32,11 +38,13 @@ local function parse_string_variables(str, variables, env) value = REQUEST_VARIABLES.parse(variable_name) else value = "{{" .. variable_name .. "}}" - Logger.info( - "The variable '" - .. variable_name - .. "' was not found in the document or in the environment. Returning the string as received ..." - ) + if not silent then + Logger.info( + "The variable '" + .. variable_name + .. "' was not found in the document or in the environment. Returning the string as received ..." + ) + end end return value end @@ -44,10 +52,10 @@ local function parse_string_variables(str, variables, env) return result end -local function parse_headers(headers, variables, env) +local function parse_headers(headers, variables, env, silent) local h = {} for key, value in pairs(headers) do - h[key] = parse_string_variables(value, variables, env) + h[key] = parse_string_variables(value, variables, env, silent) end return h end @@ -88,18 +96,39 @@ local function encode_url_params(url) return url .. anchor end -local function parse_url(url, variables, env) - url = parse_string_variables(url, variables, env) +local function parse_url(url, variables, env, silent) + url = parse_string_variables(url, variables, env, silent) url = encode_url_params(url) url = url:gsub('"', "") return url end -local function parse_body(body, variables, env) +--- Parse the body of the request +---@param body string|nil -- The body of the request +---@param variables table|nil -- The variables defined in the document +---@param env table|nil -- The environment variables +---@param silent boolean|nil -- Whether to suppress not found variable warnings +local function parse_body(body, variables, env, silent) if body == nil then return nil end - return parse_string_variables(body, variables, env) + variables = variables or {} + env = env or {} + return parse_string_variables(body, variables, env, silent) +end + +--- Parse the body_display of the request +---@param body_display string|nil -- The body of the request +---@param variables table|nil -- The variables defined in the document +---@param env table|nil -- The environment variables +---@param silent boolean|nil -- Whether to suppress not found variable warnings +local function parse_body_display(body_display, variables, env, silent) + if body_display == nil then + return nil + end + variables = variables or {} + env = env or {} + return parse_string_variables(body_display, variables, env, silent) end local function split_by_block_delimiters(text) @@ -148,7 +177,7 @@ local function get_request_from_fenced_code_block() -- If we didn't find a block start, return nil if not block_start then - return nil + return nil, nil end -- Search for the end of the fenced code block @@ -163,7 +192,7 @@ local function get_request_from_fenced_code_block() -- If we didn't find a block end, return nil if not block_end then - return nil + return nil, nil end return vim.api.nvim_buf_get_lines(0, block_start, block_end - 1, false), block_start @@ -199,12 +228,14 @@ M.get_document = function() local is_prerequest_handler_script_inline = false local is_postrequest_handler_script_inline = false local is_body_section = false - local lines = vim.split(block, "\n", { plain = true, trimempty = false }) + local lines = vim.split(block, "\n") local block_line_count = #lines local request = { headers = {}, + headers_raw = {}, metadata = {}, body = nil, + body_display = nil, show_icon_line_number = nil, start_line = line_offset + 1, block_line_count = block_line_count, @@ -223,7 +254,6 @@ M.get_document = function() }, } for relative_linenr, line in ipairs(lines) do - line = vim.trim(line) -- end of inline scripting if is_request_line == true and line:match("^%%}$") then is_prerequest_handler_script_inline = false @@ -252,9 +282,7 @@ M.get_document = function() elseif is_request_line == true and line:match("^< (.*)$") then local scriptfile = line:match("^< (.*)$") table.insert(request.scripts.pre_request.files, scriptfile) - -- It's a comment, skip it elseif line == "" and is_body_section == false then - -- Skip empty lines if is_request_line == false then is_body_section = true end @@ -289,37 +317,42 @@ M.get_document = function() variable_name = variable_name:sub(1) variables[variable_name] = variable_value end - elseif is_body_section == true and #line > 0 then + elseif is_body_section == true then + local _, content_type_header_value = PARSER_UTILS.get_header(request.headers, "content-type") + -- If the request body is nil, this also means that the body_display is nil + -- so we need to initialize it to an empty string, because the header value content-type + -- is present and implies that there is a body to be sent if request.body == nil then request.body = "" + request.body_display = "" end if line:find("^<") then - if - request.headers["content-type"] ~= nil and request.headers["content-type"]:find("^multipart/form%-data") - then + if content_type_header_value ~= nil and content_type_header_value:find("^multipart/form%-data") then request.body = request.body .. line .. "\r\n" + request.body_display = request.body_display .. line .. "\r\n" else local file_path = vim.trim(line:sub(2)) local contents = FS.read_file(file_path) - if contents then - request.body = request.body .. contents + if contents ~= nil then + request.body = request.body .. contents .. "\r\n" + request.body_display = request.body_display .. "[[external file skipped]]\r\n" else - vim.notify("The file '" .. file_path .. "' was not found. Skipping ...", "warn") + Logger.warn("The file '" .. file_path .. "' was not found. Skipping ...") end end else - if - (request.headers["content-type"] ~= nil and request.headers["content-type"]:find("^multipart/form%-data")) - or PARSER_UTILS.contains_meta_tag(request, "graphql") - then + if content_type_header_value ~= nil and content_type_header_value:find("^multipart/form%-data") then request.body = request.body .. line .. "\r\n" + request.body_display = request.body_display .. line .. "\r\n" elseif - request.headers["content-type"] ~= nil - and request.headers["content-type"]:find("^application/x%-www%-form%-urlencoded") + content_type_header_value ~= nil + and content_type_header_value:find("^application/x%-www%-form%-urlencoded") then request.body = request.body .. line + request.body_display = request.body_display .. line else request.body = request.body .. line .. "\r\n" + request.body_display = request.body_display .. line .. "\r\n" end end elseif is_request_line == false and line:match("^([^:]+):%s*(.*)$") then @@ -334,7 +367,8 @@ M.get_document = function() -- dynamic variables are defined as `{{$variable_name}}` local key, value = line:match("^([^:]+):%s*(.*)$") if key and value then - request.headers[key:lower()] = value + request.headers[key] = value + request.headers_raw[key] = value end elseif is_request_line == true then -- Request line (e.g., GET http://example.com HTTP/1.1) @@ -359,6 +393,7 @@ M.get_document = function() end if request.body ~= nil then request.body = vim.trim(request.body) + request.body_display = vim.trim(request.body_display) end request.end_line = line_offset + block_line_count line_offset = request.end_line + 1 -- +1 for the '###' separator line @@ -368,6 +403,10 @@ M.get_document = function() end M.get_request_at = function(requests, linenr) + if requests == nil then + Logger.error("No requests found in the document") + return nil + end if linenr == nil then linenr = vim.api.nvim_win_get_cursor(0)[1] end @@ -410,14 +449,20 @@ end -- extend the document_variables with the variables defined in the request -- via the # @file-to-variable variable_name file_path metadata syntax +---@param document_variables table|nil +---@param request Request +---@return table local function extend_document_variables(document_variables, request) + document_variables = document_variables or {} for _, metadata in ipairs(request.metadata) do if metadata then if metadata.name == "file-to-variable" then local kv = vim.split(metadata.value, " ") local variable_name = kv[1] local file_path = kv[2] - local file_contents = FS.read_file(file_path) + local is_binary = #kv > 2 and kv[3] == "binary" or false + file_path = FS.get_file_path(file_path) + local file_contents = FS.read_file(file_path, is_binary) if file_contents then document_variables[variable_name] = file_contents end @@ -427,43 +472,58 @@ local function extend_document_variables(document_variables, request) return document_variables end +local cleanup_request_files = function() + FS.delete_file(GLOBALS.HEADERS_FILE) + FS.delete_file(GLOBALS.BODY_FILE) + FS.delete_file(GLOBALS.COOKIES_JAR_FILE) +end + ---@class ResponseBodyToFile ---@field file string -- The file path to write the response body to ---@field overwrite boolean -- Whether to overwrite the file if it already exists ----@class ScriptsItems ----@field inline table -- Inline post-request handler scripts - each element is a line of the script ----@field file table -- File post-request handler scripts - each element is a file path ---- ---@class Scripts ----@field pre_request ScriptsItems[] -- Pre-request handler scripts ----@field post_request ScriptsItems[] -- Post-request handler scripts +---@field pre_request ScriptData -- Pre-request handler scripts +---@field post_request ScriptData -- Post-request handler scripts ---@class Request ----@field metadata table ----@field method string ----@field url table ----@field headers table ----@field body table ----@field cmd table ----@field ft string ----@field http_version string ----@field show_icon_line_number string ----@field scripts Scripts +---@field metadata table[] -- Metadata of the request +---@field method string -- The HTTP method of the request +---@field url_raw string -- The raw URL as it appears in the document +---@field url string -- The URL with variables and dynamic variables replaced +---@field headers table -- The headers with variables and dynamic variables replaced +---@field headers_raw table -- The headers as they appear in the document +---@field body_raw string|nil -- The raw body as it appears in the document +---@field body_computed string|nil -- The computed body as sent by curl; with variables and dynamic variables replaced +---@field body_display string|nil -- The body with variables and dynamic variables replaced and sanitized +---(e.g. with binary files replaced with a placeholder) +---@field body string|nil -- The body with variables and dynamic variables replaced +---@field environment table -- The environment- and document-variables +---@field cmd table -- The command to execute the request +---@field ft string -- The filetype of the document +---@field http_version string -- The HTTP version of the request +---@field show_icon_line_number string -- The line number to show the icon +---@field scripts Scripts -- The scripts to run before and after the request ---@field redirect_response_body_to_files ResponseBodyToFile[] ---Parse a request and return the request on itself, its headers and body ---@param start_request_linenr number|nil The line number where the request starts ---@return Request|nil -- Table containing the request data or nil if parsing fails -function M.parse(start_request_linenr) +function M.get_basic_request_data(start_request_linenr) local res = { metadata = {}, method = "GET", - url = {}, + url = "", + url_raw = "", headers = {}, - body = {}, + headers_raw = {}, + body = nil, + body_raw = nil, + body_computed = nil, + body_display = nil, cmd = {}, ft = "text", + environment = {}, redirect_response_body_to_files = {}, scripts = { pre_request = { @@ -477,13 +537,11 @@ function M.parse(start_request_linenr) }, } - local req, document_variables + local req if CONFIG:get().treesitter then - document_variables = TS.get_document_variables() req = TS.get_request_at(start_request_linenr) else - local requests - document_variables, requests = M.get_document() + local _, requests = M.get_document() req = M.get_request_at(requests, start_request_linenr) end @@ -491,54 +549,74 @@ function M.parse(start_request_linenr) return nil end - Scripts.javascript.run("pre_request", req.scripts.pre_request) - local env = ENV_PARSER.get_env() - - DB.update().previous_request = DB.find_unique("current_request") - - document_variables = extend_document_variables(document_variables, req) - res.scripts.pre_request = req.scripts.pre_request res.scripts.post_request = req.scripts.post_request res.show_icon_line_number = req.show_icon_line_number - res.url = parse_url(req.url, document_variables, env) + res.headers = req.headers + res.headers_raw = req.headers_raw + res.url_raw = req.url res.method = req.method res.http_version = req.http_version - res.headers = parse_headers(req.headers, document_variables, env) - res.body = parse_body(req.body, document_variables, env) + res.body_raw = req.body + res.body_display = req.body_display res.metadata = req.metadata res.redirect_response_body_to_files = req.redirect_response_body_to_files - -- We need to append the contents of the file to - -- the body if it is a POST request, - -- or to the URL itself if it is a GET request - if req.body_type == "input" and not CONFIG:get().treesitter then - if req.body_path:match("%.graphql$") or req.body_path:match("%.gql$") then - local graphql_file = io.open(req.body_path, "r") - local graphql_query = graphql_file:read("*a") - graphql_file:close() - if res.method == "POST" then - res.body = '{ "query": "' .. graphql_query .. '" }' - else - graphql_query = - STRING_UTILS.url_encode(STRING_UTILS.remove_extra_space(STRING_UTILS.remove_newline(graphql_query))) - res.graphql_query = STRING_UTILS.url_decode(graphql_query) - res.url = res.url .. "?query=" .. graphql_query - end - else - local file = io.open(req.body_path, "r") - local body = file:read("*a") - file:close() - res.body = body - end + return res +end + +---Replace the variables in the URL, headers and body +---@param res Request -- The request object +---@param document_variables table -- The variables defined in the document +---@param env table -- The environment variables +---@param silent boolean -- Whether to suppress not found variable warnings +---@return string, table, string|nil, string|nil -- The URL, headers, body and body_display with variables replaced +local replace_variables_in_url_headers_body = function(res, document_variables, env, silent) + local url = parse_url(res.url_raw, document_variables, env, silent) + local headers = parse_headers(res.headers, document_variables, env, silent) + local body = parse_body(res.body_raw, document_variables, env, silent) + local body_display = parse_body_display(res.body_display, document_variables, env, silent) + return url, headers, body, body_display +end + +---Parse a request and return the request on itself, its headers and body +---@param start_request_linenr number|nil The line number where the request starts +---@return Request|nil -- Table containing the request data or nil if parsing fails +M.parse = function(start_request_linenr) + local res = M.get_basic_request_data(start_request_linenr) + + if res == nil then + return nil + end + + local has_pre_request_scripts = #res.scripts.pre_request.inline > 0 or #res.scripts.pre_request.files > 0 + + local document_variables + if CONFIG:get().treesitter then + document_variables = TS.get_document_variables() + else + document_variables = M.get_document() end - -- Merge headers from the _base environment if it exists - if DB.find_unique("http_client_env_base") then - local default_headers = DB.find_unique("http_client_env_base")["DEFAULT_HEADERS"] + DB.update().previous_request = DB.find_unique("current_request") + + local env = ENV_PARSER.get_env() + + document_variables = extend_document_variables(document_variables, res) + res.environment = vim.tbl_extend("force", env, document_variables) + + -- INFO: if has_pre_request_script: + -- silently replace the variables in the URL, headers and body, otherwise warn the user + -- for non existing variables + res.url, res.headers, res.body, res.body_display = + replace_variables_in_url_headers_body(res, document_variables, env, has_pre_request_scripts) + + -- Merge headers from the $shared environment if it does not exist in the request + -- this ensures that you can always override the headers in the request + if DB.find_unique("http_client_env_shared") then + local default_headers = DB.find_unique("http_client_env_shared")["$default_headers"] if default_headers then for key, value in pairs(default_headers) do - key = key:lower() if res.headers[key] == nil then res.headers[key] = value end @@ -546,76 +624,138 @@ function M.parse(start_request_linenr) end end - -- build the command to exectute the request - table.insert(res.cmd, "curl") + local is_graphql = PARSER_UTILS.contains_meta_tag(res, "graphql") + or PARSER_UTILS.contains_header(res.headers, "x-request-type", "graphql") + if res.body ~= nil then + if is_graphql then + local gql_json = GRAPHQL_PARSER.get_json(res.body) + if gql_json then + res.body_computed = gql_json + end + else + res.body_computed = res.body + end + end + if CONFIG.get().treesitter then + -- treesitter parser handles graphql requests before this point + is_graphql = false + end + + FS.write_file(GLOBALS.REQUEST_FILE, vim.fn.json_encode(res), false) + -- PERF: We only want to run the scripts if they exist + -- Also we don't want to re-run the environment replace_variables_in_url_headers_body + -- if we don't actually have any scripts to run that could have changed the environment + if has_pre_request_scripts then + -- INFO: + -- This runs a client and request script that can be used to magic things + -- See: https://www.jetbrains.com/help/idea/http-response-reference.html + M.scripts.javascript.run("pre_request", res.scripts.pre_request) + -- INFO: now replace the variables in the URL, headers and body again, + -- because user scripts could have changed them, + -- but this time also warn the user if a variable is not found + env = ENV_PARSER.get_env() + res.url, res.headers, res.body, res.body_display = + replace_variables_in_url_headers_body(res, document_variables, env, false) + end + + -- build the command to execute the request + table.insert(res.cmd, CONFIG.get().curl_path) table.insert(res.cmd, "-s") table.insert(res.cmd, "-D") - table.insert(res.cmd, PLUGIN_TMP_DIR .. "/headers.txt") + table.insert(res.cmd, GLOBALS.HEADERS_FILE) table.insert(res.cmd, "-o") - table.insert(res.cmd, PLUGIN_TMP_DIR .. "/body.txt") + table.insert(res.cmd, GLOBALS.BODY_FILE) table.insert(res.cmd, "-w") table.insert(res.cmd, "@" .. CURL_FORMAT_FILE) table.insert(res.cmd, "-X") table.insert(res.cmd, res.method) - local is_graphql = PARSER_UTILS.contains_meta_tag(req, "graphql") - or PARSER_UTILS.contains_header(res.headers, "x-request-type", "GraphQL") - if CONFIG.get().treesitter then - -- treesitter parser handles graphql requests before this point - is_graphql = false - end + local content_type_header_name, content_type_header_value = PARSER_UTILS.get_header(res.headers, "content-type") - if res.headers["content-type"] ~= nil and res.body ~= nil then + if content_type_header_name and content_type_header_value and res.body ~= nil then -- check if we are a graphql query -- we need this here, because the user could have defined the content-type -- as application/json, but the body is a graphql query - -- This can happen when the user is using http-client.env.json with DEFAULT_HEADERS. + -- This can happen when the user is using http-client.env.json with $shared -> $default_headers. if is_graphql then local gql_json = GRAPHQL_PARSER.get_json(res.body) if gql_json then - table.insert(res.cmd, "--data") - table.insert(res.cmd, gql_json) - res.headers["content-type"] = "application/json" + if PARSER_UTILS.contains_meta_tag(res, "write-body-to-temporary-file") then + local tmp_file = FS.get_temp_file(res.body) + if tmp_file ~= nil then + table.insert(res.cmd, "--data") + table.insert(res.cmd, "@" .. tmp_file) + res.headers[content_type_header_name] = "application/json" + else + Logger.error("Failed to create a temporary file for the request body") + end + else + table.insert(res.cmd, "--data") + table.insert(res.cmd, gql_json) + res.headers[content_type_header_name] = "application/json" + end + end + elseif content_type_header_value:find("^multipart/form%-data") then + local tmp_file = FS.get_binary_temp_file(res.body) + if tmp_file ~= nil then + table.insert(res.cmd, "--data-binary") + table.insert(res.cmd, "@" .. tmp_file) + else + Logger.error("Failed to create a temporary file for the binary request body") end - elseif res.headers["content-type"]:find("^multipart/form%-data") then - table.insert(res.cmd, "--data-binary") - table.insert(res.cmd, res.body) else - table.insert(res.cmd, "--data") - table.insert(res.cmd, res.body) + if PARSER_UTILS.contains_meta_tag(res, "write-body-to-temporary-file") then + local tmp_file = FS.get_temp_file(res.body) + if tmp_file ~= nil then + table.insert(res.cmd, "--data") + table.insert(res.cmd, "@" .. tmp_file) + else + Logger.error("Failed to create a temporary file for the request body") + end + else + table.insert(res.cmd, "--data") + table.insert(res.cmd, res.body) + end end else -- no content type supplied -- check if we are a graphql query if is_graphql then local gql_json = GRAPHQL_PARSER.get_json(res.body) if gql_json then - table.insert(res.cmd, "--data") - table.insert(res.cmd, gql_json) - res.headers["content-type"] = "application/json" + local tmp_file = FS.get_temp_file(res.body) + if tmp_file ~= nil then + table.insert(res.cmd, "--data") + table.insert(res.cmd, "@" .. tmp_file) + res.headers["content-type"] = "application/json" + res.body_computed = gql_json + else + Logger.error("Failed to create a temporary file for the request body") + end end end end - if res.headers["authorization"] then - local auth_header = res.headers["authorization"] - local authtype = auth_header:match("^(%w+)%s+.*") + local auth_header_name, auth_header_value = PARSER_UTILS.get_header(res.headers, "authorization") + + if auth_header_name and auth_header_value then + local authtype = auth_header_value:match("^(%w+)%s+.*") if authtype == nil then - authtype = auth_header:match("^(%w+)%s*$") + authtype = auth_header_value:match("^(%w+)%s*$") end if authtype ~= nil then authtype = authtype:lower() if authtype == "ntlm" or authtype == "negotiate" or authtype == "digest" or authtype == "basic" then - local match, authuser, authpw = auth_header:match("^(%w+)%s+([^%s:]+)%s*[:%s]%s*([^%s]+)%s*$") + local match, authuser, authpw = auth_header_value:match("^(%w+)%s+([^%s:]+)%s*[:%s]%s*([^%s]+)%s*$") if match ~= nil or (authtype == "ntlm" or authtype == "negotiate") then table.insert(res.cmd, "--" .. authtype) table.insert(res.cmd, "-u") table.insert(res.cmd, (authuser or "") .. ":" .. (authpw or "")) - res.headers["authorization"] = nil + res.headers[auth_header_name] = nil end elseif authtype == "aws" then - local key, secret, optional = auth_header:match("^%w+%s([^%s]+)%s*([^%s]+)[%s$]+(.*)$") + local key, secret, optional = auth_header_value:match("^%w+%s([^%s]+)%s*([^%s]+)[%s$]+(.*)$") local token = optional:match("token:([^%s]+)") local region = optional:match("region:([^%s]+)") local service = optional:match("service:([^%s]+)") @@ -634,7 +774,40 @@ function M.parse(start_request_linenr) table.insert(res.cmd, "-H") table.insert(res.cmd, "x-amz-security-token:" .. token) end - res.headers["authorization"] = nil + res.headers[auth_header_name] = nil + end + end + end + + local protocol, host, port = res.url:match("^([^:]*)://([^:/]*):([^/]*)") + if not protocol then + protocol, host = res.url:match("^([^:]*)://([^:/]*)") + end + if protocol == "https" then + local certificate = CONFIG.get().certificates[host .. ":" .. (port or "443")] + if not certificate then + certificate = CONFIG.get().certificates[host] + end + if not certificate then + while host ~= "" do + certificate = CONFIG.get().certificates["*." .. host .. ":" .. (port or "443")] + if not certificate then + certificate = CONFIG.get().certificates["*." .. host] + end + if certificate then + break + end + host = host:gsub("^[^%.]+%.?", "") + end + end + if certificate then + if certificate.cert then + table.insert(res.cmd, "--cert") + table.insert(res.cmd, certificate.cert) + end + if certificate.key then + table.insert(res.cmd, "--key") + table.insert(res.cmd, certificate.key) end end end @@ -650,7 +823,7 @@ function M.parse(start_request_linenr) table.insert(res.cmd, "kulala.nvim/" .. GLOBALS.VERSION) -- if the user has not specified the no-cookie meta tag, -- then use the cookies jar file - if PARSER_UTILS.contains_meta_tag(req, "no-cookie-jar") == false then + if PARSER_UTILS.contains_meta_tag(res, "no-cookie-jar") == false then table.insert(res.cmd, "--cookie-jar") table.insert(res.cmd, GLOBALS.COOKIES_JAR_FILE) end @@ -658,15 +831,7 @@ function M.parse(start_request_linenr) table.insert(res.cmd, additional_curl_option) end table.insert(res.cmd, res.url) - -- TODO: - -- Make a cleanup function that deletes the files - -- and mayebe sets up other things - FS.delete_file(GLOBALS.HEADERS_FILE) - FS.delete_file(GLOBALS.BODY_FILE) - FS.delete_file(GLOBALS.COOKIES_JAR_FILE) - if CONFIG.get().debug then - FS.write_file(PLUGIN_TMP_DIR .. "/request.txt", table.concat(res.cmd, " "), false) - end + cleanup_request_files() DB.update().current_request = res -- Save this to global, -- so .replay() can be triggered from any buffer or window diff --git a/lua/kulala/parser/inspect.lua b/lua/kulala/parser/inspect.lua index 823d4de..4b49b7e 100644 --- a/lua/kulala/parser/inspect.lua +++ b/lua/kulala/parser/inspect.lua @@ -4,6 +4,9 @@ local M = {} M.get_contents = function() local req = Parser.parse() local contents = {} + if req == nil then + return contents + end if req.http_version ~= nil then req.http_version = " " .. req.http_version else @@ -13,9 +16,12 @@ M.get_contents = function() for header_key, header_value in pairs(req.headers) do table.insert(contents, header_key .. ": " .. header_value) end - if req.body ~= nil then + -- Use the body_display, because it's meant to be human-readable + -- e.g. without binary data + if req.body_display ~= nil then + -- use an empty line to separate headers and body table.insert(contents, "") - local body_as_table = vim.split(req.body, "\r?\n") + local body_as_table = vim.split(req.body_display, "\r?\n") for _, line in ipairs(body_as_table) do table.insert(contents, line) end diff --git a/lua/kulala/parser/scripts/engines/javascript/init.lua b/lua/kulala/parser/scripts/engines/javascript/init.lua new file mode 100644 index 0000000..1df8d87 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/init.lua @@ -0,0 +1,181 @@ +local FS = require("kulala.utils.fs") +local GLOBALS = require("kulala.globals") +local CONFIG = require("kulala.config") +local Logger = require("kulala.logger") +local M = {} + +local NPM_EXISTS = vim.fn.executable("npm") == 1 +local NODE_EXISTS = vim.fn.executable("node") == 1 +local NPM_BIN = vim.fn.exepath("npm") +local NODE_BIN = vim.fn.exepath("node") +local SCRIPTS_DIR = FS.get_scripts_dir() +local REQUEST_SCRIPTS_DIR = FS.get_request_scripts_dir() +local SCRIPTS_BUILD_DIR = FS.get_tmp_scripts_build_dir() +local BASE_DIR = FS.join_paths(SCRIPTS_DIR, "engines", "javascript", "lib") +local BASE_FILE_PRE_CLIENT_ONLY = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "pre_request_client_only.js") +local BASE_FILE_PRE = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "pre_request.js") +local BASE_FILE_POST_CLIENT_ONLY = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "post_request_client_only.js") +local BASE_FILE_POST = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "post_request.js") +local FILE_MAPPING = { + pre_request_client_only = BASE_FILE_PRE_CLIENT_ONLY, + pre_request = BASE_FILE_PRE, + post_request_client_only = BASE_FILE_POST_CLIENT_ONLY, + post_request = BASE_FILE_POST, +} + +M.install = function() + FS.copy_dir(BASE_DIR, SCRIPTS_BUILD_DIR) + local res_install = vim.system({ NPM_BIN, "install", "--prefix", SCRIPTS_BUILD_DIR }):wait() + if res_install.code ~= 0 then + Logger.error("npm install fail with code " .. res_install.code) + return + end + local res_build = vim.system({ NPM_BIN, "run", "build", "--prefix", SCRIPTS_BUILD_DIR }):wait() + if res_build.code ~= 0 then + Logger.error("npm run build fail with code " .. res_build.code) + return + end +end + +---@class Scripts +---@field path string -- path to script +---@field cwd string -- current working directory + +---@class ScriptData table -- data for script +---@field inline string -- inline scripts +---@field files string
-- paths to script files + +---@param script_type "pre_request_client_only" | "pre_request" | "post_request_client_only" | "post_request" +---type of script +---@param is_external_file boolean -- is external file +---@param script_data string
| string -- either inline script or path to script file +local generate_one = function(script_type, is_external_file, script_data) + local userscript + local base_file_path = FILE_MAPPING[script_type] + if base_file_path == nil then + return nil, nil + end + local base_file = FS.read_file(base_file_path) + if base_file == nil then + return nil, nil + end + local script_cwd + -- buf_dir is "kulala:" when the buffer is scratch buffer + -- in this case, use current working directory for script_cwd and base_dir + local buf_dir = FS.get_current_buffer_dir() + + if is_external_file then + -- if script_data starts with ./ or ../, it is a relative path + if string.match(script_data, "^%./") or string.match(script_data, "^%../") then + local local_script_path = script_data:gsub("^%./", "") + local base_dir = buf_dir == "kulala:" and vim.loop.cwd() or buf_dir + script_data = FS.join_paths(base_dir, local_script_path) + end + script_cwd = buf_dir == "kulala:" and vim.loop.cwd() or FS.get_dir_by_filepath(script_data) + userscript = FS.read_file(script_data) + else + script_cwd = buf_dir == "kulala:" and vim.loop.cwd() or buf_dir + userscript = vim.fn.join(script_data, "\n") + end + base_file = base_file .. "\n" .. userscript + local uuid = FS.get_uuid() + local script_path = FS.join_paths(REQUEST_SCRIPTS_DIR, uuid .. ".js") + FS.write_file(script_path, base_file, false) + return script_path, script_cwd +end + +---@param script_type "pre_request_client_only" | "pre_request" | "post_request_client_only" | "post_request" +---type of script +---@param scripts_data ScriptData -- data for scripts +---@return Scripts
-- paths to scripts +local generate_all = function(script_type, scripts_data) + local scripts = {} + local script_path, script_cwd = generate_one(script_type, false, scripts_data.inline) + if script_path ~= nil and script_cwd ~= nil then + table.insert(scripts, { path = script_path, cwd = script_cwd }) + end + for _, script_data in ipairs(scripts_data.files) do + script_path, script_cwd = generate_one(script_type, true, script_data) + if script_path ~= nil and script_cwd ~= nil then + table.insert(scripts, { path = script_path, cwd = script_cwd }) + end + end + return scripts +end + +local scripts_is_empty = function(scripts_data) + return #scripts_data.inline == 0 and #scripts_data.files == 0 +end + +---@param type "pre_request_client_only" | "pre_request" | "post_request_client_only" | "post_request" -- type of script +---@param data ScriptData +M.run = function(type, data) + if scripts_is_empty(data) then + return + end + + if not NODE_EXISTS then + Logger.error("node not found, please install nodejs") + return + end + + if not NPM_EXISTS then + Logger.error("npm not found, please install nodejs") + return + end + + if not FS.file_exists(BASE_FILE_PRE) or not FS.file_exists(BASE_FILE_POST) then + Logger.warn("Javascript base files not found. Installing dependencies...") + M.install() + end + + local scripts = generate_all(type, data) + if #scripts == 0 then + return + end + + for _, script in ipairs(scripts) do + local output = vim + .system({ + NODE_BIN, + script.path, + }, { + cwd = script.cwd, + env = { + NODE_PATH = FS.join_paths(script.cwd, "node_modules"), + }, + }) + :wait() + if output ~= nil then + FS.delete_file(GLOBALS.SCRIPT_PRE_OUTPUT_FILE) + FS.delete_file(GLOBALS.SCRIPT_POST_OUTPUT_FILE) + + if output.stderr ~= nil and not string.match(output.stderr, "^%s*$") then + if not CONFIG.get().disable_script_print_output then + vim.print(output.stderr) + end + if type == "pre_request" then + FS.write_file(GLOBALS.SCRIPT_PRE_OUTPUT_FILE, output.stderr) + elseif type == "post_request" then + FS.write_file(GLOBALS.SCRIPT_POST_OUTPUT_FILE, output.stderr) + end + end + if output.stdout ~= nil and not string.match(output.stdout, "^%s*$") then + if not CONFIG.get().disable_script_print_output then + vim.print(output.stdout) + end + if type == "pre_request" then + if not FS.write_file(GLOBALS.SCRIPT_PRE_OUTPUT_FILE, output.stdout) then + Logger.error("write " .. GLOBALS.SCRIPT_PRE_OUTPUT_FILE .. " fail") + end + elseif type == "post_request" then + if not FS.write_file(GLOBALS.SCRIPT_POST_OUTPUT_FILE, output.stdout) then + Logger.error("write " .. GLOBALS.SCRIPT_POST_OUTPUT_FILE .. " fail") + end + end + end + end + end +end + +return M diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/dist/.gitignore b/lua/kulala/parser/scripts/engines/javascript/lib/dist/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/dist/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/eslint.config.cjs b/lua/kulala/parser/scripts/engines/javascript/lib/eslint.config.cjs new file mode 100644 index 0000000..4af487e --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/eslint.config.cjs @@ -0,0 +1,6 @@ +module.exports = [ + { + ...require('eslint-config-love'), + files: ['src/**/*.ts'], + }, +] diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/package-lock.json b/lua/kulala/parser/scripts/engines/javascript/lib/package-lock.json new file mode 100644 index 0000000..bcacb43 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/package-lock.json @@ -0,0 +1,4066 @@ +{ + "name": "kulala-scripts-js", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kulala-scripts-js", + "version": "1.0.0", + "devDependencies": { + "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", + "@rollup/plugin-typescript": "12.1.0", + "@types/node": "22.7.4", + "@typescript-eslint/eslint-plugin": "7.18.0", + "eslint": "8.57.1", + "eslint-config-love": "83.0.0", + "eslint-plugin-import": "2.30.0", + "eslint-plugin-n": "15.7.0", + "eslint-plugin-promise": "6.6.0", + "rollup": "4.22.5", + "tslib": "2.7.0", + "typescript": "5.6.2", + "typescript-eslint": "7.18.0" + } + }, + "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.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "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==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "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, + "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, + "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, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": 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, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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==", + "dev": 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==", + "dev": 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==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", + "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.0.tgz", + "integrity": "sha512-Kzs8KGJofe7cfTRODsnG1jNGxSvU8gVoNNd7Z/QaY25AYwe2LSSUpx/kPxqF38NYkpR8de3m51r9uwJpDlz6dg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", + "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "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/node": { + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/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==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.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/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "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/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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "dev": true + }, + "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-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/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==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "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, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/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==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/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/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/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/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "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.1", + "@humanwhocodes/config-array": "^0.13.0", + "@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-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-love": { + "version": "83.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-love/-/eslint-config-love-83.0.0.tgz", + "integrity": "sha512-sXjmJkPlCKxtnMLKrpuEgFSKLiK29QF8RuWkULx+P+E0+0tEP3siZilvw5UNmRWvhWvEmdb0ScqtzGmgW7Gekw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mightyiam" + }, + "https://wise.com/pay/me/shaharo" + ], + "dependencies": { + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^17.0.0", + "eslint-plugin-promise": "^7.0.0", + "typescript-eslint": "^8.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "typescript": "*" + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", + "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/parser": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", + "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/type-utils": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", + "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/types": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/utils": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", + "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/eslint-config-love/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-love/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==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-config-love/node_modules/eslint-plugin-n": { + "version": "17.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.3.tgz", + "integrity": "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "enhanced-resolve": "^5.17.0", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^15.8.0", + "ignore": "^5.2.4", + "minimatch": "^9.0.5", + "semver": "^7.5.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-config-love/node_modules/eslint-plugin-promise": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.1.0.tgz", + "integrity": "sha512-8trNmPxdAy3W620WKDpaS65NlM5yAumod6XeC4LOb+jxlkG4IVcp68c6dXY2ev+uT4U1PtG57YDV6EGAXN0GbQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-config-love/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-config-love/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-love/node_modules/typescript-eslint": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.0.tgz", + "integrity": "sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.8.0", + "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/utils": "8.8.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "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-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "dev": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "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.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "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-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "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-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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/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/node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/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/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/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/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/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true + }, + "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/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "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==", + "dev": true + }, + "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" + ], + "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==", + "dev": true, + "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/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/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "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-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-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": 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/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/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "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/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "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==", + "dev": true + }, + "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/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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "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==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "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==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "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==", + "dev": true + }, + "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/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-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-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "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-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-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-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "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, + "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-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-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==", + "dev": true, + "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/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==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-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/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/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/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/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.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": 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/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.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==", + "dev": true, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true + }, + "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/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "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==", + "dev": 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/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==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-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/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/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==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": 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/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "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/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "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-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": 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==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", + "fsevents": "~2.3.2" + } + }, + "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==", + "dev": 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==", + "dev": 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/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/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/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "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==", + "dev": true, + "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/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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/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/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": 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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/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/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "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/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "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/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "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-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/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/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", + "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "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==", + "dev": true, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/package.json b/lua/kulala/parser/scripts/engines/javascript/lib/package.json new file mode 100644 index 0000000..38ca3e5 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/package.json @@ -0,0 +1,25 @@ +{ + "name": "kulala-scripts-js", + "version": "1.0.0", + "scripts": { + "build": "rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript", + "lint": "eslint" + }, + "type": "module", + "devDependencies": { + "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", + "@rollup/plugin-typescript": "12.1.0", + "@types/node": "22.7.4", + "@typescript-eslint/eslint-plugin": "7.18.0", + "eslint": "8.57.1", + "eslint-config-love": "83.0.0", + "eslint-plugin-import": "2.30.0", + "eslint-plugin-n": "15.7.0", + "eslint-plugin-promise": "6.6.0", + "rollup": "4.22.5", + "tslib": "2.7.0", + "typescript": "5.6.2", + "typescript-eslint": "7.18.0" + } +} diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/rollup.config.ts b/lua/kulala/parser/scripts/engines/javascript/lib/rollup.config.ts new file mode 100644 index 0000000..a08f28b --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/rollup.config.ts @@ -0,0 +1,45 @@ +import typescript from '@rollup/plugin-typescript'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; + +export default [ + { + input: './src/pre_request.ts', + output: { + dir: 'dist', + format: 'cjs' + }, + plugins: [ + typescript(), + nodeResolve(), + terser({ + mangle: { + reserved: [ + 'client', + 'request', + ], + }, + }), + ], + }, + { + input: './src/post_request.ts', + output: { + dir: 'dist', + format: 'cjs' + }, + plugins: [ + typescript(), + nodeResolve(), + terser({ + mangle: { + reserved: [ + 'client', + 'response', + 'request', + ], + }, + }), + ], + }, +] diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/Client.ts b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/Client.ts new file mode 100644 index 0000000..76dee21 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/Client.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs'; +import * as path from 'path'; +const _GLOBAL_VARIABLES_FILEPATH = path.join(__dirname, '..', 'global_variables.json'); + +const getGlobalVariables = (): Record => { + let json: Record = {}; + if (fs.existsSync(_GLOBAL_VARIABLES_FILEPATH)) { + json = JSON.parse(fs.readFileSync(_GLOBAL_VARIABLES_FILEPATH, { encoding: 'utf8' })) as Record; + } + return json; +}; + +export const Client = { + log: (...args: unknown[]): void => { + console.log(...args); + }, + test: (): void => { + console.error('Not yet implemented'); + }, + assert: (): void => { + console.error('Not yet implemented'); + }, + exit: (): void => { + process.exit(); + }, + global: { + set: function (key: string, value: string) { + const json = getGlobalVariables(); + json[key] = value; + fs.writeFileSync(_GLOBAL_VARIABLES_FILEPATH, JSON.stringify(json)); + }, + get: function (key: string) { + const json = getGlobalVariables(); + return json[key]; + }, + isEmpty: function () { + const noItemsInObject = 0; + const json = getGlobalVariables(); + return Object.keys(json).length === noItemsInObject; + }, + clear: function (key: string) { + const json = getGlobalVariables(); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + if (key in json) delete json[key]; + fs.writeFileSync(_GLOBAL_VARIABLES_FILEPATH, JSON.stringify(json)); + }, + clearAll: function () { + fs.writeFileSync(_GLOBAL_VARIABLES_FILEPATH, '{}'); + } + } +}; diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PostRequest.ts b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PostRequest.ts new file mode 100644 index 0000000..3201e2b --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PostRequest.ts @@ -0,0 +1,125 @@ +import * as fs from 'fs'; +import * as path from 'path'; +const _REQUEST_FILEPATH = path.join(__dirname, '..', '..', 'request.json'); +const _REQUEST_VARIABLES_FILEPATH = path.join(__dirname, 'request_variables.json'); + +type RequestVariables = Record; + +interface RequestJson { + headers: Record, + headers_raw: Record, + body_raw: string, + body_computed: string | undefined, + body: string | object, + method: string, + url_raw: string, + url: string, + environment: Record, +}; + +const getRequestVariables = (): RequestVariables => { + let reqVariables: RequestVariables = {}; + try { + reqVariables = JSON.parse(fs.readFileSync(_REQUEST_VARIABLES_FILEPATH, { encoding: 'utf8' })) as RequestVariables; + } catch (e) { + // do nothing + } + return reqVariables; +}; + +interface HeaderObject { + name: () => string, + getRawValue: () => string, + tryGetSubstituted: () => string, +}; + +const getHeaderObject = (headerName: string, headerRawValue: string, headerValue: string | undefined): HeaderObject | null => { + if (headerValue === undefined) { + return null; + } + return { + name: () => { + return headerName + }, + getRawValue: () => { + return headerRawValue; + }, + tryGetSubstituted: () => { + return headerValue; + }, + }; +}; + +const req = JSON.parse(fs.readFileSync(_REQUEST_FILEPATH, { encoding: 'utf8' })) as RequestJson; + +export const Request = { + body: { + getRaw: () => { + return req.body_raw; + }, + tryGetSubstituted: () => { + return req.body; + }, + getComputed: () => { + return req.body_computed; + }, + }, + headers: { + findByName: (headerName: string) => { + return getHeaderObject(headerName, req.headers_raw[headerName], req.headers[headerName]); + }, + all: function (): HeaderObject[] { + const h = []; + for (const [key, value] of Object.entries(req.headers)) { + const item = getHeaderObject(key, req.headers_raw[key], value); + if (item !== null) { + h.push(item); + } + } + return h; + }, + }, + environment: { + getName: (name: string): string | null => { + if (name in req.environment) { + return req.environment[name]; + } + return null; + }, + get: (name: string): string | null => { + if (name in req.environment) { + return req.environment[name]; + } + return null; + } + }, + method: req.method, + url: { + getRaw: () => { + return req.url_raw; + }, + tryGetSubstituted: () => { + return req.url + }, + }, + status: null, + contentType: { + mimeType: null, + charset: null, + }, + variables: { + set: function (key: string, value: string) { + const reqVariables = getRequestVariables(); + reqVariables[key] = value; + fs.writeFileSync(_REQUEST_VARIABLES_FILEPATH, JSON.stringify(reqVariables)); + }, + get: function (key: string) { + const reqVariables = getRequestVariables(); + if (key in reqVariables) { + return reqVariables[key]; + } + return null; + } + }, +}; + diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PostRequestResponse.ts b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PostRequestResponse.ts new file mode 100644 index 0000000..fe683f8 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PostRequestResponse.ts @@ -0,0 +1,66 @@ +import * as fs from 'fs'; +import * as path from 'path'; +const _RESPONSE_HEADERS_FILEPATH = path.join(__dirname, '..', '..', 'headers.txt'); +const _RESPONSE_BODY_FILEPATH = path.join(__dirname, '..', '..', 'body.txt'); + +interface HeaderObject { + name: string, + value: string, +}; + +type Headers = Record; +type Body = null | string | object; + +let body: Body = null; +const headers: Headers = {}; + +if (fs.existsSync(_RESPONSE_HEADERS_FILEPATH)) { + const bodyRaw = fs.readFileSync(_RESPONSE_HEADERS_FILEPATH, { encoding: 'utf8' }) + const lines = bodyRaw.split('\n'); + const delimiter = ":"; + for (const line of lines) { + if (!line.includes(delimiter)) { + continue; + } + const [key] = line.split(delimiter); + headers[key] = { + name: key, + value: line.slice(key.length + delimiter.length).trim() + } + } +} + +if (fs.existsSync(_RESPONSE_BODY_FILEPATH)) { + const bodyRaw = fs.readFileSync(_RESPONSE_BODY_FILEPATH, { encoding: 'utf8' }) + try { + body = JSON.parse(bodyRaw) as object; + } catch (e) { + body = bodyRaw; + } +} + +export const Response = { + body, + headers: { + valueOf: (headerName: string): string | null => { + if (headerName in headers) { + return headers[headerName].value; + } + return null; + }, + valuesOf: function (headerName: string): HeaderObject | null { + if (headerName in headers) { + return headers[headerName]; + } + return null; + }, + all: function (): Headers { + return headers; + }, + }, + contentType: { + mimeType: null, + charset: null, + } +}; + diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PreRequestRequest.ts b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PreRequestRequest.ts new file mode 100644 index 0000000..3201e2b --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/src/lib/PreRequestRequest.ts @@ -0,0 +1,125 @@ +import * as fs from 'fs'; +import * as path from 'path'; +const _REQUEST_FILEPATH = path.join(__dirname, '..', '..', 'request.json'); +const _REQUEST_VARIABLES_FILEPATH = path.join(__dirname, 'request_variables.json'); + +type RequestVariables = Record; + +interface RequestJson { + headers: Record, + headers_raw: Record, + body_raw: string, + body_computed: string | undefined, + body: string | object, + method: string, + url_raw: string, + url: string, + environment: Record, +}; + +const getRequestVariables = (): RequestVariables => { + let reqVariables: RequestVariables = {}; + try { + reqVariables = JSON.parse(fs.readFileSync(_REQUEST_VARIABLES_FILEPATH, { encoding: 'utf8' })) as RequestVariables; + } catch (e) { + // do nothing + } + return reqVariables; +}; + +interface HeaderObject { + name: () => string, + getRawValue: () => string, + tryGetSubstituted: () => string, +}; + +const getHeaderObject = (headerName: string, headerRawValue: string, headerValue: string | undefined): HeaderObject | null => { + if (headerValue === undefined) { + return null; + } + return { + name: () => { + return headerName + }, + getRawValue: () => { + return headerRawValue; + }, + tryGetSubstituted: () => { + return headerValue; + }, + }; +}; + +const req = JSON.parse(fs.readFileSync(_REQUEST_FILEPATH, { encoding: 'utf8' })) as RequestJson; + +export const Request = { + body: { + getRaw: () => { + return req.body_raw; + }, + tryGetSubstituted: () => { + return req.body; + }, + getComputed: () => { + return req.body_computed; + }, + }, + headers: { + findByName: (headerName: string) => { + return getHeaderObject(headerName, req.headers_raw[headerName], req.headers[headerName]); + }, + all: function (): HeaderObject[] { + const h = []; + for (const [key, value] of Object.entries(req.headers)) { + const item = getHeaderObject(key, req.headers_raw[key], value); + if (item !== null) { + h.push(item); + } + } + return h; + }, + }, + environment: { + getName: (name: string): string | null => { + if (name in req.environment) { + return req.environment[name]; + } + return null; + }, + get: (name: string): string | null => { + if (name in req.environment) { + return req.environment[name]; + } + return null; + } + }, + method: req.method, + url: { + getRaw: () => { + return req.url_raw; + }, + tryGetSubstituted: () => { + return req.url + }, + }, + status: null, + contentType: { + mimeType: null, + charset: null, + }, + variables: { + set: function (key: string, value: string) { + const reqVariables = getRequestVariables(); + reqVariables[key] = value; + fs.writeFileSync(_REQUEST_VARIABLES_FILEPATH, JSON.stringify(reqVariables)); + }, + get: function (key: string) { + const reqVariables = getRequestVariables(); + if (key in reqVariables) { + return reqVariables[key]; + } + return null; + } + }, +}; + diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/src/post_request.ts b/lua/kulala/parser/scripts/engines/javascript/lib/src/post_request.ts new file mode 100644 index 0000000..1a32cae --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/src/post_request.ts @@ -0,0 +1,7 @@ +import { Client } from './lib/Client'; +import { Response } from './lib/PostRequestResponse'; +import { Request } from './lib/PostRequest'; + +export const client = Client; +export const response = Response; +export const request = Request; diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/src/pre_request.ts b/lua/kulala/parser/scripts/engines/javascript/lib/src/pre_request.ts new file mode 100644 index 0000000..ac7ed2d --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/src/pre_request.ts @@ -0,0 +1,6 @@ +import { Client } from './lib/Client'; +import { Request } from './lib/PreRequestRequest'; + +export const client = Client; +export const request = Request; + diff --git a/lua/kulala/parser/scripts/engines/javascript/lib/tsconfig.json b/lua/kulala/parser/scripts/engines/javascript/lib/tsconfig.json new file mode 100644 index 0000000..a172d92 --- /dev/null +++ b/lua/kulala/parser/scripts/engines/javascript/lib/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "esnext", + "strict": true, + "outDir": "./dist", + "rootDir": "./", + "skipLibCheck": true, + "moduleResolution": "node", + "target": "esnext", + "esModuleInterop": false + }, + "types": ["node"], + "include": ["src/**/*.ts", "rollup.config.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/lua/kulala/parser/scripts/init.lua b/lua/kulala/parser/scripts/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/lua/kulala/parser/scripts/javascript/init.lua b/lua/kulala/parser/scripts/javascript/init.lua new file mode 100644 index 0000000..9b4c56e --- /dev/null +++ b/lua/kulala/parser/scripts/javascript/init.lua @@ -0,0 +1,2 @@ +local M = require("kulala.parser.scripts.engines.javascript") +return M diff --git a/lua/kulala/scripts/utils.lua b/lua/kulala/parser/scripts/utils/init.lua similarity index 100% rename from lua/kulala/scripts/utils.lua rename to lua/kulala/parser/scripts/utils/init.lua diff --git a/lua/kulala/parser/treesitter.lua b/lua/kulala/parser/treesitter.lua index 77b87d0..90a96b0 100644 --- a/lua/kulala/parser/treesitter.lua +++ b/lua/kulala/parser/treesitter.lua @@ -7,12 +7,17 @@ local FS = require("kulala.utils.fs") local STRING_UTILS = require("kulala.utils.string") local M = {} +local QUERIES = {} -local QUERIES = { - section = vim.treesitter.query.parse("http", "(section (request) @request) @section"), - variable = vim.treesitter.query.parse("http", "(variable_declaration) @variable"), +local function init_queries() + if QUERIES.section ~= nil then + return + end + + QUERIES.section = vim.treesitter.query.parse("http", "(section (request) @request) @section") + QUERIES.variable = vim.treesitter.query.parse("http", "(variable_declaration) @variable") - request = vim.treesitter.query.parse( + QUERIES.request = vim.treesitter.query.parse( "http", [[ (comment name: (_) value: (_)) @meta @@ -37,8 +42,8 @@ local QUERIES = { (res_redirect path: (path)) @redirect ]] - ), -} + ) +end local function text(node, metadata) if not node then @@ -58,6 +63,7 @@ local REQUEST_VISITORS = { req.method = fields.method req.http_version = fields.http_version req.body = fields.body + req.body_display = fields.body req.start_line = start_line req.block_line_count = end_line - start_line req.lines_length = end_line - start_line @@ -196,6 +202,7 @@ local function parse_request(section_node) end M.get_document_variables = function(root) + init_queries() root = root or get_root_node() local vars = {} @@ -208,6 +215,7 @@ M.get_document_variables = function(root) end M.get_request_at = function(line) + init_queries() line = line or (vim.fn.line(".") - 1) local root = get_root_node() @@ -219,6 +227,7 @@ M.get_request_at = function(line) end M.get_all_requests = function(root) + init_queries() root = root or get_root_node() local requests = {} @@ -237,6 +246,7 @@ M.get_all_requests = function(root) end M.get_document = function() + init_queries() local root = get_root_node() local variables = M.get_document_variables(root) local requests = M.get_all_requests(root) diff --git a/lua/kulala/parser/utils.lua b/lua/kulala/parser/utils.lua index 374ab31..de72e40 100644 --- a/lua/kulala/parser/utils.lua +++ b/lua/kulala/parser/utils.lua @@ -1,21 +1,130 @@ local M = {} +-- PERF: we do a lot of if else blocks with repeating loops +-- we could "optimize" this by using a single loop and if else blocks +-- that would make the code more readable and easier to maintain +-- but it would also make it slower + +---Get the value of a meta tag from the request +---@param request table The request to check +---@param tag string The meta tag to check for +---@return string|nil +M.get_meta_tag = function(request, tag) + tag = tag:lower() + for _, meta in ipairs(request.metadata) do + if meta.name:lower() == tag then + return meta.value + end + end + return nil +end + +---Check if a request has a specific meta tag +---@param request table The request to check +---@param tag string The meta tag to check for M.contains_meta_tag = function(request, tag) + tag = tag:lower() for _, meta in ipairs(request.metadata) do - if meta.name == tag then + if meta.name:lower() == tag then return true end end return false end +---Check if a header is present in the request +---@param headers table The headers to check +---@param header string The header name to check +---@param value string|nil The value to check for or nil if only the header name should be checked +---@return boolean M.contains_header = function(headers, header, value) - for k, v in pairs(headers) do - if k == header and v == value then - return true + header = header:lower() + value = value and value:lower() or nil + if value == nil then + for k, _ in pairs(headers) do + if k:lower() == header then + return true + end + end + else + for k, v in pairs(headers) do + if k:lower() == header and v:lower() == value then + return true + end end end return false end +---Get the value of a header from the request +---@param headers table The headers to check +---@param header string The header name to check +---@param dont_ignore_case boolean|nil If true, the header name will be case sensitive +---@return string|nil +M.get_header_value = function(headers, header, dont_ignore_case) + header = dont_ignore_case and header or header:lower() + for k, v in pairs(headers) do + if k == header then + return v + end + end + return nil +end + +---Get the name of a header from the request +---@param headers table The headers to check +---@param header string The header name to check +---@param dont_ignore_case boolean|nil If true, the header name will be case sensitive +---@return string|nil +M.get_header_name = function(headers, header, dont_ignore_case) + header = dont_ignore_case and header or header:lower() + for k, _ in pairs(headers) do + if k:lower() == header then + return k + end + end + return nil +end + +---Get a header from the request +---@param headers table The headers to check +---@param header string The header name to check +---@param value string|nil The value to check for or nil if only the header name should be checked +---@param dont_ignore_case boolean|nil If true, the header name will be case sensitive +---@return (string|nil), (string|nil) The header name and value or nil if not found +M.get_header = function(headers, header, value, dont_ignore_case) + header = dont_ignore_case and header or header:lower() + value = value and (dont_ignore_case and value or value:lower()) or nil + if dont_ignore_case then + if value == nil then + for k, _ in pairs(headers) do + if k == header then + return k, headers[k] + end + end + else + for k, v in pairs(headers) do + if k == header and v == value then + return k, v + end + end + end + else + if value == nil then + for k, _ in pairs(headers) do + if k:lower() == header then + return k, headers[k] + end + end + else + for k, v in pairs(headers) do + if k:lower() == header and v:lower() == value then + return k, v + end + end + end + end + return nil, nil +end + return M diff --git a/lua/kulala/scripts/init.lua b/lua/kulala/scripts/init.lua deleted file mode 100644 index 7e20732..0000000 --- a/lua/kulala/scripts/init.lua +++ /dev/null @@ -1,6 +0,0 @@ -local Javascript = require("kulala.scripts.javascript") -local M = { - javascript = Javascript, -} - -return M diff --git a/lua/kulala/scripts/javascript.lua b/lua/kulala/scripts/javascript.lua deleted file mode 100644 index 3c61b81..0000000 --- a/lua/kulala/scripts/javascript.lua +++ /dev/null @@ -1,126 +0,0 @@ -local FS = require("kulala.utils.fs") -local GLOBALS = require("kulala.globals") -local CONFIG = require("kulala.config") -local M = {} - -local NODE_EXISTS = vim.fn.executable("node") == 1 -local SCRIPTS_DIR = FS.get_scripts_dir() -local REQUEST_SCRIPTS_DIR = FS.get_request_scripts_dir() -local BASE_FILE_PRE = FS.join_paths(SCRIPTS_DIR, "pre_request_base.js") -local BASE_FILE_POST = FS.join_paths(SCRIPTS_DIR, "post_request_base.js") - -local generate_one = function(script_type, is_external_file, script_data) - local lines - local base_file_path = script_type == "pre_request" and BASE_FILE_PRE or BASE_FILE_POST - local base_file = FS.read_file(base_file_path) - if base_file == nil then - return nil, nil - end - local script_cwd - if is_external_file then - -- if script_data starts with ./ or ../, it is a relative path - if string.match(script_data, "^%./") or string.match(script_data, "^%../") then - script_data = FS.get_current_buffer_dir() .. FS.ps .. script_data:gsub("^%./", "") - end - script_cwd = FS.get_dir_by_filepath(script_data) - lines = FS.read_file_lines(script_data) - else - script_cwd = FS.get_current_buffer_dir() - lines = script_data - end - for _, line in ipairs(lines) do - base_file = base_file .. "\n" .. line - end - if #lines == 0 then - return nil, nil - end - local uuid = FS.get_uuid() - local script_path = REQUEST_SCRIPTS_DIR .. FS.ps .. uuid .. ".js" - FS.write_file(script_path, base_file, false) - return script_path, script_cwd -end - ----@class Scripts ----@field path string -- path to script ----@field cwd string -- current working directory - ----@class ScriptData table -- data for script ----@field inline string
-- inline scripts ----@field files string
-- paths to script files - ----@param script_type string -- "pre_request" or "post_request" ----@param scripts_data ScriptData ----@return Scripts
-- paths to scripts -local generate_all = function(script_type, scripts_data) - local scripts = {} - local script_path, script_cwd = generate_one(script_type, false, scripts_data.inline) - if script_path ~= nil and script_cwd ~= nil then - table.insert(scripts, { path = script_path, cwd = script_cwd }) - end - for _, script_data in ipairs(scripts_data.files) do - script_path, script_cwd = generate_one(script_type, true, script_data) - if script_path ~= nil and script_cwd ~= nil then - table.insert(scripts, { path = script_path, cwd = script_cwd }) - end - end - return scripts -end - ----@param type string -- "pre_request" or "post_request" ----@param data ScriptData -M.run = function(type, data) - if not NODE_EXISTS then - return - end - local scripts = generate_all(type, data) - if scripts == nil then - return - end - - for _, script in ipairs(scripts) do - local output = vim - .system({ - "node", - script.path, - }, { - cwd = script.cwd, - env = { - NODE_PATH = script.cwd .. FS.ps .. "node_modules", - }, - }) - :wait() - if output ~= nil then - local script_pre_output_file = GLOBALS.SCRIPT_PRE_OUTPUT_FILE - FS.delete_file(script_pre_output_file) - local script_post_output_file = GLOBALS.SCRIPT_POST_OUTPUT_FILE - FS.delete_file(script_post_output_file) - - if output.stderr ~= nil and not string.match(output.stderr, "^%s*$") then - if not CONFIG.get().disable_script_print_output then - vim.print(output.stderr) - end - if type == "pre_request" then - FS.write_file(script_pre_output_file, output.stderr) - elseif type == "post_request" then - FS.write_file(script_post_output_file, output.stderr) - end - end - if output.stdout ~= nil and not string.match(output.stdout, "^%s*$") then - if not CONFIG.get().disable_script_print_output then - vim.print(output.stdout) - end - if type == "pre_request" then - if not FS.write_file(script_pre_output_file, output.stdout) then - vim.print("write " .. script_pre_output_file .. " fail") - end - elseif type == "post_request" then - if not FS.write_file(script_post_output_file, output.stdout) then - vim.print("write " .. script_post_output_file .. " fail") - end - end - end - end - end -end - -return M diff --git a/lua/kulala/scripts/post_request_base.js b/lua/kulala/scripts/post_request_base.js deleted file mode 100644 index 430da1d..0000000 --- a/lua/kulala/scripts/post_request_base.js +++ /dev/null @@ -1,63 +0,0 @@ -const __fs = require('fs'); -const __path = require('path'); -const __GLOBAL_VARIABLES_FILEPATH = __path.join(__dirname, '..', 'global_variables.json'); -const __RESPONSE_HEADERS_FILEPATH = __path.join(__dirname, '..', '..', 'headers.txt'); -const __RESPONSE_BODY_FILEPATH = __path.join(__dirname, '..', '..', 'body.txt'); - -const client = {}; -client.global = {}; -client.global.set = function (key, value) { - let json = {}; - if (__fs.existsSync(__GLOBAL_VARIABLES_FILEPATH)) { - json = JSON.parse(__fs.readFileSync(__GLOBAL_VARIABLES_FILEPATH)); - } - json[key] = value; - __fs.writeFileSync(__GLOBAL_VARIABLES_FILEPATH, JSON.stringify(json)); -}; -client.global.get = function (key) { - let json = {}; - if (__fs.existsSync(__GLOBAL_VARIABLES_FILEPATH)) { - json = JSON.parse(__fs.readFileSync(__GLOBAL_VARIABLES_FILEPATH)); - } - return json[key]; -}; - -const response = {}; -response.body = null; -response.headers = { - headers: {}, - valueOf: function (headerName) { - return this.headers[headerName]; - }, - valuesOf: function (headerName) { - const values = []; - for (const key in this.headers) { - if (key.toLowerCase() === headerName.toLowerCase()) { - values.push(this[key]); - } - } - return values; - }, -}; -response.status = null; -response.contentType = { - mimeType: null, - charset: null, -}; - -if (__fs.existsSync(__RESPONSE_HEADERS_FILEPATH)) { - const headers = __fs.readFileSync(__RESPONSE_HEADERS_FILEPATH, 'utf8'); - headers.split('\n').forEach(header => { - const [key, _] = header.split(':'); - response.headers.headers[key] = header.split(':').slice(1).join(':').trim(); - }); -} - -if (__fs.existsSync(__RESPONSE_BODY_FILEPATH)) { - response.body = __fs.readFileSync(__RESPONSE_BODY_FILEPATH, 'utf8'); - try { - response.body = JSON.parse(response.body); - } catch (e) { - // do nothing - } -} diff --git a/lua/kulala/scripts/pre_request_base.js b/lua/kulala/scripts/pre_request_base.js deleted file mode 100644 index 5b7c5bc..0000000 --- a/lua/kulala/scripts/pre_request_base.js +++ /dev/null @@ -1,39 +0,0 @@ -const __fs = require('fs'); -const __path = require('path'); -const __REQUEST_VARIABLES_FILEPATH = __path.join(__dirname, 'request_variables.json'); -const __GLOBAL_VARIABLES_FILEPATH = __path.join(__dirname, '..', 'global_variables.json'); - -const request = {}; -request.variables = {}; -request.variables.set = function (key, value) { - let json = {}; - if (__fs.existsSync(__REQUEST_VARIABLES_FILEPATH)) { - json = JSON.parse(__fs.readFileSync(__REQUEST_VARIABLES_FILEPATH)); - } - json[key] = value; - __fs.writeFileSync(__REQUEST_VARIABLES_FILEPATH, JSON.stringify(json)); -} -request.variables.get = function (key) { - let json = {}; - if (__fs.existsSync(__REQUEST_VARIABLES_FILEPATH)) { - json = JSON.parse(__fs.readFileSync(__REQUEST_VARIABLES_FILEPATH)); - } - return json[key]; -}; -const client = {}; -client.global = {}; -client.global.set = function (key, value) { - let json = {}; - if (__fs.existsSync(__GLOBAL_VARIABLES_FILEPATH)) { - json = JSON.parse(__fs.readFileSync(__GLOBAL_VARIABLES_FILEPATH)); - } - json[key] = value; - __fs.writeFileSync(__GLOBAL_VARIABLES_FILEPATH, JSON.stringify(json)); -}; -client.global.get = function (key) { - let json = {}; - if (__fs.existsSync(__GLOBAL_VARIABLES_FILEPATH)) { - json = JSON.parse(__fs.readFileSync(__GLOBAL_VARIABLES_FILEPATH)); - } - return json[key]; -}; diff --git a/lua/kulala/ui/init.lua b/lua/kulala/ui/init.lua index a46be52..42924b5 100644 --- a/lua/kulala/ui/init.lua +++ b/lua/kulala/ui/init.lua @@ -10,7 +10,6 @@ local FS = require("kulala.utils.fs") local DB = require("kulala.db") local INT_PROCESSING = require("kulala.internal_processing") local FORMATTER = require("kulala.formatter") -local TS = require("kulala.parser.treesitter") local Logger = require("kulala.logger") local AsciiUtils = require("kulala.utils.ascii") local Inspect = require("kulala.parser.inspect") @@ -30,6 +29,26 @@ local get_win = function() return nil end +local open_float = function() + local bufnr = vim.api.nvim_create_buf(false, false) + vim.api.nvim_buf_set_name(bufnr, "kulala://ui") + + local width = vim.api.nvim_win_get_width(0) - 10 + local height = vim.api.nvim_win_get_height(0) - 10 + + local winnr = vim.api.nvim_open_win(bufnr, true, { + title = "Kulala", + title_pos = "center", + relative = "editor", + border = "single", + width = width, + height = height, + row = math.floor(((vim.o.lines - height) / 2) - 1), + col = math.floor((vim.o.columns - width) / 2), + style = "minimal", + }) +end + local get_buffer = function() -- Iterate through all buffers for _, buf in ipairs(vim.api.nvim_list_bufs()) do @@ -52,9 +71,7 @@ local replace_buffer = function() local old_bufnr = get_buffer() local new_bufnr = vim.api.nvim_create_buf(true, false) - vim.api.nvim_set_option_value("buftype", "nofile", { - buf = new_bufnr, - }) + vim.bo[new_bufnr].buftype = "nofile" if old_bufnr ~= nil then for _, win in ipairs(vim.fn.win_findbuf(old_bufnr)) do @@ -73,21 +90,29 @@ local replace_buffer = function() return new_bufnr end -local open_buffer = function() +local open_split = function() local prev_win = vim.api.nvim_get_current_win() local sd = CONFIG.get().split_direction == "vertical" and "vsplit" or "split" - vim.cmd(sd .. " " .. GLOBALS.UI_ID) + vim.cmd("keepalt " .. sd .. " " .. GLOBALS.UI_ID) if CONFIG.get().winbar then WINBAR.create_winbar(get_win()) end vim.api.nvim_set_current_win(prev_win) end +local open_buffer = function() + if CONFIG.get().display_mode == "split" then + open_split() + else + open_float() + end +end + local close_buffer = function() vim.cmd("bdelete! " .. GLOBALS.UI_ID) end -local function buffer_exists() +local buffer_exists = function() return get_buffer() ~= nil end @@ -99,8 +124,9 @@ vim.api.nvim_create_autocmd("WinClosed", { group = augroup, callback = function(args) -- if the window path is the same as the GLOBALS.UI_ID and the buffer exists - if args.buf == get_buffer() then - vim.api.nvim_buf_delete(get_buffer(), { force = true }) + local buf = get_buffer() + if buf and args.buf == buf then + vim.api.nvim_buf_delete(buf, { force = true }) end end, }) @@ -108,13 +134,14 @@ vim.api.nvim_create_autocmd("WinClosed", { local function set_buffer_contents(contents, ft) if buffer_exists() then local buf = replace_buffer() - local lines = vim.split(contents, "\n") - vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + -- setup filetype first so that treesitter foldexpr can calculate fold level per lines if ft ~= nil then vim.bo[buf].filetype = ft else vim.bo[buf].filetype = "text" end + local lines = vim.split(contents, "\n") + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) end end @@ -443,7 +470,6 @@ end M.scratchpad = function() vim.cmd("e " .. GLOBALS.SCRATCHPAD_ID) - vim.cmd("setlocal buftype=nofile") vim.cmd("setlocal filetype=http") vim.api.nvim_buf_set_lines(0, 0, -1, false, CONFIG.get().scratchpad_default_contents) end diff --git a/lua/kulala/ui/selector.lua b/lua/kulala/ui/selector.lua index be05e45..5767948 100644 --- a/lua/kulala/ui/selector.lua +++ b/lua/kulala/ui/selector.lua @@ -1,5 +1,7 @@ local DB = require("kulala.db") local FS = require("kulala.utils.fs") +local Parser = require("kulala.parser") +local ParserUtils = require("kulala.parser.utils") local M = {} @@ -11,7 +13,9 @@ function M.select_env() local envs = {} for key, _ in pairs(http_client_env) do - table.insert(envs, key) + if key ~= "$schema" and key ~= "$shared" then + table.insert(envs, key) + end end local opts = { @@ -26,15 +30,30 @@ function M.select_env() end M.search = function() - local files = FS.find_all_http_files() - if #files == 0 then + local _, requests = Parser.get_document() + + if requests == nil then + return + end + + local line_starts = {} + local names = {} + + for _, request in ipairs(requests) do + local request_name = ParserUtils.get_meta_tag(request, "name") + if request_name ~= nil then + table.insert(names, request_name) + line_starts[request_name] = request.start_line + end + end + if #names == 0 then return end - vim.ui.select(files, { prompt = "Search" }, function(result) + vim.ui.select(names, { prompt = "Search" }, function(result) if not result then return end - vim.cmd("e " .. result) + vim.cmd("normal! " .. line_starts[result] + 1 .. "G") end) end diff --git a/lua/kulala/ui/winbar.lua b/lua/kulala/ui/winbar.lua index fe76610..635352b 100644 --- a/lua/kulala/ui/winbar.lua +++ b/lua/kulala/ui/winbar.lua @@ -87,10 +87,11 @@ M.create_winbar = function(win_id) local default_view = CONFIG.get().default_view M.winbar_sethl() M.toggle_winbar_tab(win_id, default_view) - UICallbacks.add("on_replace_buffer", function(_, new_buffer) - M.winbar_set_key_mapping(new_buffer) - end) end end +UICallbacks.add("on_replace_buffer", function(_, new_buffer) + M.winbar_set_key_mapping(new_buffer) +end) + return M diff --git a/lua/kulala/utils/fs.lua b/lua/kulala/utils/fs.lua index 9c7ad49..5f69a43 100644 --- a/lua/kulala/utils/fs.lua +++ b/lua/kulala/utils/fs.lua @@ -1,15 +1,81 @@ +local Logger = require("kulala.logger") local M = {} ---- Path separator -M.ps = package.config:sub(1, 1) +---Get the OS +---@return "windows" | "mac" | "unix" | "unknown" +M.get_os = function() + if vim.fn.has("unix") == 1 then + return "unix" + end + if vim.fn.has("mac") == 1 then + return "mac" + end + if vim.fn.has("win32") == 1 or vim.fn.has("win64") then + return "windows" + end + return "unknown" +end + +---The OS +---@type "windows" | "mac" | "unix" | "unknown" +M.os = M.get_os() + +---Get the path separator for the current OS +---@return "\\" | "/" +M.get_path_separator = function() + if M.os == "windows" then + return "\\" + end + return "/" +end + +---Path separator +---@type "\\" | "/" +M.ps = M.get_path_separator() ---Join paths -- similar to os.path.join in python ---@vararg string ---@return string M.join_paths = function(...) + if M.os == "windows" then + for _, v in ipairs({ ... }) do + -- if the path contains at least one forward slash, + -- then it needs to be converted to backslashes + if v:match("/") then + local parts = {} + for _, p in ipairs({ ... }) do + p = p:gsub("/", "\\") + table.insert(parts, p) + end + return table.concat(parts, M.ps) + end + end + return table.concat({ ... }, M.ps) + end return table.concat({ ... }, M.ps) end +---Returns true if the path is absolute, false otherwise +M.is_absolute_path = function(path) + if path:match("^/") or path:match("^%a:\\") then + return true + end + return false +end + +---Either returns the absolute path if the path is already absolute or +---joins the path with the current buffer directory +M.get_file_path = function(path) + if M.is_absolute_path(path) then + return path + end + local buffer_dir = vim.fn.expand("%:p:h") + if path:sub(1, 2) == "./" or path:sub(1, 2) == ".\\" then + path = path:sub(3) + end + return M.join_paths(buffer_dir, path) +end + -- This is mainly used for determining if the current buffer is a non-http file -- and therefore maybe we need to parse a fenced code block M.is_non_http_file = function() @@ -72,13 +138,13 @@ end -- Writes string to file --- @param filename string --- @param content string ---- @param append boolean +--- @param append boolean|nil --- @usage fs.write_file('Makefile', 'all: \n\t@echo "Hello World"') --- @usage fs.write_file('Makefile', 'all: \n\t@echo "Hello World"', true) --- @return boolean --- @usage local p = fs.write_file('Makefile', 'all: \n\t@echo "Hello World"') M.write_file = function(filename, content, append) - local f = nil + local f if append then f = io.open(filename, "a") else @@ -113,6 +179,14 @@ M.file_exists = function(filename) return vim.fn.filereadable(filename) == 1 end +M.copy_dir = function(source, destination) + if M.os == "unix" or M.os == "mac" then + vim.system({ "cp", "-r", source .. M.ps .. ".", destination }):wait() + elseif M.os == "windows" then + vim.system({ "xcopy", "/H", "/E", "/I", source .. M.ps .. "*", destination }):wait() + end +end + M.ensure_dir_exists = function(dir) if vim.fn.isdirectory(dir) == 0 then vim.fn.mkdir(dir, "p") @@ -123,13 +197,21 @@ end --- @return string --- @usage local p = fs.get_plugin_tmp_dir() M.get_plugin_tmp_dir = function() - local dir = M.join_paths(vim.fn.stdpath("data"), "tmp", "kulala") + local cache = vim.fn.stdpath("cache") + ---@cast cache string + local dir = M.join_paths(cache, "kulala") M.ensure_dir_exists(dir) return dir end M.get_scripts_dir = function() - local dir = M.join_paths(M.get_plugin_root_dir(), "scripts") + local dir = M.join_paths(M.get_plugin_root_dir(), "parser", "scripts") + return dir +end + +M.get_tmp_scripts_build_dir = function() + local dir = M.join_paths(M.get_plugin_tmp_dir(), "scripts", "build") + M.ensure_dir_exists(dir) return dir end @@ -145,7 +227,12 @@ M.get_request_scripts_dir = function() return dir end +---Delete all files in a directory +---@param dir string +---@usage fs.delete_files_in_directory('tmp') +---@return string[] deleted_files M.delete_files_in_directory = function(dir) + local deleted_files = {} -- Open the directory for scanning local scandir = vim.loop.fs_scandir(dir) if scandir then @@ -155,18 +242,21 @@ M.delete_files_in_directory = function(dir) if not name then break end - -- Only delete files, not directories - if type == "file" then - local filepath = dir .. M.ps .. name + -- Only delete files, not directories except .gitingore + if type == "file" and name:match(".gitignore$") == nil then + local filepath = M.join_paths(dir, name) local success, err = vim.loop.fs_unlink(filepath) if not success then print("Error deleting file:", filepath, err) + else + table.insert(deleted_files, filepath) end end end else print("Error opening directory:", dir) end + return deleted_files end M.delete_request_scripts_files = function() @@ -202,6 +292,10 @@ M.command_exists = function(cmd) return vim.fn.executable(cmd) == 1 end +M.command_path = function(cmd) + return vim.fn.exepath(cmd) +end + M.get_plugin_root_dir = function() local source = debug.getinfo(1).source local dir_path = source:match("@(.*/)") or source:match("@(.*\\)") @@ -218,12 +312,14 @@ M.get_plugin_path = function(paths) return M.get_plugin_root_dir() .. M.ps .. table.concat(paths, M.ps) end --- Read a file ---- @param filename string ---- @return string|nil ---- @usage local p = fs.read_file('Makefile') -M.read_file = function(filename) - local f = io.open(filename, "r") +---Read a file +---@param filename string +---@param is_binary boolean|nil +---@return string|nil +---@usage local p = fs.read_file('Makefile') +M.read_file = function(filename, is_binary) + local read_mode = is_binary and "rb" or "r" + local f = io.open(filename, read_mode) if f == nil then return nil end @@ -232,6 +328,28 @@ M.read_file = function(filename) return content end +M.get_temp_file = function(content) + local tmp_file = vim.fn.tempname() + local f = io.open(tmp_file, "w") + if f == nil then + return nil + end + f:write(content) + f:close() + return tmp_file +end + +M.get_binary_temp_file = function(content) + local tmp_file = vim.fn.tempname() + local f = io.open(tmp_file, "wb") + if f == nil then + return nil + end + f:write(content) + f:close() + return tmp_file +end + ---Read file lines ---@param filename string ---@return string[] @@ -248,4 +366,17 @@ M.read_file_lines = function(filename) return lines end +---Clears all cached files +M.clear_cached_files = function() + local tmp_dir = M.get_plugin_tmp_dir() + local deleted_files = M.delete_files_in_directory(tmp_dir) + local string_list = vim.fn.join( + vim.tbl_map(function(file) + return "- " .. file + end, deleted_files), + "\n" + ) + Logger.info("Deleted files:\n" .. string_list) +end + return M diff --git a/lua/telescope/_extensions/kulala.lua b/lua/telescope/_extensions/kulala.lua index a8a0759..5439113 100644 --- a/lua/telescope/_extensions/kulala.lua +++ b/lua/telescope/_extensions/kulala.lua @@ -5,7 +5,8 @@ if not has_telescope then end local DB = require("kulala.db") -local FS = require("kulala.utils.fs") +local Parser = require("kulala.parser") +local ParserUtils = require("kulala.parser.utils") local action_state = require("telescope.actions.state") local actions = require("telescope.actions") @@ -15,15 +16,28 @@ local previewers = require("telescope.previewers") local config = require("telescope.config").values local function kulala_search(_) - -- a list of all the .http/.rest files in the current directory - -- and its subdirectories - local files = FS.find_all_http_files() + local _, requests = Parser.get_document() + + if requests == nil then + return + end + + local data = {} + local names = {} + + for _, request in ipairs(requests) do + local request_name = ParserUtils.get_meta_tag(request, "name") + if request_name ~= nil then + table.insert(names, request_name) + data[request_name] = request + end + end pickers .new({}, { prompt_title = "Search", finder = finders.new_table({ - results = files, + results = names, }), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() @@ -32,12 +46,34 @@ local function kulala_search(_) if selection == nil then return end - vim.cmd("e " .. selection.value) + local request = data[selection.value] + vim.cmd("normal! " .. request.start_line + 1 .. "G") end) return true end, - previewer = previewers.vim_buffer_cat.new({ + previewer = previewers.new_buffer_previewer({ title = "Preview", + define_preview = function(self, entry) + local request = data[entry.value] + if request == nil then + return + end + local lines = {} + local http_version = request.http_version and "HTTP/" .. request.http_version or "HTTP/1.1" + table.insert(lines, request.method .. " " .. request.url .. " " .. http_version) + for key, value in pairs(request.headers) do + table.insert(lines, key .. ": " .. value) + end + if request.body_display ~= nil then + table.insert(lines, "") + local body_as_table = vim.split(request.body_display, "\r?\n") + for _, line in ipairs(body_as_table) do + table.insert(lines, line) + end + end + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines) + vim.bo[self.state.bufnr].filetype = "http" + end, }), sorter = config.generic_sorter({}), }) @@ -52,7 +88,9 @@ local function kulala_env_select(_) local envs = {} for key, _ in pairs(http_client_env) do - table.insert(envs, key) + if key ~= "$schema" and key ~= "$shared" then + table.insert(envs, key) + end end pickers diff --git a/schemas/http-client.env.schema.json b/schemas/http-client.env.schema.json new file mode 100644 index 0000000..8f62ce3 --- /dev/null +++ b/schemas/http-client.env.schema.json @@ -0,0 +1,43 @@ +{ + "$id": "https://raw.githubusercontent.com/mistweaverco/kulala.nvim/main/schemas/http-client.env.schema.json", + "title": "HTTP Client Environment Variables", + "description": "The environment variables required for the HTTP client", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "$shared": { + "type": "object", + "properties": { + "$default_headers": { + "type": "object", + "patternProperties": { + "^.*$": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "patternProperties": { + "^([A-Za-z0-9_]+)$": { + "type": ["string", "number"] + } + }, + "additionalProperties": false + } + }, + "patternProperties": { + "^.*$": { + "type": "object", + "patternProperties": { + "^([A-Za-z0-9_]+)$": { + "type": ["string", "number"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schemas/http-client.private.env.schema.json b/schemas/http-client.private.env.schema.json new file mode 100644 index 0000000..8473415 --- /dev/null +++ b/schemas/http-client.private.env.schema.json @@ -0,0 +1,43 @@ +{ + "$id": "https://raw.githubusercontent.com/mistweaverco/kulala.nvim/main/schemas/http-client.private.env.schema.json", + "title": "HTTP Client Private Environment Variables", + "description": "The private environment variables required for the HTTP client", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "$shared": { + "type": "object", + "properties": { + "$default_headers": { + "type": "object", + "patternProperties": { + "^.*$": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "patternProperties": { + "^([A-Za-z0-9_]+)$": { + "type": ["string", "number"] + } + }, + "additionalProperties": false + } + }, + "patternProperties": { + "^.*$": { + "type": "object", + "patternProperties": { + "^([A-Za-z0-9_]+)$": { + "type": ["string", "number"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/scripts/install-ci-test-requirements.ps1 b/scripts/install-ci-test-requirements.ps1 new file mode 100644 index 0000000..82f6783 --- /dev/null +++ b/scripts/install-ci-test-requirements.ps1 @@ -0,0 +1,44 @@ +$Env:KULALA_ROOT_DIR = (Get-Location).Path + +if ($Env:GH_CACHE_HIT -eq $null) { + mkdir .tests +} + +cd .tests + +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +if ($Env:GH_CACHE_HIT -eq $null) { + Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression + scoop install main/git + scoop install main/neovim@0.10.2 +} else { + $Env:PATH = "$Env:USERPROFILE\scoop\shims;$Env:USERPROFILE\scoop\apps\git\current\cmd;$Env:USERPROFILE\scoop\apps\neovim\current\bin;$Env:PATH" +} + +if ($Env:GH_CACHE_HIT -eq $null) { + Invoke-RestMethod -Uri https://github.com/mistweaverco/luajit-for-win64/archive/refs/tags/v0.0.2.zip -outfile luajit.zip + 7z x luajit.zip + RM luajit.zip + cd luajit-for-win64-0.0.2 + .\luajit-for-win64.cmd +} else { + cd luajit-for-win64-0.0.2 +} + +$Env:KULALA_LUA_DIR = (Get-Location).Path + +$Env:PATH = "$Env:KULALA_LUA_DIR\tools\cmd;$Env:KULALA_LUA_DIR\tools\PortableGit\mingw64\bin;$Env:KULALA_LUA_DIR\tools\PortableGit\usr\bin;$Env:KULALA_LUA_DIR\tools\mingw\bin;$Env:KULALA_LUA_DIR\lib;$Env:KULALA_LUA_DIR\bin;$Env:APPDATA\LJ4W\LuaRocks\bin;$Env:PATH" +$Env:LUA_PATH = "$Env:KULALA_LUA_DIR\lua\?.lua;$Env:KULALA_LUA_DIR\lua\?\init.lua;$Env:APPDATA\luarocks\share\lua\5.1\?.lua;$Env:APPDATA\luarocks\share\lua\5.1\?\init.lua;$Env:LUA_PATH" +$Env:LUA_CPATH = "$Env:APPDATA\luarocks;$Env:APPDATA\luarocks\lib\lua\5.1\?.dll;$Env:LUA_CPATH" + +if ($Env:GH_CACHE_HIT -eq $null) { + luarocks install --lua-version 5.1 busted +} + +# Persist the Environment Variables +"PATH=$Env:Path" >> $Env:GITHUB_ENV +"LUA_PATH=$Env:LUA_PATH" >> $Env:GITHUB_ENV +"LUA_CPATH=$Env:LUA_CPATH" >> $Env:GITHUB_ENV + +cd $Env:KULALA_ROOT_DIR diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 0000000..6618eba --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +check_code() { + if ! command -v stylua &> /dev/null; then + echo "stylua is not installed" + exit 1 + fi + stylua --version + if [[ -n $1 ]]; then + stylua --check "$1" + else + stylua --check . + fi +} + +check_docs() { + if ! command -v vale &> /dev/null; then + echo "stylua is not installed" + exit 1 + fi + cd docs || exit 1 + if [[ -n $1 ]]; then + vale "$1" + else + vale . + fi +} + +main() { + local action="$1" + shift + local args=$* + case $action in + "check-code") + check_code "$args" + ;; + "check-docs") + check_docs "$args" + ;; + *) + echo "Invalid action" + exit 1 + ;; + esac + +} +main "$@" diff --git a/scripts/tag.sh b/scripts/tag.sh new file mode 100755 index 0000000..8efbfb7 --- /dev/null +++ b/scripts/tag.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Creates a tag based on the version found in lua/kulala/globals.lua +# and pushes it to the remote repository. + +set -euo pipefail + +get_tag() { + local file="./lua/kulala/globals/init.lua" + local VERSION_REGEX="VERSION = \"([0-9]+\.[0-9]+\.[0-9]+)\"" + local version + version=$(grep -oP "$VERSION_REGEX" "$file" | cut -d'"' -f2) + echo "v$version" +} + +check_on_main_branch() { + local branch + branch=$(git branch --show-current) + if [ "$branch" != "main" ]; then + echo "You must be on the main branch to create a tag." + exit 1 + fi +} + +check_if_clean() { + if ! git diff --quiet; then + echo "You have uncommitted changes. Please commit or stash them before creating a tag." + exit 1 + fi +} + +check_on_main_branch +check_if_clean + +tag=$(get_tag) + +git tag "$tag" && git push origin "$tag" diff --git a/scripts/tests.ps1 b/scripts/tests.ps1 new file mode 100644 index 0000000..d4774d8 --- /dev/null +++ b/scripts/tests.ps1 @@ -0,0 +1,2 @@ +nvim --version +nvim -l tests/minit.lua tests diff --git a/scripts/tests.sh b/scripts/tests.sh index b265a91..dc77216 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash +if ! command -v nvim &> /dev/null; then + echo "nvim is not installed" + exit 1 +fi + run() { nvim --version if [[ -n $1 ]]; then diff --git a/tests/_dockerfiles/ubuntu/Dockerfile b/tests/_dockerfiles/ubuntu/Dockerfile index 5ec7f7d..d644c55 100644 --- a/tests/_dockerfiles/ubuntu/Dockerfile +++ b/tests/_dockerfiles/ubuntu/Dockerfile @@ -1,6 +1,15 @@ FROM ubuntu:latest -RUN apt-get update && apt-get upgrade -y && apt-get install -y curl git gcc lua5.1 luarocks -RUN mkdir -p _neovim && curl -sL https://github.com/neovim/neovim/releases/download/v0.10.1/nvim-linux64.tar.gz | tar -xz -C _neovim && mv _neovim/nvim-linux64 /usr/local/nvim && rm -rf _neovim +RUN apt-get update && apt-get upgrade -y && apt-get install -y curl git gcc lua5.1 luarocks unzip xclip +RUN mkdir -p _neovim && curl -sL https://github.com/neovim/neovim/releases/download/v0.10.2/nvim-linux64.tar.gz | tar -xz -C _neovim && mv _neovim/nvim-linux64 /usr/local/nvim && rm -rf _neovim +RUN mkdir -p _stylua && curl -sL https://github.com/JohnnyMorganz/StyLua/releases/download/v0.20.0/stylua-linux-x86_64.zip | funzip > _stylua/stylua && chmod +x _stylua/stylua && mv _stylua/stylua /usr/local/bin/stylua && rm -rf _stylua +RUN mkdir -p _vale && curl -sL https://github.com/errata-ai/vale/releases/download/v2.28.0/vale_2.28.0_Linux_64-bit.tar.gz | tar -xz -C _vale && mv _vale/vale /usr/local/bin/vale && rm -rf _vale RUN luarocks install busted RUN ln -s /usr/local/nvim/bin/nvim /usr/bin/nvim +RUN ln -s /usr/local/bin/stylua /usr/bin/stylua +RUN ln -s /usr/local/bin/vale /usr/bin/vale + +# for xclip and neovim to work, see :h clipboard in neovim +ENV DISPLAY=:0 + +WORKDIR /app diff --git a/tests/_dockerfiles/ubuntu/README.md b/tests/_dockerfiles/ubuntu/README.md new file mode 100644 index 0000000..de1e839 --- /dev/null +++ b/tests/_dockerfiles/ubuntu/README.md @@ -0,0 +1,35 @@ +# Kulala Neovim Linux Testrunner Docker Image + +This is a docker image for running tests in a Linux environment. + +It is based on the [ubuntu](https://hub.docker.com/_/ubuntu) image. + +## Features + +- `neovim` v0.10.2 +- `stylua` v0.20.0 +- `vale` v2.28.0 +- `curl` +- `git` +- `gcc` +- `lua5.1` +- `luarocks` +- `unzip` +- `xclip` (for neovim clipboard support) +- `luarocks busted` (for running tests) + +## Building the image + +```bash +make docker-build OS=linux +``` + +## Pushing the image + +> [!WARNING] +> You need to have write access to the docker registry at +> `ghcr.io/mistweaverco/kulala-nvim-linux-testrunner`. + +```bash +make docker-push OS=windows +``` diff --git a/tests/_dockerfiles/windows/Dockerfile b/tests/_dockerfiles/windows/Dockerfile new file mode 100644 index 0000000..0f027c9 --- /dev/null +++ b/tests/_dockerfiles/windows/Dockerfile @@ -0,0 +1,46 @@ +# FROM mcr.microsoft.com/windows/nanoserver:20H2-amd64 +FROM mcr.microsoft.com/powershell:lts-windowsservercore-1809 + +SHELL ["pwsh", "-Command"] + +USER ContainerAdministrator + +WORKDIR "C:\\kulala.nvim" + +RUN Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +RUN Invoke-RestMethod -Uri https://get.scoop.sh -outfile 'install.ps1' +RUN .\install.ps1 -RunAsAdmin + +# required before add extras +RUN scoop install main/git + +# add for extras suggested by neovim +RUN scoop bucket add extras + +RUN scoop install extras/vcredist2022 +RUN scoop install main/neovim@0.10.2 + +WORKDIR "C:\\luajjt" + +RUN Invoke-RestMethod -Uri https://github.com/mistweaverco/luajit-for-win64/archive/refs/tags/v0.0.2.zip -outfile luajit.zip +RUN 7z x luajit.zip + +WORKDIR "C:\\luajjt\luajit-for-win64-0.0.2" + +RUN .\luajit-for-win64.cmd + +RUN setx /M KULALA_LUA_DIR \"C:\luajjt\luajit-for-win64-0.0.2\" + +RUN setx /M PATH \"$Env:KULALA_LUA_DIR\tools\cmd;$Env:KULALA_LUA_DIR\tools\PortableGit\mingw64\bin;$Env:KULALA_LUA_DIR\tools\PortableGit\usr\bin;$Env:KULALA_LUA_DIR\tools\mingw\bin;$Env:KULALA_LUA_DIR\lib;$Env:KULALA_LUA_DIR\bin;$Env:APPDATA\LJ4W\LuaRocks\bin;$Env:path\" + +RUN setx /M LUA_PATH \"$Env:KULALA_LUA_DIR\lua\?.lua;$Env:KULALA_LUA_DIR\lua\?\init.lua;$Env:APPDATA\luarocks\share\lua\5.1\?.lua;$Env:APPDATA\luarocks\share\lua\5.1\?\init.lua;$Env:LUA_PATH\" +RUN setx /M LUA_CPATH \"$Env:APPDATA\luarocks;$Env:APPDATA\luarocks\lib\lua\5.1\?.dll;$Env:LUA_CPATH\" + +RUN luarocks install --lua-version 5.1 busted + +WORKDIR "C:\\kulala.nvim" + +RUN git config --global safe.directory '*' +RUN git config --global core.autocrlf true + +ENTRYPOINT ["pwsh"] diff --git a/tests/_dockerfiles/windows/README.md b/tests/_dockerfiles/windows/README.md new file mode 100644 index 0000000..6462c47 --- /dev/null +++ b/tests/_dockerfiles/windows/README.md @@ -0,0 +1,40 @@ +# Kulala Neovim Windows Testrunner Docker Image + +This is a docker image for running tests in a Windows environment. + +It is based on the [microsoft/windows-nanoserver](https://hub.docker.com/r/microsoft/windows-nanoserver) image. + +## Features + +- `neovim` v0.10.2 +- `stylua` v0.20.0 +- `vale` v2.28.0 +- `curl` +- `git` +- `gcc` +- `lua5.1` +- `luarocks` +- `unzip` +- `luarocks busted` (for running tests) + +## Building the image + +> [!WARNING] +> You need to run the docker build command on +> a windows machine. +> It's a limitation of the windows docker images, +> provided by Microsoft, not us. + +```bash +make docker-build OS=windows +``` + +## Pushing the image + +> [!WARNING] +> You need to have write access to the docker registry at +> `ghcr.io/mistweaverco/kulala-nvim-windows-testrunner`. + +```bash +make docker-push OS=windows +``` diff --git a/tests/lib/shlex/shlex_spec.lua b/tests/lib/shlex/shlex_spec.lua new file mode 100644 index 0000000..2d771d5 --- /dev/null +++ b/tests/lib/shlex/shlex_spec.lua @@ -0,0 +1,139 @@ +local SHLEX = require("kulala.lib.shlex") + +-- testing data from cpython implementation +-- https://github.com/python/cpython/blob/4a6b1f179667e2a8c6131718eb78a15f726e047b/Lib/test/test_shlex.py#L73 +local posix_data = [[x|x| +foo bar|foo|bar| +foo bar|foo|bar| +foo bar |foo|bar| +foo bar bla fasel|foo|bar|bla|fasel| +x y z xxxx|x|y|z|xxxx| +\x bar|x|bar| +\ x bar| x|bar| +\ bar| bar| +foo \x bar|foo|x|bar| +foo \ x bar|foo| x|bar| +foo \ bar|foo| bar| +foo "bar" bla|foo|bar|bla| +"foo" "bar" "bla"|foo|bar|bla| +"foo" bar "bla"|foo|bar|bla| +"foo" bar bla|foo|bar|bla| +foo 'bar' bla|foo|bar|bla| +'foo' 'bar' 'bla'|foo|bar|bla| +'foo' bar 'bla'|foo|bar|bla| +'foo' bar bla|foo|bar|bla| +blurb foo"bar"bar"fasel" baz|blurb|foobarbarfasel|baz| +blurb foo'bar'bar'fasel' baz|blurb|foobarbarfasel|baz| +""|| +''|| +\"|"| +"\""|"| +"foo\ bar"|foo\ bar| +"foo\\ bar"|foo\ bar| +"foo\\ bar\""|foo\ bar"| +"foo\\" bar\"|foo\|bar"| +"foo\\ bar\" dfadf"|foo\ bar" dfadf| +"foo\\\ bar\" dfadf"|foo\\ bar" dfadf| +"foo\\\x bar\" dfadf"|foo\\x bar" dfadf| +"foo\x bar\" dfadf"|foo\x bar" dfadf| +\'|'| +'foo\ bar'|foo\ bar| +'foo\\ bar'|foo\\ bar| +"foo\\\x bar\" df'a\ 'df"|foo\\x bar" df'a\ 'df| +\"foo|"foo| +\"foo\x|"foox| +"foo\x"|foo\x| +"foo\ "|foo\ | +foo\ xx|foo xx| +foo\ x\x|foo xx| +foo\ x\x\"|foo xx"| +"foo\ x\x"|foo\ x\x| +"foo\ x\x\\"|foo\ x\x\| +"foo\ x\x\\""foobar"|foo\ x\x\foobar| +"foo\ x\x\\"\'"foobar"|foo\ x\x\'foobar| +"foo\ x\x\\"\'"fo'obar"|foo\ x\x\'fo'obar| +"foo\ x\x\\"\'"fo'obar" 'don'\''t'|foo\ x\x\'fo'obar|don't| +"foo\ x\x\\"\'"fo'obar" 'don'\''t' \\|foo\ x\x\'fo'obar|don't|\| +'foo\ bar'|foo\ bar| +'foo\\ bar'|foo\\ bar| +foo\ bar|foo bar| +foo#bar\nbaz|foo|baz| +:-) ;-)|:-)|;-)| +áéíóú|áéíóú| +]] + +-- broken data are test cases which works well in CPython shlex, but do not in Lua version +local broken_data = [[ +foo "" bar|foo||bar| +foo '' bar|foo||bar| +foo "" "" "" bar|foo||||bar| +foo '' '' '' bar|foo||||bar| +]] + +local function splitlines(str, sep) + if sep == nil then + sep = "\r?\n" + end + local pos = 0 + return function() + if pos >= #str then + return nil + end + local s, e = str:find(sep, pos) + local line = str:sub(pos, s and s - 1) + pos = (e or #str) + 1 + return line + end +end + +local function split(str, sep) + local t = {} + for part in splitlines(str, sep) do + table.insert(t, part) + end + return t +end + +-- reimplementation of test_shlex.py setUp code +-- https://github.com/python/cpython/blob/962304a54ca79da0838cf46dd4fb744045167cdd/Lib/test/test_shlex.py#L141 +local function test_cases(str) + local it = splitlines(str) + return function() + local line = it() + if line == nil then + return nil + end + local expected = split(line, "|") + local input = expected[1] + input = input:gsub("\\n", "\n") + table.remove(expected, 1) + return input, expected + end +end + +describe("posix", function() + for input, expected in test_cases(posix_data) do + it("'" .. input .. "'", function() + local actual = SHLEX.split(input) + assert.same(expected, actual) + end) + end +end) + +describe("curl", function() + it("should return url as one string", function() + local input = "curl http://example.com" + local actual = SHLEX.split(input) + local expected = { "curl", "http://example.com" } + assert.same(expected, actual) + end) +end) + +describe("broken", function() + for input, expected in test_cases(broken_data) do + it("'" .. input .. "'", function() + local actual = SHLEX.split(input) + assert.is_not.same(expected, actual) + end) + end +end) diff --git a/tests/test_helper/ui.lua b/tests/test_helper/ui.lua new file mode 100644 index 0000000..554bac4 --- /dev/null +++ b/tests/test_helper/ui.lua @@ -0,0 +1,55 @@ +local api = vim.api + +local UITestHelper = {} + +UITestHelper.delete_all_bufs = function() + -- Get a list of all buffer numbers + local buffers = vim.api.nvim_list_bufs() + + -- Iterate over each buffer and delete it + for _, buf in ipairs(buffers) do + -- Check if the buffer is valid and loaded + if vim.api.nvim_buf_is_loaded(buf) then + vim.api.nvim_buf_delete(buf, {}) + end + end +end + +---@param lines? string[] +---@param bufname? string +---@return integer bufnr +UITestHelper.create_buf = function(lines, bufname) + lines = lines or {} + local bufnr = vim.api.nvim_create_buf(true, true) + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + api.nvim_set_current_buf(bufnr) + api.nvim_win_set_cursor(0, { 1, 1 }) + + if bufname then + vim.api.nvim_buf_set_name(bufnr, bufname) + end + + return bufnr +end + +---@param bufnr integer +---@return string[] lines +UITestHelper.get_buf_lines = function(bufnr) + return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) +end + +---@return integer[] bufnr list +UITestHelper.list_loaded_bufs = function() + local bufnr_list = vim.api.nvim_list_bufs() + + local loaded_bufs = {} + for _, bufnr in ipairs(bufnr_list) do + if vim.api.nvim_buf_is_loaded(bufnr) then + loaded_bufs[#loaded_bufs + 1] = bufnr + end + end + + return loaded_bufs +end + +return UITestHelper diff --git a/tests/ui/init_spec.lua b/tests/ui/init_spec.lua new file mode 100644 index 0000000..7e2b284 --- /dev/null +++ b/tests/ui/init_spec.lua @@ -0,0 +1,59 @@ +local GLOBALS = require("kulala.globals") +local UI = require("kulala.ui") +local ui_helper = require("test_helper.ui") + +local assert = require("luassert") + +describe("kulala.ui", function() + -- restore all changed done by luassert before each test run + local snapshot + + before_each(function() + snapshot = assert:snapshot() + end) + + after_each(function() + snapshot:revert() + ui_helper.delete_all_bufs() + end) + + describe("from_curl()", function() + it("pastes simple curl", function() + local bufnr = ui_helper.create_buf() + vim.fn.setreg("+", "curl http://example.com") + + UI.from_curl() + + local expected = { + [[# curl http://example.com]], + [[GET http://example.com]], + [[]], + [[]], + } + assert.are.same(expected, ui_helper.get_buf_lines(bufnr)) + end) + end) + + describe("close()", function() + local extensions = { "http", "rest" } + for _, ext in ipairs(extensions) do + it(("closes ui and %s file"):format(ext), function() + ui_helper.create_buf({ "" }, GLOBALS.UI_ID) + ui_helper.create_buf({ "" }, "file_for_requests." .. ext) + + UI.close() + + local loaded_bufs = ui_helper.list_loaded_bufs() + for _, bufnr in ipairs(loaded_bufs) do + local bufname = vim.api.nvim_buf_get_name(bufnr) + + assert.is.True(bufname:find(GLOBALS.UI_ID) == nil, "should have closed the ui") + assert.is.True( + bufname:find("file_for_requests." .. ext) == nil, + "should have closed the file with extension: " .. ext + ) + end + end) + end + end) +end) diff --git a/tests/util/fs_spec.lua b/tests/util/fs_spec.lua new file mode 100644 index 0000000..f68dcde --- /dev/null +++ b/tests/util/fs_spec.lua @@ -0,0 +1,55 @@ +local Fs = require("kulala.utils.fs") + +local assert = require("luassert") + +describe("kulala.utils.fs", function() + -- restore all changed done by luassert before each test run + local snapshot + + before_each(function() + snapshot = assert:snapshot() + end) + + after_each(function() + snapshot:revert() + end) + + describe("join_paths on windows", function() + Fs.os = "windows" + Fs.ps = "\\" + it("joins mixed on windows", function() + local expected = "C:\\a\\b\\c" + local actual = Fs.join_paths("C:\\a", "b", "c") + assert.are.same(expected, actual) + end) + it("joins no-mixed on windows", function() + local expected = "C:\\a\\b\\c" + local actual = Fs.join_paths("C:\\a", "b", "c") + assert.are.same(expected, actual) + end) + it("fixes ps on windows", function() + local expected = "C:\\a\\user\\bin\\blah\\blubb" + local actual = Fs.join_paths("C:\\a", "user/bin", "blah/blubb") + assert.are.same(expected, actual) + end) + end) + describe("join_paths on linux", function() + Fs.os = "unix" + Fs.ps = "/" + it("joins mixed on unix", function() + local expected = "/a/b/c" + local actual = Fs.join_paths("/a", "b", "c") + assert.are.same(expected, actual) + end) + it("joins no-mixed on unix", function() + local expected = "/a/b/c" + local actual = Fs.join_paths("/a", "b", "c") + assert.are.same(expected, actual) + end) + it("joins more mixed on unix", function() + local expected = "/a/user/bin/blah/blubb" + local actual = Fs.join_paths("/a", "user/bin", "blah/blubb") + assert.are.same(expected, actual) + end) + end) +end)