diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 000000000..770012e87
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,47 @@
+---
+name: Bug report
+about: If you think something is broken with Tailwind CSS IntelliSense itself, create a bug report.
+title: ''
+labels: ''
+assignees: ''
+---
+
+**What version of VS Code are you using?**
+
+For example: v1.78.2
+
+**What version of Tailwind CSS IntelliSense are you using?**
+
+For example: v0.7.0
+
+**What version of Tailwind CSS are you using?**
+
+For example: v2.0.4
+
+**What package manager are you using?**
+
+For example: npm, yarn
+
+**What operating system are you using?**
+
+For example: macOS, Windows
+
+**Tailwind CSS Stylesheet (v4) or config file (v3)**
+
+```js
+// Paste the contents of your CSS file or config file here
+```
+
+**VS Code settings**
+
+```json
+// Paste your VS Code settings in JSON format here
+```
+
+**Reproduction URL**
+
+A public GitHub repo that includes a minimal reproduction of the bug. **Please do not link to your actual project**, what we need instead is a _minimal_ reproduction in a fresh project without any unnecessary code. This means it doesn't matter if your real project is private/confidential, since we want a link to a separate, isolated reproduction anyways.
+
+**Describe your issue**
+
+Describe the problem you're seeing, any important steps to reproduce and what behavior you expect instead
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 1f1449204..7c5bf993c 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -3,6 +3,3 @@ contact_links:
- name: Feature Request
url: https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas&title=%5BIntelliSense%5D%20
about: 'Suggest any ideas you have using our discussion forums.'
- - name: Bug Report
- url: https://github.com/tailwindlabs/tailwindcss-intellisense/issues/new?body=**What%20version%20of%20VS%20Code%20are%20you%20using%3F**%0A%0AFor%20example%3A%20v1.78.2%0A%0A**What%20version%20of%20Tailwind%20CSS%20IntelliSense%20are%20you%20using%3F**%0A%0AFor%20example%3A%20v0.7.0%0A%0A**What%20version%20of%20Tailwind%20CSS%20are%20you%20using%3F**%0A%0AFor%20example%3A%20v2.0.4%0A%0A**What%20package%20manager%20are%20you%20using%3F**%0A%0AFor%20example%3A%20npm%2C%20yarn%0A%0A**What%20operating%20system%20are%20you%20using%3F**%0A%0AFor%20example%3A%20macOS%2C%20Windows%0A%0A**Tailwind%20config**%0A%0A%60%60%60js%0A%2F%2F%20Paste%20the%20contents%20of%20your%20Tailwind%20config%20file%20here%0A%60%60%60%0A%0A**VS%20Code%20settings**%0A%0A%60%60%60json%0A%2F%2F%20Paste%20your%20VS%20Code%20settings%20in%20JSON%20format%20here%0A%60%60%60%0A%0A**Reproduction%20URL**%0A%0AA%20public%20GitHub%20repo%20that%20includes%20a%20minimal%20reproduction%20of%20the%20bug.%20**Please%20do%20not%20link%20to%20your%20actual%20project**%2C%20what%20we%20need%20instead%20is%20a%20_minimal_%20reproduction%20in%20a%20fresh%20project%20without%20any%20unnecessary%20code.%20This%20means%20it%20doesn%27t%20matter%20if%20your%20real%20project%20is%20private%2Fconfidential%2C%20since%20we%20want%20a%20link%20to%20a%20separate%2C%20isolated%20reproduction%20anyways.%0A%0A**Describe%20your%20issue**%0A%0ADescribe%20the%20problem%20you%27re%20seeing%2C%20any%20important%20steps%20to%20reproduce%20and%20what%20behavior%20you%20expect%20instead.
- about: If you think something is broken with Tailwind CSS IntelliSense itself, create a bug report.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..a01f382e4
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,42 @@
+name: Run Tests
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ tests:
+ strategy:
+ fail-fast: false
+ matrix:
+ node: [18, 20, 22, 24]
+ os:
+ - namespace-profile-default
+ - namespace-profile-macos-arm64
+ - namespace-profile-windows-amd64
+
+ runs-on: ${{ matrix.os }}
+ name: Run Tests - Node v${{ matrix.node }} / ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ cache: 'pnpm'
+ node-version: ${{ matrix.node }}
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Run syntax tests
+ working-directory: packages/tailwindcss-language-syntax
+ run: pnpm run build && pnpm run test
+
+ - name: Run service tests
+ working-directory: packages/tailwindcss-language-service
+ run: pnpm run build && pnpm run test
+
+ - name: Run server tests
+ working-directory: packages/tailwindcss-language-server
+ run: pnpm run build && pnpm run test
diff --git a/.vscode/launch.json b/.vscode/launch.json
index fd173bdc4..044786528 100755
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,7 +7,15 @@
"request": "launch",
"name": "Launch Client",
"runtimeExecutable": "${execPath}",
- "args": ["--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-tailwindcss"],
+ "args": [
+ // enable this flag if you want to activate the extension only when you are debugging the extension
+ // "--disable-extensions",
+ "--disable-updates",
+ "--disable-workspace-trust",
+ "--skip-release-notes",
+ "--skip-welcome",
+ "--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-tailwindcss"
+ ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/packages/vscode-tailwindcss/dist/**/*.js"],
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 1abde264e..aec43080f 100755
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -23,6 +23,9 @@
"panel": "dedicated",
"reveal": "never"
},
+ "options": {
+ "cwd": "${workspaceFolder}/packages/vscode-tailwindcss"
+ },
"problemMatcher": ["$tsc-watch"]
}
]
diff --git a/package.json b/package.json
index ff4394fba..e89380519 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"@npmcli/package-json": "^5.0.0",
"@types/culori": "^2.1.0",
"culori": "^4.0.1",
- "esbuild": "^0.25.0",
+ "esbuild": "^0.25.5",
"minimist": "^1.2.8",
"prettier": "^3.2.5",
"semver": "^7.7.1"
diff --git a/packages/tailwindcss-language-server/ThirdPartyNotices.txt b/packages/tailwindcss-language-server/ThirdPartyNotices.txt
index 26d8efa89..ee6b7439d 100644
--- a/packages/tailwindcss-language-server/ThirdPartyNotices.txt
+++ b/packages/tailwindcss-language-server/ThirdPartyNotices.txt
@@ -1,3 +1,78 @@
+@csstools/css-parser-algorithms@2.1.1
+
+The MIT License (MIT)
+
+Copyright 2022 Romain Menke, Antonio Laguna
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+================================================================================
+
+@csstools/css-tokenizer@2.1.1
+
+The MIT License (MIT)
+
+Copyright 2022 Romain Menke, Antonio Laguna
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+================================================================================
+
+@csstools/media-query-list-parser@2.0.4
+
+The MIT License (MIT)
+
+Copyright 2022 Romain Menke, Antonio Laguna
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+================================================================================
+
@parcel/watcher@2.0.3
MIT License
@@ -343,7 +418,33 @@ SOFTWARE.
================================================================================
-chokidar@3.5.1
+braces@3.0.3
+
+The MIT License (MIT)
+
+Copyright (c) 2014-present, Jon Schlinkert.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+================================================================================
+
+chokidar@3.6.0
The MIT License (MIT)
@@ -382,29 +483,28 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
================================================================================
-culori@0.20.1
-
-MIT License
+css.escape@1.5.1
-Copyright (c) 2018 Dan Burzo
+Copyright Mathias Bynens
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
@@ -507,6 +607,20 @@ THE SOFTWARE.
================================================================================
+detect-indent@6.0.0
+
+MIT License
+
+Copyright (c) Sindre Sorhus (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+================================================================================
+
dlv@1.1.3
# `dlv(obj, keypath)` [](https://npmjs.com/package/dlv) [](https://travis-ci.org/developit/dlv)
@@ -588,7 +702,7 @@ delve(obj, undefined, 'foo') === 'foo';
================================================================================
-dset@3.1.2
+dset@3.1.4
The MIT License (MIT)
@@ -614,31 +728,6 @@ THE SOFTWARE.
================================================================================
-enhanced-resolve@5.15.0
-
-Copyright JS Foundation and other contributors
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-'Software'), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-================================================================================
-
fast-glob@3.2.4
The MIT License (MIT)
@@ -679,11 +768,11 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
================================================================================
-is-builtin-module@3.2.1
+klona@2.0.4
MIT License
-Copyright (c) Sindre Sorhus (sindresorhus.com)
+Copyright (c) Luke Edwards (lukeed.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@@ -693,37 +782,61 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
================================================================================
-klona@2.0.4
-
-MIT License
+line-column@1.0.2
-Copyright (c) Luke Edwards (lukeed.com)
+Copyright (c) 2016 IRIDE Monad
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
================================================================================
-minimatch@5.1.4
+moo@0.5.1
-The ISC License
+BSD 3-Clause License
-Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
+Copyright (c) 2017, Tim Radvan (tjvr)
+All rights reserved.
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
-IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
@@ -819,7 +932,34 @@ OTHER DEALINGS IN THE SOFTWARE.
================================================================================
-postcss@8.3.9
+postcss-value-parser@4.2.0
+
+Copyright (c) Bogdan Chadkin
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+================================================================================
+
+postcss@8.4.31
The MIT License (MIT)
@@ -870,15 +1010,89 @@ SOFTWARE.
================================================================================
-stack-trace@0.0.10
+semver@7.7.1
-Copyright (c) 2011 Felix Geisendörfer (felix@debuggable.com)
+The ISC License
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+================================================================================
+
+sift-string@0.0.2
+
+# sift
+
+ Fast String Distance (SIFT) Algorithm.
+
+[](https://nodei.co/npm/sift-string/)
+
+[](https://david-dm.org/timoxley/sift)
+
+## Installation
+
+#### Browserify/Node
+
+ $ npm install sift-string
+
+
+#### Component
+
+ $ component install timoxley/sift
+
+## Demo
+
+[Demo](http://timoxley.github.com/sift/examples/spellcheck/)
+
+or if you want to check it out locally:
+
+```bash
+# run only once to install npm dev dependencies
+npm install .
+# this will install && build the components and open the demo web page
+npm run c-demo
+```
+
+## API
+
+### sift(string, string)
+
+Return 'distance' between two strings.
+
+## TODO
+
+* Dictionary Helper supply it emits suggestions.
+
+## Credit
+
+Code extracted from [MailCheck](https://github.com/kicksend/mailcheck)
+
+## License
+
+ MIT
+
+================================================================================
+
+stack-trace@0.0.10
+
+Copyright (c) 2011 Felix Geisendörfer (felix@debuggable.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
@@ -894,7 +1108,34 @@ Copyright (c) 2011 Felix Geisendörfer (felix@debuggable.com)
================================================================================
-tailwindcss@3.3.0
+stringify-object@3.3.0
+
+Copyright (c) 2015, Yeoman team
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+================================================================================
+
+tailwindcss@3.4.17
MIT License
@@ -920,7 +1161,33 @@ SOFTWARE.
================================================================================
-vscode-css-languageservice@5.4.1
+tmp-cache@1.1.0
+
+The MIT License (MIT)
+
+Copyright (c) Luke Edwards (lukeed.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+================================================================================
+
+vscode-css-languageservice@6.2.9
The MIT License (MIT)
@@ -946,7 +1213,7 @@ SOFTWARE.
================================================================================
-vscode-languageserver-textdocument@1.0.7
+vscode-jsonrpc@8.2.0
Copyright (c) Microsoft Corporation
@@ -962,7 +1229,7 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
================================================================================
-vscode-languageserver@8.0.2
+vscode-languageclient@8.1.0
Copyright (c) Microsoft Corporation
@@ -978,238 +1245,46 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
================================================================================
-vscode-uri@3.0.2
-
-The MIT License (MIT)
-
-Copyright (c) Microsoft
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+vscode-languageserver-textdocument@1.0.11
-================================================================================
-
-css.escape@1.5.1
-
-Copyright Mathias Bynens
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-================================================================================
+Copyright (c) Microsoft Corporation
-detect-indent@6.0.0
+All rights reserved.
MIT License
-Copyright (c) Sindre Sorhus (sindresorhus.com)
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-================================================================================
-
-line-column@1.0.2
-
-Copyright (c) 2016 IRIDE Monad
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
-moo@0.5.1
+vscode-languageserver@8.1.0
-BSD 3-Clause License
+Copyright (c) Microsoft Corporation
-Copyright (c) 2017, Tim Radvan (tjvr)
All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-================================================================================
-
-semver@7.3.7
-
-The ISC License
-
-Copyright (c) Isaac Z. Schlueter and Contributors
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
-IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-================================================================================
-
-sift-string@0.0.2
-
-# sift
-
- Fast String Distance (SIFT) Algorithm.
-
-[](https://nodei.co/npm/sift-string/)
-
-[](https://david-dm.org/timoxley/sift)
-
-## Installation
-
-#### Browserify/Node
-
- $ npm install sift-string
-
-
-#### Component
-
- $ component install timoxley/sift
-
-## Demo
-
-[Demo](http://timoxley.github.com/sift/examples/spellcheck/)
-
-or if you want to check it out locally:
-
-```bash
-# run only once to install npm dev dependencies
-npm install .
-# this will install && build the components and open the demo web page
-npm run c-demo
-```
-
-## API
-
-### sift(string, string)
-
-Return 'distance' between two strings.
-
-## TODO
-
-* Dictionary Helper supply it emits suggestions.
-
-## Credit
-
-Code extracted from [MailCheck](https://github.com/kicksend/mailcheck)
-
-## License
-
- MIT
-
-================================================================================
-
-stringify-object@3.3.0
-
-Copyright (c) 2015, Yeoman team
-All rights reserved.
+MIT License
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
-tmp-cache@1.1.0
+vscode-uri@3.0.2
The MIT License (MIT)
-Copyright (c) Luke Edwards (lukeed.com)
+Copyright (c) Microsoft
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index f8736d195..e8bfd41d2 100644
--- a/packages/tailwindcss-language-server/package.json
+++ b/packages/tailwindcss-language-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/language-server",
- "version": "0.14.6",
+ "version": "0.14.23",
"description": "Tailwind CSS Language Server",
"license": "MIT",
"repository": {
@@ -34,14 +34,23 @@
"access": "public"
},
"devDependencies": {
- "@parcel/watcher": "2.0.3",
+ "@parcel/watcher": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
"@tailwindcss/aspect-ratio": "0.4.2",
"@tailwindcss/container-queries": "0.1.0",
"@tailwindcss/forms": "0.5.3",
"@tailwindcss/language-service": "workspace:*",
"@tailwindcss/line-clamp": "0.4.2",
- "@tailwindcss/oxide": "^4.0.0-alpha.19",
+ "@tailwindcss/oxide": "^4.1.0",
"@tailwindcss/typography": "0.5.7",
+ "@types/braces": "3.0.1",
"@types/color-name": "^1.1.3",
"@types/culori": "^2.1.0",
"@types/debounce": "1.2.0",
@@ -65,8 +74,7 @@
"dlv": "1.1.3",
"dset": "3.1.4",
"enhanced-resolve": "^5.16.1",
- "esbuild": "^0.25.0",
- "fast-glob": "3.2.4",
+ "esbuild": "^0.25.5",
"find-up": "5.0.0",
"jiti": "^2.3.3",
"klona": "2.0.4",
@@ -75,7 +83,7 @@
"normalize-path": "3.0.0",
"picomatch": "^4.0.1",
"pkg-up": "3.1.0",
- "postcss": "8.4.31",
+ "postcss": "8.5.4",
"postcss-import": "^16.1.0",
"postcss-load-config": "3.0.1",
"postcss-selector-parser": "6.0.2",
@@ -83,18 +91,20 @@
"rimraf": "3.0.2",
"stack-trace": "0.0.10",
"tailwindcss": "3.4.17",
- "tailwindcss-v4": "npm:tailwindcss@4.0.6",
+ "tailwindcss-v4": "npm:tailwindcss@4.1.1",
+ "tinyglobby": "^0.2.12",
"tsconfck": "^3.1.4",
"tsconfig-paths": "^4.2.0",
- "typescript": "5.3.3",
- "vite-tsconfig-paths": "^4.3.1",
- "vitest": "^1.6.1",
- "vscode-css-languageservice": "6.2.9",
+ "typescript": "5.8.3",
+ "vite": "^6.3.5",
+ "vite-tsconfig-paths": "^5.1.4",
+ "vitest": "^3.2.1",
+ "vscode-css-languageservice": "6.3.6",
"vscode-jsonrpc": "8.2.0",
"vscode-languageclient": "8.1.0",
"vscode-languageserver": "8.1.0",
"vscode-languageserver-protocol": "^3.17.5",
- "vscode-languageserver-textdocument": "1.0.11",
+ "vscode-languageserver-textdocument": "1.0.12",
"vscode-uri": "3.0.2"
},
"engines": {
diff --git a/packages/tailwindcss-language-server/src/config.ts b/packages/tailwindcss-language-server/src/config.ts
index d8364d061..a5e68f7df 100644
--- a/packages/tailwindcss-language-server/src/config.ts
+++ b/packages/tailwindcss-language-server/src/config.ts
@@ -1,6 +1,9 @@
import merge from 'deepmerge'
import { isObject } from './utils'
-import type { Settings } from '@tailwindcss/language-service/src/util/state'
+import {
+ getDefaultTailwindSettings,
+ type Settings,
+} from '@tailwindcss/language-service/src/util/state'
import type { Connection } from 'vscode-languageserver'
export interface SettingsCache {
@@ -8,40 +11,6 @@ export interface SettingsCache {
clear(): void
}
-function getDefaultSettings(): Settings {
- return {
- editor: { tabSize: 2 },
- tailwindCSS: {
- inspectPort: null,
- emmetCompletions: false,
- classAttributes: ['class', 'className', 'ngClass', 'class:list'],
- codeActions: true,
- hovers: true,
- suggestions: true,
- validate: true,
- colorDecorators: true,
- rootFontSize: 16,
- lint: {
- cssConflict: 'warning',
- invalidApply: 'error',
- invalidScreen: 'error',
- invalidVariant: 'error',
- invalidConfigPath: 'error',
- invalidTailwindDirective: 'error',
- invalidSourceDirective: 'error',
- recommendedVariantOrder: 'warning',
- },
- showPixelEquivalents: true,
- includeLanguages: {},
- files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] },
- experimental: {
- classRegex: [],
- configFile: null,
- },
- },
- }
-}
-
export function createSettingsCache(connection: Connection): SettingsCache {
const cache: Map = new Map()
@@ -73,7 +42,7 @@ export function createSettingsCache(connection: Connection): SettingsCache {
tailwindCSS = isObject(tailwindCSS) ? tailwindCSS : {}
return merge(
- getDefaultSettings(),
+ getDefaultTailwindSettings(),
{ editor, tailwindCSS },
{ arrayMerge: (_destinationArray, sourceArray, _options) => sourceArray },
)
diff --git a/packages/tailwindcss-language-server/src/css/extract-source-directives.ts b/packages/tailwindcss-language-server/src/css/extract-source-directives.ts
index 9de33a9b3..a97e35594 100644
--- a/packages/tailwindcss-language-server/src/css/extract-source-directives.ts
+++ b/packages/tailwindcss-language-server/src/css/extract-source-directives.ts
@@ -1,12 +1,21 @@
import type { Plugin } from 'postcss'
+import type { SourcePattern } from '../project-locator'
-export function extractSourceDirectives(sources: string[]): Plugin {
+export function extractSourceDirectives(sources: SourcePattern[]): Plugin {
return {
postcssPlugin: 'extract-at-rules',
AtRule: {
source: ({ params }) => {
+ let negated = /^not\s+/.test(params)
+
+ if (negated) params = params.slice(4).trimStart()
+
if (params[0] !== '"' && params[0] !== "'") return
- sources.push(params.slice(1, -1))
+
+ sources.push({
+ pattern: params.slice(1, -1),
+ negated,
+ })
},
},
}
diff --git a/packages/tailwindcss-language-server/src/language/css-server.ts b/packages/tailwindcss-language-server/src/language/css-server.ts
index a146cea4f..73e967fcc 100644
--- a/packages/tailwindcss-language-server/src/language/css-server.ts
+++ b/packages/tailwindcss-language-server/src/language/css-server.ts
@@ -13,7 +13,7 @@ import {
CompletionItemKind,
Connection,
} from 'vscode-languageserver/node'
-import { TextDocument } from 'vscode-languageserver-textdocument'
+import { Position, TextDocument } from 'vscode-languageserver-textdocument'
import { Utils, URI } from 'vscode-uri'
import { getLanguageModelCache } from './languageModelCache'
import { Stylesheet } from 'vscode-css-languageservice'
@@ -121,6 +121,7 @@ export class CssServer {
async function withDocumentAndSettings(
uri: string,
callback: (result: {
+ original: TextDocument
document: TextDocument
settings: LanguageSettings | undefined
}) => T | Promise,
@@ -130,13 +131,64 @@ export class CssServer {
return null
}
return await callback({
+ original: document,
document: createVirtualCssDocument(document),
settings: await getDocumentSettings(document),
})
}
+ function isInImportDirective(doc: TextDocument, pos: Position) {
+ let text = doc.getText({
+ start: { line: pos.line, character: 0 },
+ end: pos,
+ })
+
+ // Scan backwards to see if we're inside an `@import` directive
+ let foundImport = false
+ let foundDirective = false
+
+ for (let i = text.length - 1; i >= 0; i--) {
+ let char = text[i]
+ if (char === '\n') break
+
+ if (char === '(' && !foundDirective) {
+ if (text.startsWith(' source(', i - 7)) {
+ foundDirective = true
+ }
+
+ //
+ else if (text.startsWith(' theme(', i - 6)) {
+ foundDirective = true
+ }
+
+ //
+ else if (text.startsWith(' prefix(', i - 7)) {
+ foundDirective = true
+ }
+ }
+
+ //
+ else if (char === '@' && !foundImport) {
+ if (text.startsWith('@import ', i)) {
+ foundImport = true
+ }
+ }
+ }
+
+ return foundImport && foundDirective
+ }
+
connection.onCompletion(async ({ textDocument, position }, _token) =>
- withDocumentAndSettings(textDocument.uri, async ({ document, settings }) => {
+ withDocumentAndSettings(textDocument.uri, async ({ original, document, settings }) => {
+ // If we're inside source(…), prefix(…), or theme(…), don't show
+ // completions from the CSS language server
+ if (isInImportDirective(original, position)) {
+ return {
+ isIncomplete: false,
+ items: [],
+ }
+ }
+
let result = await cssLanguageService.doComplete2(
document,
position,
@@ -225,7 +277,7 @@ export class CssServer {
if (match) {
symbol.name = `${match[1]} ${match[2]?.trim() ?? match[3]?.trim()}`
}
- } else if (symbol.name === `.placeholder`) {
+ } else if (/^\._+$/.test(symbol.name)) {
let doc = documents.get(symbol.location.uri)
let text = doc.getText(symbol.location.range)
let match = text.trim().match(/^(@[^\s]+)(?:([^{]+)[{]|([^;{]+);)/)
diff --git a/packages/tailwindcss-language-server/src/language/rewriting.test.ts b/packages/tailwindcss-language-server/src/language/rewriting.test.ts
index 33596a2ec..23249932b 100644
--- a/packages/tailwindcss-language-server/src/language/rewriting.test.ts
+++ b/packages/tailwindcss-language-server/src/language/rewriting.test.ts
@@ -50,16 +50,22 @@ test('@utility', () => {
'@utility foo-* {',
' color: red;',
'}',
+ '@utility bar-* {',
+ ' color: --value(--font-*-line-height);',
+ '}',
]
let output = [
//
- '.placeholder {', // wrong
+ '._______ {',
' color: red;',
'}',
- '.placeholder {', // wrong
+ '._______ {',
' color: red;',
'}',
+ '._______ {',
+ ' color: --value(--font-_-line-height);',
+ '}',
]
expect(rewriteCss(input.join('\n'))).toBe(output.join('\n'))
@@ -69,20 +75,36 @@ test('@theme', () => {
let input = [
//
'@theme {',
- ' color: red;',
+ ' --color: red;',
+ ' --*: initial;',
+ ' --text*: initial;',
+ ' --font-*: initial;',
+ ' --font-weight-*: initial;',
'}',
'@theme inline reference static default {',
- ' color: red;',
+ ' --color: red;',
+ ' --*: initial;',
+ ' --text*: initial;',
+ ' --font-*: initial;',
+ ' --font-weight-*: initial;',
'}',
]
let output = [
//
- '.placeholder {', // wrong
- ' color: red;',
+ '._____ {',
+ ' --color: red;',
+ ' --_: initial;',
+ ' --text_: initial;',
+ ' --font-_: initial;',
+ ' --font-weight-_: initial;',
'}',
- '.placeholder {', // wrong
- ' color: red;',
+ '._____ {',
+ ' --color: red;',
+ ' --_: initial;',
+ ' --text_: initial;',
+ ' --font-_: initial;',
+ ' --font-weight-_: initial;',
'}',
]
@@ -102,8 +124,8 @@ test('@custom-variant', () => {
let output = [
//
- '@media (℘) {}', // wrong
- '.placeholder {', // wrong
+ '@media(℘) {}',
+ '.______________ {',
' &:hover {',
' @slot;',
' }',
@@ -125,7 +147,7 @@ test('@variant', () => {
let output = [
//
- '.placeholder {', // wrong
+ '._______ {',
' &:hover {',
' @slot;',
' }',
diff --git a/packages/tailwindcss-language-server/src/language/rewriting.ts b/packages/tailwindcss-language-server/src/language/rewriting.ts
index 4dc7a4293..1d6607097 100644
--- a/packages/tailwindcss-language-server/src/language/rewriting.ts
+++ b/packages/tailwindcss-language-server/src/language/rewriting.ts
@@ -14,9 +14,10 @@ function replaceWithAtRule(delta = 0) {
}
function replaceWithStyleRule(delta = 0) {
- return (_match: string, p1: string) => {
+ return (_match: string, name: string, p1: string) => {
+ let className = '_'.repeat(name.length)
let spaces = ' '.repeat(p1.length + delta)
- return `.placeholder${spaces}{`
+ return `.${className}${spaces}{`
}
}
@@ -36,16 +37,16 @@ export function rewriteCss(css: string) {
css = css.replace(/@screen(\s+[^{]+){/g, replaceWithAtRule(-2))
css = css.replace(/@variants(\s+[^{]+){/g, replaceWithAtRule())
css = css.replace(/@responsive(\s*){/g, replaceWithAtRule())
- css = css.replace(/@utility(\s+[^{]+){/g, replaceWithStyleRule())
- css = css.replace(/@theme(\s+[^{]*){/g, replaceWithStyleRule())
+ css = css.replace(/@(utility)(\s+[^{]+){/g, replaceWithStyleRule())
+ css = css.replace(/@(theme)(\s+[^{]*){/g, replaceWithStyleRule())
- css = css.replace(/@custom-variant(\s+[^;{]+);/g, (match: string) => {
- let spaces = ' '.repeat(match.length - 11)
- return `@media (${MEDIA_MARKER})${spaces}{}`
+ css = css.replace(/@(custom-variant)(\s+[^;{]+);/g, (match: string, name: string) => {
+ let spaces = ' '.repeat(match.length - name.length + 3)
+ return `@media(${MEDIA_MARKER})${spaces}{}`
})
- css = css.replace(/@custom-variant(\s+[^{]+){/g, replaceWithStyleRule())
- css = css.replace(/@variant(\s+[^{]+){/g, replaceWithStyleRule())
+ css = css.replace(/@(custom-variant)(\s+[^{]+){/g, replaceWithStyleRule())
+ css = css.replace(/@(variant)(\s+[^{]+){/g, replaceWithStyleRule())
css = css.replace(/@layer(\s+[^{]{2,}){/g, replaceWithAtRule(-3))
css = css.replace(/@reference\s*([^;]{2,})/g, '@import $1')
@@ -73,8 +74,13 @@ export function rewriteCss(css: string) {
return match.replace(/[*]/g, '_')
})
+ // Replace `--*` with `--_`
+ // Replace `--some-var*` with `--some-var_`
// Replace `--some-var-*` with `--some-var-_`
- css = css.replace(/--([a-zA-Z0-9]+)-[*]/g, '--$1_')
+ // Replace `--text-*-line-height` with `--text-_-line-height`
+ css = css.replace(/--([a-zA-Z0-9-*]*)/g, (match) => {
+ return match.replace(/[*]/g, '_')
+ })
return css
}
diff --git a/packages/tailwindcss-language-server/src/matching.ts b/packages/tailwindcss-language-server/src/matching.ts
new file mode 100644
index 000000000..a373b116c
--- /dev/null
+++ b/packages/tailwindcss-language-server/src/matching.ts
@@ -0,0 +1,24 @@
+import picomatch from 'picomatch'
+import { DefaultMap } from './util/default-map'
+
+export interface PathMatcher {
+ anyMatches(pattern: string, paths: string[]): boolean
+ clear(): void
+}
+
+export function createPathMatcher(): PathMatcher {
+ let matchers = new DefaultMap((pattern) => {
+ // Escape picomatch special characters so they're matched literally
+ pattern = pattern.replace(/[\[\]{}()]/g, (m) => `\\${m}`)
+
+ return picomatch(pattern, { dot: true })
+ })
+
+ return {
+ anyMatches: (pattern, paths) => {
+ let check = matchers.get(pattern)
+ return paths.some((path) => check(path))
+ },
+ clear: () => matchers.clear(),
+ }
+}
diff --git a/packages/tailwindcss-language-server/src/oxide.ts b/packages/tailwindcss-language-server/src/oxide.ts
index bb8700ff4..4dd529dfc 100644
--- a/packages/tailwindcss-language-server/src/oxide.ts
+++ b/packages/tailwindcss-language-server/src/oxide.ts
@@ -36,8 +36,8 @@ declare namespace OxideV2 {
}
}
-// This covers the Oxide API from v4.0.0-alpha.20+
-declare namespace OxideV3 {
+// This covers the Oxide API from v4.0.0-alpha.30+
+declare namespace OxideV3And4 {
interface GlobEntry {
base: string
pattern: string
@@ -58,17 +58,44 @@ declare namespace OxideV3 {
}
}
+// This covers the Oxide API from v4.1.0+
+declare namespace OxideV5 {
+ interface GlobEntry {
+ base: string
+ pattern: string
+ }
+
+ interface SourceEntry {
+ base: string
+ pattern: string
+ negated: boolean
+ }
+
+ interface ScannerOptions {
+ sources: Array
+ }
+
+ interface ScannerConstructor {
+ new (options: ScannerOptions): Scanner
+ }
+
+ interface Scanner {
+ get files(): Array
+ get globs(): Array
+ }
+}
+
interface Oxide {
scanDir?(options: OxideV1.ScanOptions): OxideV1.ScanResult
scanDir?(options: OxideV2.ScanOptions): OxideV2.ScanResult
- Scanner?: OxideV3.ScannerConstructor
+ Scanner?: OxideV3And4.ScannerConstructor | OxideV5.ScannerConstructor
}
async function loadOxideAtPath(id: string): Promise {
let oxide = await import(id)
// This is a much older, unsupported version of Oxide before v4.0.0-alpha.1
- if (!oxide.scanDir) return null
+ if (!oxide.scanDir && !oxide.Scanner) return null
return oxide
}
@@ -78,11 +105,17 @@ interface GlobEntry {
pattern: string
}
+interface SourceEntry {
+ base: string
+ pattern: string
+ negated: boolean
+}
+
interface ScanOptions {
oxidePath: string
oxideVersion: string
basePath: string
- sources: Array
+ sources: Array
}
interface ScanResult {
@@ -101,7 +134,7 @@ interface ScanResult {
* For example, the `sources` option is ignored before v4.0.0-alpha.19.
*/
export async function scan(options: ScanOptions): Promise {
- const oxide = await loadOxideAtPath(options.oxidePath)
+ let oxide = await loadOxideAtPath(options.oxidePath)
if (!oxide) return null
// V1
@@ -118,38 +151,58 @@ export async function scan(options: ScanOptions): Promise {
}
// V2
- if (lte(options.oxideVersion, '4.0.0-alpha.19')) {
+ else if (lte(options.oxideVersion, '4.0.0-alpha.19')) {
let result = oxide.scanDir({
base: options.basePath,
- sources: options.sources,
+ sources: options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
})
return {
files: result.files,
- globs: result.globs,
+ globs: result.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
}
}
// V3
- if (lte(options.oxideVersion, '4.0.0-alpha.30')) {
- let scanner = new oxide.Scanner({
+ else if (lte(options.oxideVersion, '4.0.0-alpha.30')) {
+ let scanner = new (oxide.Scanner as OxideV3And4.ScannerConstructor)({
detectSources: { base: options.basePath },
- sources: options.sources,
+ sources: options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
})
return {
files: scanner.files,
- globs: scanner.globs,
+ globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
}
}
// V4
- let scanner = new oxide.Scanner({
- sources: [{ base: options.basePath, pattern: '**/*' }, ...options.sources],
- })
+ else if (lte(options.oxideVersion, '4.0.9999')) {
+ let scanner = new (oxide.Scanner as OxideV3And4.ScannerConstructor)({
+ sources: [
+ { base: options.basePath, pattern: '**/*' },
+ ...options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
+ ],
+ })
- return {
- files: scanner.files,
- globs: scanner.globs,
+ return {
+ files: scanner.files,
+ globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
+ }
+ }
+
+ // V5
+ else {
+ let scanner = new (oxide.Scanner as OxideV5.ScannerConstructor)({
+ sources: [
+ { base: options.basePath, pattern: '**/*', negated: false },
+ ...options.sources.map((g) => ({ base: g.base, pattern: g.pattern, negated: g.negated })),
+ ],
+ })
+
+ return {
+ files: scanner.files,
+ globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
+ }
}
}
diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index 6414a099c..7728d7957 100644
--- a/packages/tailwindcss-language-server/src/project-locator.test.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.test.ts
@@ -4,12 +4,13 @@ import { ProjectLocator } from './project-locator'
import { URL, fileURLToPath } from 'url'
import { Settings } from '@tailwindcss/language-service/src/util/state'
import { createResolver } from './resolver'
-import { css, defineTest, js, json, scss, Storage, TestUtils } from './testing'
+import { css, defineTest, html, js, json, scss, Storage, symlinkTo, TestUtils } from './testing'
+import { normalizePath } from './utils'
let settings: Settings = {
tailwindCSS: {
files: {
- exclude: [],
+ exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'],
},
},
} as any
@@ -29,12 +30,14 @@ function testFixture(fixture: string, details: any[]) {
let detail = details[i]
- let configPath = path.relative(fixturePath, project.config.path)
+ let configPath = path.posix.relative(normalizePath(fixturePath), project.config.path)
expect(configPath).toEqual(detail?.config)
if (detail?.content) {
- let expected = detail?.content.map((path) => path.replace('{URL}', fixturePath)).sort()
+ let expected = detail?.content
+ .map((path) => path.replace('{URL}', normalizePath(fixturePath)))
+ .sort()
let actual = project.documentSelector
.filter((selector) => selector.priority === 1 /** content */)
@@ -45,7 +48,9 @@ function testFixture(fixture: string, details: any[]) {
}
if (detail?.selectors) {
- let expected = detail?.selectors.map((path) => path.replace('{URL}', fixturePath)).sort()
+ let expected = detail?.selectors
+ .map((path) => path.replace('{URL}', normalizePath(fixturePath)))
+ .sort()
let actual = project.documentSelector.map((selector) => selector.pattern).sort()
@@ -114,27 +119,22 @@ testFixture('v4/workspaces', [
{
config: 'packages/admin/app.css',
selectors: [
- '{URL}/node_modules/tailwindcss/**',
- '{URL}/node_modules/tailwindcss/index.css',
- '{URL}/node_modules/tailwindcss/theme.css',
- '{URL}/node_modules/tailwindcss/utilities.css',
+ '{URL}/packages/admin/*',
'{URL}/packages/admin/**',
'{URL}/packages/admin/app.css',
'{URL}/packages/admin/package.json',
+ '{URL}/packages/admin/tw.css',
],
},
{
config: 'packages/web/app.css',
selectors: [
- '{URL}/node_modules/tailwindcss/**',
- '{URL}/node_modules/tailwindcss/index.css',
- '{URL}/node_modules/tailwindcss/theme.css',
- '{URL}/node_modules/tailwindcss/utilities.css',
'{URL}/packages/style-export/**',
'{URL}/packages/style-export/lib.css',
'{URL}/packages/style-export/theme.css',
'{URL}/packages/style-main-field/**',
'{URL}/packages/style-main-field/lib.css',
+ '{URL}/packages/web/*',
'{URL}/packages/web/**',
'{URL}/packages/web/app.css',
'{URL}/packages/web/package.json',
@@ -142,68 +142,173 @@ testFixture('v4/workspaces', [
},
])
-testFixture('v4/auto-content', [
- //
- {
- config: 'src/app.css',
- content: [
- '{URL}/package.json',
- '{URL}/src/index.html',
- '{URL}/src/components/example.html',
- '{URL}/src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
- ],
+testLocator({
+ name: 'automatic content detection with Oxide',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "4.1.0",
+ "@tailwindcss/oxide": "4.1.0"
+ }
+ }
+ `,
+ 'src/index.html': html`Test
`,
+ 'src/app.css': css`
+ @import 'tailwindcss';
+ `,
+ 'src/components/example.html': html`Test
`,
},
-])
+ expected: [
+ {
+ config: '/src/app.css',
+ content: [
+ '/*',
+ '/package.json',
+ '/src/**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
+ '/src/components/example.html',
+ '/src/index.html',
+ ],
+ },
+ ],
+})
-testFixture('v4/auto-content-split', [
- //
- {
- // TODO: This should _probably_ not be present
- config: 'node_modules/tailwindcss/index.css',
- content: [],
- },
- {
- config: 'src/app.css',
- content: [
- '{URL}/package.json',
- '{URL}/src/index.html',
- '{URL}/src/components/example.html',
- '{URL}/src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
- ],
+testLocator({
+ name: 'automatic content detection with Oxide using split config',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "4.1.0",
+ "@tailwindcss/oxide": "4.1.0"
+ }
+ }
+ `,
+ 'src/index.html': html`Test
`,
+ 'src/app.css': css`
+ @import 'tailwindcss/preflight' layer(base);
+ @import 'tailwindcss/theme' layer(theme);
+ @import 'tailwindcss/utilities' layer(utilities);
+ `,
+ 'src/components/example.html': html`Test
`,
},
-])
+ expected: [
+ {
+ config: '/src/app.css',
+ content: [
+ '/*',
+ '/package.json',
+ '/src/**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
+ '/src/components/example.html',
+ '/src/index.html',
+ ],
+ },
+ ],
+})
-testFixture('v4/custom-source', [
- //
- {
- config: 'admin/app.css',
- content: [
- '{URL}/admin/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
- '{URL}/admin/**/*.bin',
- '{URL}/admin/foo.bin',
- '{URL}/package.json',
- '{URL}/shared.html',
- '{URL}/web/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
- ],
+testLocator({
+ name: 'automatic content detection with custom sources',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "4.1.0",
+ "@tailwindcss/oxide": "4.1.0"
+ }
+ }
+ `,
+ 'admin/app.css': css`
+ @import './tw.css';
+ @import './ui.css';
+ `,
+ 'admin/tw.css': css`
+ @import 'tailwindcss';
+ @source './**/*.bin';
+ `,
+ 'admin/ui.css': css`
+ @theme {
+ --color-potato: #907a70;
+ }
+ `,
+ 'admin/foo.bin': html`Admin
`,
+
+ 'web/app.css': css`
+ @import 'tailwindcss';
+ @source './*.bin';
+ `,
+ 'web/bar.bin': html`Web
`,
+
+ 'shared.html': html`I belong to no one!
`,
},
- {
- config: 'web/app.css',
- content: [
- '{URL}/admin/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
- '{URL}/web/*.bin',
- '{URL}/web/bar.bin',
- '{URL}/package.json',
- '{URL}/shared.html',
- '{URL}/web/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
- ],
+ expected: [
+ {
+ config: '/admin/app.css',
+ content: [
+ '/*',
+ '/admin/foo.bin',
+ '/admin/tw.css',
+ '/admin/ui.css',
+ '/admin/{**/*.bin,**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}',
+ '/package.json',
+ '/shared.html',
+ '/web/**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
+ '/web/app.css',
+ ],
+ },
+ {
+ config: '/web/app.css',
+ content: [
+ '/*',
+ '/admin/**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
+ '/admin/app.css',
+ '/admin/tw.css',
+ '/admin/ui.css',
+ '/package.json',
+ '/shared.html',
+ '/web/bar.bin',
+ '/web/{**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue},*.bin}',
+ ],
+ },
+ ],
+})
+
+testLocator({
+ name: 'automatic content detection with negative custom sources',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "4.1.0",
+ "@tailwindcss/oxide": "4.1.0"
+ }
+ }
+ `,
+ 'src/app.css': css`
+ @import 'tailwindcss';
+ @source './**/*.html';
+ @source not './ignored.html';
+ `,
+ 'src/index.html': html``,
+ 'src/ignored.html': html``,
},
-])
+ expected: [
+ {
+ config: '/src/app.css',
+ content: [
+ '/*',
+ '/package.json',
+ '/src/index.html',
+ '/src/{**/*.html,**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}',
+ ],
+ },
+ ],
+})
testFixture('v4/missing-files', [
//
{
config: 'app.css',
- content: ['{URL}/package.json'],
+ content: ['{URL}/*', '{URL}/i-exist.css', '{URL}/package.json'],
},
])
@@ -212,8 +317,10 @@ testFixture('v4/path-mappings', [
{
config: 'app.css',
content: [
+ '{URL}/*',
'{URL}/package.json',
- '{URL}/src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
+ '{URL}/src/**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
+ '{URL}/src/a/file.css',
'{URL}/src/a/my-config.ts',
'{URL}/src/a/my-plugin.ts',
'{URL}/tsconfig.json',
@@ -225,7 +332,7 @@ testFixture('v4/invalid-import-order', [
//
{
config: 'tailwind.css',
- content: ['{URL}/package.json'],
+ content: ['{URL}/*', '{URL}/a.css', '{URL}/b.css', '{URL}/package.json'],
},
])
@@ -237,7 +344,7 @@ testLocator({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "^4.0.2"
+ "tailwindcss": "4.1.0"
}
}
`,
@@ -285,7 +392,7 @@ testLocator({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.6"
+ "tailwindcss": "4.1.0"
}
}
`,
@@ -314,13 +421,173 @@ testLocator({
},
expected: [
{
- version: '4.0.6',
+ version: '4.1.0',
config: '/src/articles/articles.css',
content: [],
},
],
})
+testLocator({
+ name: 'Recursive symlinks do not cause infinite traversal loops',
+ fs: {
+ 'src/a/b/c/index.css': css`
+ @import 'tailwindcss';
+ `,
+ 'src/a/b/c/z': symlinkTo('src', 'dir'),
+ 'src/a/b/x': symlinkTo('src', 'dir'),
+ 'src/a/b/y': symlinkTo('src', 'dir'),
+ 'src/a/b/z': symlinkTo('src', 'dir'),
+ 'src/a/x': symlinkTo('src', 'dir'),
+
+ 'src/b/c/d/z': symlinkTo('src', 'dir'),
+ 'src/b/c/d/index.css': css``,
+ 'src/b/c/x': symlinkTo('src', 'dir'),
+ 'src/b/c/y': symlinkTo('src', 'dir'),
+ 'src/b/c/z': symlinkTo('src', 'dir'),
+ 'src/b/x': symlinkTo('src', 'dir'),
+
+ 'src/c/d/e/z': symlinkTo('src', 'dir'),
+ 'src/c/d/x': symlinkTo('src', 'dir'),
+ 'src/c/d/y': symlinkTo('src', 'dir'),
+ 'src/c/d/z': symlinkTo('src', 'dir'),
+ 'src/c/x': symlinkTo('src', 'dir'),
+ },
+ expected: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/a/b/c/index.css',
+ content: [],
+ },
+ ],
+})
+
+testLocator({
+ name: 'File exclusions starting with `/` do not cause traversal to loop forever',
+ fs: {
+ 'index.css': css`
+ @import 'tailwindcss';
+ `,
+ 'vendor/a.css': css`
+ @import 'tailwindcss';
+ `,
+ 'vendor/nested/b.css': css`
+ @import 'tailwindcss';
+ `,
+ 'src/vendor/c.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ settings: {
+ tailwindCSS: {
+ files: {
+ exclude: ['/vendor'],
+ },
+ } as Settings['tailwindCSS'],
+ },
+ expected: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/index.css',
+ content: [],
+ },
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/vendor/c.css',
+ content: [],
+ },
+ ],
+})
+
+testLocator({
+ name: 'Stylesheets that import Tailwind CSS are picked over ones that dont',
+ fs: {
+ 'a/foo.css': css`
+ @import './bar.css';
+ .a {
+ color: red;
+ }
+ `,
+ 'a/bar.css': css`
+ .b {
+ color: red;
+ }
+ `,
+ 'src/app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ expected: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/app.css',
+ content: [],
+ },
+ {
+ version: '4.1.1 (bundled)',
+ config: '/a/foo.css',
+ content: [],
+ },
+ ],
+})
+
+testLocator({
+ name: 'Stylesheets that import Tailwind CSS indirectly are picked over ones that dont',
+ fs: {
+ 'a/foo.css': css`
+ @import './bar.css';
+ .a {
+ color: red;
+ }
+ `,
+ 'a/bar.css': css`
+ .b {
+ color: red;
+ }
+ `,
+ 'src/app.css': css`
+ @import './tw.css';
+ `,
+ 'src/tw.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ expected: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/app.css',
+ content: [],
+ },
+ {
+ version: '4.1.1 (bundled)',
+ config: '/a/foo.css',
+ content: [],
+ },
+ ],
+})
+
+testLocator({
+ name: 'Stylesheets that only have URL imports are not considered roots',
+ fs: {
+ 'a/fonts.css': css`
+ @import 'https://example.com/fonts/some-font.css';
+ .a {
+ color: red;
+ }
+ `,
+ 'src/app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ expected: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/app.css',
+ content: [],
+ },
+ ],
+})
+
// ---
function testLocator({
@@ -361,7 +628,7 @@ function testLocator({
})
}
-async function prepare({ root }: TestUtils) {
+async function prepare({ root }: TestUtils) {
let defaultSettings = {
tailwindCSS: {
files: {
@@ -373,7 +640,7 @@ async function prepare({ root }: TestUtils) {
} as Settings
function adjustPath(filepath: string) {
- filepath = filepath.replace(root, '{URL}')
+ filepath = filepath.replace(normalizePath(root), '{URL}')
if (filepath.startsWith('{URL}/')) {
filepath = filepath.slice(5)
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index 460b14233..bec102900 100644
--- a/packages/tailwindcss-language-server/src/project-locator.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.ts
@@ -1,7 +1,6 @@
-import * as os from 'node:os'
import * as path from 'node:path'
import * as fs from 'node:fs/promises'
-import glob from 'fast-glob'
+import { glob } from 'tinyglobby'
import picomatch from 'picomatch'
import type { Settings } from '@tailwindcss/language-service/src/util/state'
import { CONFIG_GLOB, CSS_GLOB } from './lib/constants'
@@ -23,10 +22,17 @@ export interface ProjectConfig {
folder: string
/** The path to the config file (if it exists) */
- configPath?: string
+ configPath: string
/** The list of documents that are related to this project */
- documentSelector?: DocumentSelector[]
+ documentSelector: DocumentSelector[]
+
+ /**
+ * Additional selectors that should be matched with this project
+ *
+ * These are *never* reset
+ */
+ additionalSelectors: DocumentSelector[]
/** Whether or not this project was explicitly defined by the user */
isUserConfigured: boolean
@@ -66,7 +72,7 @@ export class ProjectLocator {
}
if (projects.length === 1) {
- projects[0].documentSelector.push({
+ projects[0].additionalSelectors.push({
pattern: normalizePath(path.join(this.base, '**')),
priority: DocumentSelectorPriority.ROOT_DIRECTORY,
})
@@ -86,6 +92,10 @@ export class ProjectLocator {
for (let selector of project.documentSelector) {
selector.pattern = normalizeDriveLetter(selector.pattern)
}
+
+ for (let selector of project.additionalSelectors) {
+ selector.pattern = normalizeDriveLetter(selector.pattern)
+ }
}
return projects
@@ -133,6 +143,7 @@ export class ProjectLocator {
priority: DocumentSelectorPriority.USER_CONFIGURED,
pattern: selector,
})),
+ additionalSelectors: [],
tailwind,
}
}
@@ -207,62 +218,7 @@ export class ProjectLocator {
// Look for the package root for the config
config.packageRoot = await getPackageRoot(path.dirname(config.path), this.base)
- let selectors: DocumentSelector[] = []
-
- // selectors:
- // - CSS files
- for (let entry of config.entries) {
- if (entry.type !== 'css') continue
- selectors.push({
- pattern: entry.path,
- priority: DocumentSelectorPriority.CSS_FILE,
- })
- }
-
- // - Config File
- selectors.push({
- pattern: config.path,
- priority: DocumentSelectorPriority.CONFIG_FILE,
- })
-
- // - Content patterns from config
- for await (let selector of contentSelectorsFromConfig(
- config,
- tailwind.features,
- this.resolver,
- )) {
- selectors.push(selector)
- }
-
- // - Directories containing the CSS files
- for (let entry of config.entries) {
- if (entry.type !== 'css') continue
- selectors.push({
- pattern: normalizePath(path.join(path.dirname(entry.path), '**')),
- priority: DocumentSelectorPriority.CSS_DIRECTORY,
- })
- }
-
- // - Directory containing the config
- selectors.push({
- pattern: normalizePath(path.join(path.dirname(config.path), '**')),
- priority: DocumentSelectorPriority.CONFIG_DIRECTORY,
- })
-
- // - Root of package that contains the config
- selectors.push({
- pattern: normalizePath(path.join(config.packageRoot, '**')),
- priority: DocumentSelectorPriority.PACKAGE_DIRECTORY,
- })
-
- // Reorder selectors from most specific to least specific
- selectors.sort((a, z) => a.priority - z.priority)
-
- // Eliminate duplicate selector patterns
- selectors = selectors.filter(
- ({ pattern }, index, documentSelectors) =>
- documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index,
- )
+ let selectors = await calculateDocumentSelectors(config, tailwind.features, this.resolver)
return {
config,
@@ -270,20 +226,32 @@ export class ProjectLocator {
isUserConfigured: false,
configPath: config.path,
documentSelector: selectors,
+ additionalSelectors: [],
tailwind,
}
}
private async findConfigs(): Promise {
+ let ignore = this.settings.tailwindCSS.files.exclude
+
+ // NOTE: This is a temporary workaround for a bug in the `fdir` package used
+ // by `tinyglobby`. It infinite loops when the ignore pattern starts with
+ // a `/`. This should be removed once the bug is fixed.
+ ignore = ignore.map((pattern) => {
+ if (!pattern.startsWith('/')) return pattern
+
+ return pattern.slice(1)
+ })
+
// Look for config files and CSS files
- let files = await glob([`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`], {
+ let files = await glob({
+ patterns: [`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`],
cwd: this.base,
- ignore: this.settings.tailwindCSS.files.exclude,
+ ignore,
onlyFiles: true,
absolute: true,
- suppressErrors: true,
+ followSymbolicLinks: true,
dot: true,
- concurrency: Math.max(os.cpus().length, 1),
})
let realpaths = await Promise.all(files.map((file) => fs.realpath(file)))
@@ -390,6 +358,17 @@ export class ProjectLocator {
// Resolve all @source directives
await Promise.all(imports.map((file) => file.resolveSourceDirectives()))
+ let byRealPath: Record = {}
+ for (let file of imports) byRealPath[file.realpath] = file
+
+ // TODO: Link every entry in the import graph
+ // This breaks things tho
+ // for (let file of imports) file.deps = file.deps.map((dep) => byRealPath[dep.realpath] ?? dep)
+
+ // Check if each file has a direct or indirect tailwind import
+ // TODO: Remove the `byRealPath` argument and use linked deps instead
+ await Promise.all(imports.map((file) => file.resolveImportsTailwind(byRealPath)))
+
// Create a graph of all the CSS files that might (indirectly) use Tailwind
let graph = new Graph()
@@ -427,14 +406,20 @@ export class ProjectLocator {
if (indexPath && themePath) graph.connect(indexPath, themePath)
if (indexPath && utilitiesPath) graph.connect(indexPath, utilitiesPath)
- // Sort the graph so potential "roots" appear first
- // The entire concept of roots needs to be rethought because it's not always
- // clear what the root of a project is. Even when imports are present a file
- // may import a file that is the actual "root" of the project.
let roots = Array.from(graph.roots())
roots.sort((a, b) => {
- return a.meta.root === b.meta.root ? 0 : a.meta.root ? -1 : 1
+ return (
+ // Sort the graph so potential "roots" appear first
+ // The entire concept of roots needs to be rethought because it's not always
+ // clear what the root of a project is. Even when imports are present a file
+ // may import a file that is the actual "root" of the project.
+ Number(b.meta.root) - Number(a.meta.root) ||
+ // Move stylesheets with an explicit tailwindcss import before others
+ Number(b.importsTailwind) - Number(a.importsTailwind) ||
+ // Otherwise stylesheets are kept in discovery order
+ 0
+ )
})
for (let root of roots) {
@@ -535,13 +520,14 @@ function contentSelectorsFromConfig(
entry: ConfigEntry,
features: Feature[],
resolver: Resolver,
+ actualConfig?: any,
): AsyncIterable {
if (entry.type === 'css') {
return contentSelectorsFromCssConfig(entry, resolver)
}
if (entry.type === 'js') {
- return contentSelectorsFromJsConfig(entry, features)
+ return contentSelectorsFromJsConfig(entry, features, actualConfig)
}
}
@@ -576,11 +562,18 @@ async function* contentSelectorsFromJsConfig(
if (typeof item !== 'string') continue
let filepath = item.startsWith('!')
- ? `!${path.resolve(contentBase, item.slice(1))}`
+ ? path.resolve(contentBase, item.slice(1))
: path.resolve(contentBase, item)
+ filepath = normalizePath(filepath)
+ filepath = normalizeDriveLetter(filepath)
+
+ if (item.startsWith('!')) {
+ filepath = `!${filepath}`
+ }
+
yield {
- pattern: normalizePath(filepath),
+ pattern: filepath,
priority: DocumentSelectorPriority.CONTENT_FILE,
}
}
@@ -593,8 +586,11 @@ async function* contentSelectorsFromCssConfig(
let auto = false
for (let item of entry.content) {
if (item.kind === 'file') {
+ let filepath = item.file
+ filepath = normalizePath(filepath)
+ filepath = normalizeDriveLetter(filepath)
yield {
- pattern: normalizePath(item.file),
+ pattern: filepath,
priority: DocumentSelectorPriority.CONTENT_FILE,
}
} else if (item.kind === 'auto' && !auto) {
@@ -623,25 +619,23 @@ async function* contentSelectorsFromCssConfig(
async function* detectContentFiles(
base: string,
inputFile: string,
- sources: string[],
+ sources: SourcePattern[],
resolver: Resolver,
): AsyncIterable {
try {
- let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', path.dirname(base))
+ let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', base)
oxidePath = pathToFileURL(oxidePath).href
- let oxidePackageJsonPath = await resolver.resolveJsId(
- '@tailwindcss/oxide/package.json',
- path.dirname(base),
- )
+ let oxidePackageJsonPath = await resolver.resolveJsId('@tailwindcss/oxide/package.json', base)
let oxidePackageJson = JSON.parse(await fs.readFile(oxidePackageJsonPath, 'utf8'))
let result = await oxide.scan({
oxidePath,
oxideVersion: oxidePackageJson.version,
basePath: base,
- sources: sources.map((pattern) => ({
+ sources: sources.map((source) => ({
base: path.dirname(inputFile),
- pattern,
+ pattern: source.pattern,
+ negated: source.negated,
})),
})
@@ -649,12 +643,16 @@ async function* detectContentFiles(
if (!result) return
for (let file of result.files) {
- yield normalizePath(file)
+ file = normalizePath(file)
+ file = normalizeDriveLetter(file)
+ yield file
}
for (let { base, pattern } of result.globs) {
// Do not normalize the glob itself as it may contain escape sequences
- yield normalizePath(base) + '/' + pattern
+ base = normalizePath(base)
+ base = normalizeDriveLetter(base)
+ yield `${base}/${pattern}`
}
} catch {
//
@@ -675,11 +673,16 @@ type ConfigEntry = {
content: ContentItem[]
}
+export interface SourcePattern {
+ pattern: string
+ negated: boolean
+}
+
class FileEntry {
content: string | null
deps: FileEntry[] = []
realpath: string | null
- sources: string[] = []
+ sources: SourcePattern[] = []
meta: TailwindStylesheet | null = null
constructor(
@@ -752,7 +755,31 @@ class FileEntry {
* Determine which Tailwind versions this file might be using
*/
async resolvePossibleVersions() {
- this.meta = this.content ? analyzeStylesheet(this.content) : null
+ this.meta ??= this.content ? analyzeStylesheet(this.content) : null
+ }
+
+ /**
+ * Determine if this entry or any of its dependencies import a Tailwind CSS
+ * stylesheet
+ */
+ importsTailwind: boolean | null = null
+
+ resolveImportsTailwind(byPath: Record) {
+ // Already calculated so nothing to do
+ if (this.importsTailwind !== null) return
+
+ // We import it directly
+ let self = byPath[this.realpath]
+
+ if (this.meta?.explicitImport || self?.meta?.explicitImport) {
+ this.importsTailwind = true
+ return
+ }
+
+ // Maybe one of our deps does
+ for (let dep of this.deps) dep.resolveImportsTailwind(byPath)
+
+ this.importsTailwind = this.deps.some((dep) => dep.importsTailwind)
}
/**
@@ -780,3 +807,74 @@ function requiresPreprocessor(filepath: string) {
return ext === '.scss' || ext === '.sass' || ext === '.less' || ext === '.styl' || ext === '.pcss'
}
+
+export async function calculateDocumentSelectors(
+ config: ConfigEntry,
+ features: Feature[],
+ resolver: Resolver,
+ actualConfig?: any,
+) {
+ let selectors: DocumentSelector[] = []
+
+ // selectors:
+ // - CSS files
+ for (let entry of config.entries) {
+ if (entry.type !== 'css') continue
+
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(entry.path)),
+ priority: DocumentSelectorPriority.CSS_FILE,
+ })
+ }
+
+ // - Config File
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(config.path)),
+ priority: DocumentSelectorPriority.CONFIG_FILE,
+ })
+
+ // - Content patterns from config
+ for await (let selector of contentSelectorsFromConfig(config, features, resolver, actualConfig)) {
+ selectors.push(selector)
+ }
+
+ // - Directories containing the CSS files
+ for (let entry of config.entries) {
+ if (entry.type !== 'css') continue
+
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(path.join(path.dirname(entry.path), '**'))),
+ priority: DocumentSelectorPriority.CSS_DIRECTORY,
+ })
+ }
+
+ // - Directory containing the config
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(path.join(path.dirname(config.path), '**'))),
+ priority: DocumentSelectorPriority.CONFIG_DIRECTORY,
+ })
+
+ // - Root of package that contains the config
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(path.join(config.packageRoot, '**'))),
+ priority: DocumentSelectorPriority.PACKAGE_DIRECTORY,
+ })
+
+ // Reorder selectors from most specific to least specific
+ selectors.sort((a, z) => a.priority - z.priority)
+
+ // Eliminate duplicate selector patterns
+ selectors = selectors.filter(
+ ({ pattern }, index, documentSelectors) =>
+ documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index,
+ )
+
+ // Move all the negated patterns to the front
+ selectors = selectors.sort((a, z) => {
+ if (a.pattern.startsWith('!') && !z.pattern.startsWith('!')) return -1
+ if (!a.pattern.startsWith('!') && z.pattern.startsWith('!')) return 1
+ return 0
+ })
+
+ return selectors
+}
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index b55ee0781..1038d31bd 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -15,6 +15,8 @@ import type {
Disposable,
DocumentLinkParams,
DocumentLink,
+ CodeLensParams,
+ CodeLens,
} from 'vscode-languageserver/node'
import { FileChangeType } from 'vscode-languageserver/node'
import type { TextDocument } from 'vscode-languageserver-textdocument'
@@ -35,6 +37,7 @@ import stackTrace from 'stack-trace'
import extractClassNames from './lib/extractClassNames'
import { klona } from 'klona/full'
import { doHover } from '@tailwindcss/language-service/src/hoverProvider'
+import { getCodeLens } from '@tailwindcss/language-service/src/codeLensProvider'
import { Resolver } from './resolver'
import {
doComplete,
@@ -77,7 +80,7 @@ import {
normalizeDriveLetter,
} from './utils'
import type { DocumentService } from './documents'
-import type { ProjectConfig } from './project-locator'
+import { calculateDocumentSelectors, type ProjectConfig } from './project-locator'
import { supportedFeatures } from '@tailwindcss/language-service/src/features'
import { loadDesignSystem } from './util/v4'
import { readCssFile } from './util/css'
@@ -110,6 +113,7 @@ export interface ProjectService {
onColorPresentation(params: ColorPresentationParams): Promise
onCodeAction(params: CodeActionParams): Promise
onDocumentLinks(params: DocumentLinkParams): Promise
+ onCodeLens(params: CodeLensParams): Promise
sortClassLists(classLists: string[]): string[]
dependencies(): Iterable
@@ -212,6 +216,7 @@ export async function createProjectService(
let state: State = {
enabled: false,
+ features: [],
completionItemData: {
_projectKey: projectKey,
},
@@ -281,7 +286,9 @@ export async function createProjectService(
)
}
- function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void {
+ async function onFileEvents(
+ changes: Array<{ file: string; type: FileChangeType }>,
+ ): Promise {
let needsInit = false
let needsRebuild = false
@@ -302,16 +309,11 @@ export async function createProjectService(
projectConfig.configPath &&
(isConfigFile || isDependency)
) {
- documentSelector = [
- ...documentSelector.filter(
- ({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE,
- ),
- ...getContentDocumentSelectorFromConfigFile(
- projectConfig.configPath,
- initialTailwindVersion,
- projectConfig.folder,
- ),
- ]
+ documentSelector = await calculateDocumentSelectors(
+ projectConfig.config,
+ state.features,
+ resolver,
+ )
checkOpenDocuments()
}
@@ -462,6 +464,14 @@ export async function createProjectService(
// and this should be determined there and passed in instead
let features = supportedFeatures(tailwindcssVersion, tailwindcss)
log(`supported features: ${JSON.stringify(features)}`)
+ state.features = features
+
+ if (params.initializationOptions?.testMode) {
+ state.features = [
+ ...state.features,
+ ...(params.initializationOptions.additionalFeatures ?? []),
+ ]
+ }
if (!features.includes('css-at-theme')) {
tailwindcss = tailwindcss.default ?? tailwindcss
@@ -688,6 +698,15 @@ export async function createProjectService(
state.v4 = true
state.v4Fallback = true
state.jit = true
+ state.features = features
+
+ if (params.initializationOptions?.testMode) {
+ state.features = [
+ ...state.features,
+ ...(params.initializationOptions.additionalFeatures ?? []),
+ ]
+ }
+
state.modules = {
tailwindcss: { version: tailwindcssVersion, module: tailwindcss },
postcss: { version: null, module: null },
@@ -815,6 +834,7 @@ export async function createProjectService(
)
state.designSystem = designSystem
+ state.blocklist = Array.from(designSystem.invalidCandidates ?? [])
let deps = designSystem.dependencies()
@@ -940,17 +960,12 @@ export async function createProjectService(
/////////////////////
if (!projectConfig.isUserConfigured) {
- documentSelector = [
- ...documentSelector.filter(
- ({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE,
- ),
- ...getContentDocumentSelectorFromConfigFile(
- state.configPath,
- tailwindcss.version,
- projectConfig.folder,
- originalConfig,
- ),
- ]
+ documentSelector = await calculateDocumentSelectors(
+ projectConfig.config,
+ state.features,
+ resolver,
+ originalConfig,
+ )
}
//////////////////////
@@ -960,7 +975,9 @@ export async function createProjectService(
if (typeof state.separator !== 'string') {
state.separator = ':'
}
- state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
+ if (!state.v4) {
+ state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
+ }
delete state.config.blocklist
if (state.v4) {
@@ -1061,6 +1078,11 @@ export async function createProjectService(
refreshDiagnostics()
updateCapabilities()
+
+ let isTestMode = params.initializationOptions?.testMode ?? false
+ if (!isTestMode) return
+
+ connection.sendNotification('@/tailwindCSS/projectReloaded')
}
for (let entry of projectConfig.config.entries) {
@@ -1126,6 +1148,7 @@ export async function createProjectService(
state.designSystem = designSystem
state.classList = classList
state.variants = getVariants(state)
+ state.blocklist = Array.from(designSystem.invalidCandidates ?? [])
let deps = designSystem.dependencies()
@@ -1142,15 +1165,20 @@ export async function createProjectService(
let elapsed = process.hrtime.bigint() - start
console.log(`---- RELOADED IN ${(Number(elapsed) / 1e6).toFixed(2)}ms ----`)
+
+ let isTestMode = params.initializationOptions?.testMode ?? false
+ if (!isTestMode) return
+
+ connection.sendNotification('@/tailwindCSS/projectReloaded')
},
state,
documentSelector() {
- return documentSelector
+ return [...documentSelector, ...projectConfig.additionalSelectors]
},
tryInit,
async dispose() {
- state = { enabled: false }
+ state = { enabled: false, features: [] }
for (let disposable of disposables) {
;(await disposable).dispose()
}
@@ -1159,11 +1187,9 @@ export async function createProjectService(
if (state.enabled) {
refreshDiagnostics()
}
- if (settings.editor?.colorDecorators) {
- updateCapabilities()
- } else {
- connection.sendNotification('@/tailwindCSS/clearColors')
- }
+
+ updateCapabilities()
+ connection.sendNotification('@/tailwindCSS/clearColors')
},
onFileEvents,
async onHover(params: TextDocumentPositionParams): Promise {
@@ -1177,6 +1203,17 @@ export async function createProjectService(
return doHover(state, document, params.position)
}, null)
},
+ async onCodeLens(params: CodeLensParams): Promise {
+ return withFallback(async () => {
+ if (!state.enabled) return null
+ let document = documentService.getDocument(params.textDocument.uri)
+ if (!document) return null
+ let settings = await state.editor.getConfiguration(document.uri)
+ if (!settings.tailwindCSS.codeLens) return null
+ if (await isExcluded(state, document)) return null
+ return getCodeLens(state, document)
+ }, null)
+ },
async onCompletion(params: CompletionParams): Promise {
return withFallback(async () => {
if (!state.enabled) return null
diff --git a/packages/tailwindcss-language-server/src/testing/index.ts b/packages/tailwindcss-language-server/src/testing/index.ts
index 2435ca0f7..f4737e196 100644
--- a/packages/tailwindcss-language-server/src/testing/index.ts
+++ b/packages/tailwindcss-language-server/src/testing/index.ts
@@ -1,41 +1,67 @@
-import { onTestFinished, test, TestOptions } from 'vitest'
+import { onTestFinished, test, TestContext, TestOptions } from 'vitest'
+import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as proc from 'node:child_process'
-import dedent from 'dedent'
+import dedent, { type Dedent } from 'dedent'
-export interface TestUtils {
+export interface TestUtils> {
/** The "cwd" for this test */
root: string
+
+ /**
+ * The input for this test — taken from the `inputs` in the test config
+ *
+ * @see {TestConfig}
+ */
+ input?: TestInput
+}
+
+export interface StorageSymlink {
+ [IS_A_SYMLINK]: true
+ filepath: string
+ type: 'file' | 'dir' | undefined
}
export interface Storage {
/** A list of files and their content */
- [filePath: string]: string | Uint8Array
+ [filePath: string]: string | Uint8Array | StorageSymlink
}
-export interface TestConfig {
+export interface TestConfig> {
name: string
+ inputs?: TestInput[]
+
+ skipNPM?: boolean
fs?: Storage
- prepare?(utils: TestUtils): Promise
- handle(utils: TestUtils & Extras): void | Promise
+ debug?: boolean
+ prepare?(utils: TestUtils): Promise
+ handle(utils: TestUtils & Extras): void | Promise
options?: TestOptions
}
-export function defineTest(config: TestConfig) {
- return test(config.name, config.options ?? {}, async ({ expect }) => {
- let utils = await setup(config)
+export function defineTest(config: TestConfig) {
+ async function runTest(ctx: TestContext, input?: I) {
+ let utils = await setup(config, input)
let extras = await config.prepare?.(utils)
await config.handle({
...utils,
...extras,
})
- })
+ }
+
+ if (config.inputs) {
+ return test.for(config.inputs ?? [])(config.name, config.options ?? {}, (input, ctx) =>
+ runTest(ctx, input),
+ )
+ }
+
+ return test(config.name, config.options ?? {}, runTest)
}
-async function setup(config: TestConfig): Promise {
+async function setup(config: TestConfig, input: I): Promise> {
let randomId = Math.random().toString(36).substring(7)
let baseDir = path.resolve(process.cwd(), `../../.debug/${randomId}`)
@@ -45,17 +71,28 @@ async function setup(config: TestConfig): Promise {
if (config.fs) {
await prepareFileSystem(baseDir, config.fs)
- await installDependencies(baseDir, config.fs)
+
+ if (!config.skipNPM) {
+ await installDependencies(baseDir, config.fs)
+ }
}
- onTestFinished(async (result) => {
+ onTestFinished(async (ctx) => {
// Once done, move all the files to a new location
- await fs.rename(baseDir, doneDir)
+ try {
+ await fs.rename(baseDir, doneDir)
+ } catch {
+ // If it fails it doesn't really matter. It only fails on Windows and then
+ // only randomly so whatever
+ console.error('Failed to move test files to done directory')
+ }
- if (result.state === 'fail') return
+ if (ctx.task.result?.state === 'fail') return
if (path.sep === '\\') return
+ if (config.debug) return
+
// Remove the directory on *nix systems. Recursive removal on Windows will
// randomly fail b/c its slow and buggy.
await fs.rm(doneDir, { recursive: true })
@@ -63,6 +100,16 @@ async function setup(config: TestConfig): Promise {
return {
root: baseDir,
+ input,
+ }
+}
+
+const IS_A_SYMLINK = Symbol('is-a-symlink')
+export function symlinkTo(filepath: string, type?: 'file' | 'dir'): StorageSymlink {
+ return {
+ [IS_A_SYMLINK]: true as const,
+ filepath,
+ type,
}
}
@@ -74,6 +121,20 @@ async function prepareFileSystem(base: string, storage: Storage) {
for (let [filepath, content] of Object.entries(storage)) {
let fullPath = path.resolve(base, filepath)
await fs.mkdir(path.dirname(fullPath), { recursive: true })
+
+ if (typeof content === 'object' && IS_A_SYMLINK in content) {
+ let target = path.resolve(base, content.filepath)
+
+ let type: string = content.type
+
+ if (os.platform() === 'win32' && content.type === 'dir') {
+ type = 'junction'
+ }
+
+ await fs.symlink(target, fullPath, type)
+ continue
+ }
+
await fs.writeFile(fullPath, content, { encoding: 'utf-8' })
}
}
@@ -103,8 +164,8 @@ async function installDependenciesIn(dir: string) {
})
}
-export const css = dedent
-export const scss = dedent
-export const html = dedent
-export const js = dedent
-export const json = dedent
+export const css: Dedent = dedent
+export const scss: Dedent = dedent
+export const html: Dedent = dedent
+export const js: Dedent = dedent
+export const json: Dedent = dedent
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index efb12a34f..07d3956a9 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -19,6 +19,10 @@ import type {
DocumentLink,
InitializeResult,
WorkspaceFolder,
+ CodeLensParams,
+ CodeLens,
+ ServerCapabilities,
+ ClientCapabilities,
} from 'vscode-languageserver/node'
import {
CompletionRequest,
@@ -30,10 +34,14 @@ import {
FileChangeType,
DocumentLinkRequest,
TextDocumentSyncKind,
+ CodeLensRequest,
+ DidChangeConfigurationNotification,
} from 'vscode-languageserver/node'
import { URI } from 'vscode-uri'
import normalizePath from 'normalize-path'
import * as path from 'node:path'
+import * as fs from 'node:fs/promises'
+import * as fsSync from 'node:fs'
import type * as chokidar from 'chokidar'
import picomatch from 'picomatch'
import * as parcel from './watcher/index.js'
@@ -47,7 +55,8 @@ import { readCssFile } from './util/css'
import { ProjectLocator, type ProjectConfig } from './project-locator'
import type { TailwindCssSettings } from '@tailwindcss/language-service/src/util/state'
import { createResolver, Resolver } from './resolver'
-import { retry } from './util/retry'
+import { analyzeStylesheet } from './version-guesser.js'
+import { createPathMatcher, PathMatcher } from './matching.js'
const TRIGGER_CHARACTERS = [
// class attributes
@@ -96,12 +105,14 @@ export class TW {
private watched: string[] = []
private settingsCache: SettingsCache
+ private pathMatcher: PathMatcher
constructor(private connection: Connection) {
this.documentService = new DocumentService(this.connection)
this.projects = new Map()
this.projectCounter = 0
this.settingsCache = createSettingsCache(connection)
+ this.pathMatcher = createPathMatcher()
}
async init(): Promise {
@@ -111,38 +122,75 @@ export class TW {
await this.initPromise
}
+ private validateFolderUri(uri: URI): boolean {
+ if (uri.scheme !== 'file') {
+ console.warn(
+ `The workspace folder [${uri.toString()}] will be ignored: it does not use the file scheme.`,
+ )
+ return false
+ }
+
+ if (uri.fsPath === '/' || uri.fsPath === '\\\\') {
+ console.warn(
+ `The workspace folder [${uri.toString()}] will be ignored: it starts at the root of the filesystem which is most likely an error.`,
+ )
+ return false
+ }
+
+ return true
+ }
+
private getWorkspaceFolders(): WorkspaceFolder[] {
if (this.initializeParams.workspaceFolders?.length) {
- return this.initializeParams.workspaceFolders.map((folder) => ({
- uri: URI.parse(folder.uri).fsPath,
- name: folder.name,
- }))
+ return this.initializeParams.workspaceFolders.flatMap((folder) => {
+ let uri = URI.parse(folder.uri)
+
+ if (!this.validateFolderUri(uri)) return []
+
+ return [
+ {
+ uri: uri.fsPath,
+ name: folder.name,
+ },
+ ]
+ })
}
if (this.initializeParams.rootUri) {
+ let uri = URI.parse(this.initializeParams.rootUri)
+
+ if (!this.validateFolderUri(uri)) return []
+
return [
{
- uri: URI.parse(this.initializeParams.rootUri).fsPath,
+ uri: uri.fsPath,
name: 'Root',
},
]
}
if (this.initializeParams.rootPath) {
+ let uri = URI.file(this.initializeParams.rootPath)
+
+ if (!this.validateFolderUri(uri)) return []
+
return [
{
- uri: URI.file(this.initializeParams.rootPath).fsPath,
+ uri: uri.fsPath,
name: 'Root',
},
]
}
+ console.warn(`No workspace folders detected`)
+
return []
}
private async _init(): Promise {
clearRequireCache()
+ this.pathMatcher.clear()
let folders = this.getWorkspaceFolders().map((folder) => normalizePath(folder.uri))
if (folders.length === 0) {
@@ -166,10 +214,35 @@ export class TW {
}
}
+ if (results.some((result) => result.status === 'fulfilled')) {
+ await this.updateCommonCapabilities()
+ }
+
await this.listenForEvents()
}
private async _initFolder(baseUri: URI): Promise {
+ // NOTE: We do this check because on Linux when using an LSP client that does
+ // not support watching files on behalf of the server, we'll use Parcel
+ // Watcher (if possible). If we start the watcher with a non-existent or
+ // inaccessible directory, it will throw an error with a very unhelpful
+ // message: "Bad file descriptor"
+ //
+ // The best thing we can do is an initial check for access to the directory
+ // and log a more helpful error message if it fails.
+ let base = baseUri.fsPath
+
+ try {
+ // TODO: Change this to fs.constants after the node version bump
+ await fs.access(base, fsSync.constants.F_OK | fsSync.constants.R_OK)
+ } catch (err) {
+ console.error(
+ `Unable to access the workspace folder [${base}]. This may happen if the directory does not exist or the current user does not have the necessary permissions to access it.`,
+ )
+ console.error(err)
+ return
+ }
+
let initUserLanguages = this.initializeParams.initializationOptions?.userLanguages ?? {}
if (Object.keys(initUserLanguages).length > 0) {
@@ -178,7 +251,6 @@ export class TW {
)
}
- let base = baseUri.fsPath
let workspaceFolders: Array = []
let globalSettings = await this.settingsCache.get()
let ignore = globalSettings.tailwindCSS.files.exclude
@@ -279,7 +351,7 @@ export class TW {
return {
folder: workspace.folder,
config: workspace.config.path,
- selectors: workspace.documentSelector,
+ selectors: [...workspace.documentSelector, ...workspace.additionalSelectors],
user: workspace.isUserConfigured,
tailwind: workspace.tailwind,
}
@@ -293,6 +365,7 @@ export class TW {
let needsRestart = false
let needsSoftRestart = false
+ // TODO: This should use the server-level path matcher
let isPackageMatcher = picomatch(`**/${PACKAGE_LOCK_GLOB}`, { dot: true })
let isCssMatcher = picomatch(`**/${CSS_GLOB}`, { dot: true })
let isConfigMatcher = picomatch(`**/${CONFIG_GLOB}`, { dot: true })
@@ -307,6 +380,7 @@ export class TW {
normalizedFilename = normalizeDriveLetter(normalizedFilename)
for (let ignorePattern of ignore) {
+ // TODO: This should use the server-level path matcher
let isIgnored = picomatch(ignorePattern, { dot: true })
if (isIgnored(normalizedFilename)) {
@@ -358,6 +432,13 @@ export class TW {
for (let [, project] of this.projects) {
if (!project.state.v4) continue
+ if (
+ change.type === FileChangeType.Deleted &&
+ changeAffectsFile(normalizedFilename, [project.projectConfig.configPath])
+ ) {
+ continue
+ }
+
if (!changeAffectsFile(normalizedFilename, project.dependencies())) continue
needsSoftRestart = true
@@ -381,6 +462,31 @@ export class TW {
needsRestart = true
break
}
+
+ //
+ else {
+ // If the main CSS file in a project is deleted and then re-created
+ // the server won't restart because the project is gone by now and
+ // there's no concept of a "config file" for us to compare with
+ //
+ // So we'll check if the stylesheet could *potentially* create
+ // a new project but we'll only do so if no projects were found
+ //
+ // If we did this all the time we'd potentially restart the server
+ // unncessarily a lot while the user is editing their stylesheets
+ if (this.projects.size > 0) continue
+
+ let content = await readCssFile(change.file)
+ if (!content) continue
+
+ let stylesheet = analyzeStylesheet(content)
+ if (!stylesheet.root) continue
+
+ if (!stylesheet.versions.includes('4')) continue
+
+ needsRestart = true
+ break
+ }
}
let isConfigFile = isConfigMatcher(normalizedFilename)
@@ -465,6 +571,10 @@ export class TW {
}
}
} else if (parcel.getBinding()) {
+ console.log(
+ '[Global] Your LSP client does not support watching files on behalf of the server',
+ )
+ console.log('[Global] Using bundled file watcher: @parcel/watcher')
let typeMap = {
create: FileChangeType.Created,
update: FileChangeType.Changed,
@@ -491,6 +601,10 @@ export class TW {
},
})
} else {
+ console.log(
+ '[Global] Your LSP client does not support watching files on behalf of the server',
+ )
+ console.log('[Global] Using bundled file watcher: chokidar')
let watch: typeof chokidar.watch = require('chokidar').watch
let chokidarWatcher = watch(
[`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`, `**/${TSCONFIG_GLOB}`],
@@ -576,8 +690,6 @@ export class TW {
console.log(`[Global] Initialized ${enabledProjectCount} projects`)
- this.setupLSPHandlers()
-
this.disposables.push(
this.connection.onDidChangeConfiguration(async ({ settings }) => {
let previousExclude = globalSettings.tailwindCSS.files.exclude
@@ -699,7 +811,7 @@ export class TW {
this.connection,
params,
this.documentService,
- () => this.updateCapabilities(),
+ () => this.updateProjectCapabilities(),
() => {
for (let document of this.documentService.getAllDocuments()) {
let project = this.getProject(document)
@@ -746,9 +858,7 @@ export class TW {
}
setupLSPHandlers() {
- if (this.lspHandlersAdded) {
- return
- }
+ if (this.lspHandlersAdded) return
this.lspHandlersAdded = true
this.connection.onHover(this.onHover.bind(this))
@@ -757,6 +867,7 @@ export class TW {
this.connection.onDocumentColor(this.onDocumentColor.bind(this))
this.connection.onColorPresentation(this.onColorPresentation.bind(this))
this.connection.onCodeAction(this.onCodeAction.bind(this))
+ this.connection.onCodeLens(this.onCodeLens.bind(this))
this.connection.onDocumentLinks(this.onDocumentLinks.bind(this))
this.connection.onRequest(this.onRequest.bind(this))
}
@@ -793,37 +904,106 @@ export class TW {
}
}
- private updateCapabilities() {
- if (!supportsDynamicRegistration(this.initializeParams)) {
- return
+ // Common capabilities are always supported by the language server and do not
+ // require any project-specific information to know how to configure them.
+ //
+ // These capabilities will stay valid until/unless the server has to restart
+ // in which case they'll be unregistered and then re-registered once project
+ // discovery has completed
+ private commonRegistrations: BulkUnregistration | undefined
+ private async updateCommonCapabilities() {
+ let capabilities = BulkRegistration.create()
+
+ let client = this.initializeParams.capabilities
+
+ if (client.textDocument?.hover?.dynamicRegistration) {
+ capabilities.add(HoverRequest.type, { documentSelector: null })
}
- if (this.registrations) {
- this.registrations.then((r) => r.dispose())
+ if (client.textDocument?.colorProvider?.dynamicRegistration) {
+ capabilities.add(DocumentColorRequest.type, { documentSelector: null })
}
- let projects = Array.from(this.projects.values())
+ if (client.textDocument?.codeAction?.dynamicRegistration) {
+ capabilities.add(CodeActionRequest.type, { documentSelector: null })
+ }
- let capabilities = BulkRegistration.create()
+ if (client.textDocument?.codeLens?.dynamicRegistration) {
+ capabilities.add(CodeLensRequest.type, { documentSelector: null })
+ }
+
+ if (client.textDocument?.documentLink?.dynamicRegistration) {
+ capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
+ }
+
+ if (client.workspace?.didChangeConfiguration?.dynamicRegistration) {
+ capabilities.add(DidChangeConfigurationNotification.type, undefined)
+ }
+
+ this.commonRegistrations?.dispose()
+ this.commonRegistrations = await this.connection.client.register(capabilities)
+ }
- capabilities.add(HoverRequest.type, { documentSelector: null })
- capabilities.add(DocumentColorRequest.type, { documentSelector: null })
- capabilities.add(CodeActionRequest.type, { documentSelector: null })
- capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
+ // These capabilities depend on the projects we've found to appropriately
+ // configure them. This may mean collecting information from all discovered
+ // projects to determine what we can do and how
+ private updateProjectCapabilities() {
+ this.updateTriggerCharacters()
+ }
+
+ private lastTriggerCharacters: Set | undefined
+ private completionRegistration: Promise | undefined
+ private async updateTriggerCharacters() {
+ // If the client does not suppory dynamic registration of completions then
+ // we cannot update the set of trigger characters
+ let client = this.initializeParams.capabilities
+ if (!client.textDocument?.completion?.dynamicRegistration) return
+
+ // The new set of trigger characters is all the static ones plus
+ // any characters from any separator in v3 config
+ let chars = new Set(TRIGGER_CHARACTERS)
+
+ for (let project of this.projects.values()) {
+ let sep = project.state.separator
+ if (typeof sep !== 'string') continue
+
+ sep = sep.slice(-1)
+ if (!sep) continue
+
+ chars.add(sep)
+ }
- capabilities.add(CompletionRequest.type, {
+ // If the trigger characters haven't changed then we don't need to do anything
+ if (
+ this.completionRegistration &&
+ equal(Array.from(chars), Array.from(this.lastTriggerCharacters ?? []))
+ ) {
+ return
+ }
+
+ this.lastTriggerCharacters = chars
+
+ let current = this.completionRegistration
+ this.completionRegistration = this.connection.client.register(CompletionRequest.type, {
documentSelector: null,
resolveProvider: true,
- triggerCharacters: [
- ...TRIGGER_CHARACTERS,
- ...projects
- .map((project) => project.state.separator)
- .filter((sep) => typeof sep === 'string')
- .map((sep) => sep.slice(-1)),
- ].filter(Boolean),
+ triggerCharacters: Array.from(chars),
})
- this.registrations = this.connection.client.register(capabilities)
+ // NOTE:
+ // This weird setup works around a race condition where multiple projects
+ // with different separators update their capabilities at the same time. It
+ // is extremely unlikely but it could cause `CompletionRequest` to be
+ // registered more than once with the LSP client.
+ //
+ // We store the promises meaning everything up to this point is synchronous
+ // so it should be fine but really the proper fix here is to:
+ // - Refactor workspace folder initialization so discovery, initialization,
+ // file events, config watchers, etc… are all shared.
+ // - Remove the need for the "restart" concept in the server for as much as
+ // possible. Each project should be capable of reloading its modules.
+ await current?.then((r) => r.dispose())
+ await this.completionRegistration
}
private getProject(document: TextDocumentIdentifier): ProjectService {
@@ -832,6 +1012,15 @@ export class TW {
let matchedPriority: number = Infinity
let uri = URI.parse(document.uri)
+
+ if (uri.scheme !== 'file') {
+ console.debug(`Cannot get project for a non-file document. They are unsupported.`, {
+ uri: uri.toString(),
+ })
+
+ return null
+ }
+
let fsPath = uri.fsPath
let normalPath = uri.path
@@ -846,44 +1035,20 @@ export class TW {
continue
}
- let documentSelector = project
- .documentSelector()
- .concat()
- // move all the negated patterns to the front
- .sort((a, z) => {
- if (a.pattern.startsWith('!') && !z.pattern.startsWith('!')) {
- return -1
- }
- if (!a.pattern.startsWith('!') && z.pattern.startsWith('!')) {
- return 1
- }
- return 0
- })
-
- for (let selector of documentSelector) {
- let pattern = selector.pattern.replace(/[\[\]{}()]/g, (m) => `\\${m}`)
-
- if (pattern.startsWith('!')) {
- if (picomatch(pattern.slice(1), { dot: true })(fsPath)) {
- break
- }
-
- if (picomatch(pattern.slice(1), { dot: true })(normalPath)) {
- break
- }
- }
-
- if (picomatch(pattern, { dot: true })(fsPath) && selector.priority < matchedPriority) {
- matchedProject = project
- matchedPriority = selector.priority
-
- continue
+ for (let selector of project.documentSelector()) {
+ if (
+ selector.pattern.startsWith('!') &&
+ this.pathMatcher.anyMatches(selector.pattern.slice(1), [fsPath, normalPath])
+ ) {
+ break
}
- if (picomatch(pattern, { dot: true })(normalPath) && selector.priority < matchedPriority) {
+ if (
+ selector.priority < matchedPriority &&
+ this.pathMatcher.anyMatches(selector.pattern, [fsPath, normalPath])
+ ) {
matchedProject = project
matchedPriority = selector.priority
-
continue
}
}
@@ -931,6 +1096,11 @@ export class TW {
return this.getProject(params.textDocument)?.onCodeAction(params) ?? null
}
+ async onCodeLens(params: CodeLensParams): Promise {
+ await this.init()
+ return this.getProject(params.textDocument)?.onCodeLens(params) ?? null
+ }
+
async onDocumentLinks(params: DocumentLinkParams): Promise {
await this.init()
return this.getProject(params.textDocument)?.onDocumentLinks(params) ?? null
@@ -940,44 +1110,58 @@ export class TW {
this.connection.onInitialize(async (params: InitializeParams): Promise => {
this.initializeParams = params
- if (supportsDynamicRegistration(params)) {
- return {
- capabilities: {
- textDocumentSync: TextDocumentSyncKind.Full,
- workspace: {
- workspaceFolders: {
- changeNotifications: true,
- },
- },
- },
- }
- }
-
this.setupLSPHandlers()
return {
- capabilities: {
- textDocumentSync: TextDocumentSyncKind.Full,
- hoverProvider: true,
- colorProvider: true,
- codeActionProvider: true,
- documentLinkProvider: {},
- completionProvider: {
- resolveProvider: true,
- triggerCharacters: [...TRIGGER_CHARACTERS, ':'],
- },
- workspace: {
- workspaceFolders: {
- changeNotifications: true,
- },
- },
- },
+ capabilities: this.computeServerCapabilities(params.capabilities),
}
})
this.connection.onInitialized(() => this.init())
}
+ computeServerCapabilities(client: ClientCapabilities) {
+ let capabilities: ServerCapabilities = {
+ textDocumentSync: TextDocumentSyncKind.Full,
+ workspace: {
+ workspaceFolders: {
+ changeNotifications: true,
+ },
+ },
+ }
+
+ if (!client.textDocument?.hover?.dynamicRegistration) {
+ capabilities.hoverProvider = true
+ }
+
+ if (!client.textDocument?.colorProvider?.dynamicRegistration) {
+ capabilities.colorProvider = true
+ }
+
+ if (!client.textDocument?.codeAction?.dynamicRegistration) {
+ capabilities.codeActionProvider = true
+ }
+
+ if (!client.textDocument?.codeLens?.dynamicRegistration) {
+ capabilities.codeLensProvider = {
+ resolveProvider: false,
+ }
+ }
+
+ if (!client.textDocument?.completion?.dynamicRegistration) {
+ capabilities.completionProvider = {
+ resolveProvider: true,
+ triggerCharacters: [...TRIGGER_CHARACTERS, ':'],
+ }
+ }
+
+ if (!client.textDocument?.documentLink?.dynamicRegistration) {
+ capabilities.documentLinkProvider = {}
+ }
+
+ return capabilities
+ }
+
listen() {
this.connection.listen()
}
@@ -991,10 +1175,12 @@ export class TW {
this.refreshDiagnostics()
- if (this.registrations) {
- this.registrations.then((r) => r.dispose())
- this.registrations = undefined
- }
+ this.commonRegistrations?.dispose()
+ this.commonRegistrations = undefined
+
+ this.lastTriggerCharacters?.clear()
+ this.completionRegistration?.then((r) => r.dispose())
+ this.completionRegistration = undefined
this.disposables.forEach((d) => d.dispose())
this.disposables.length = 0
@@ -1002,11 +1188,17 @@ export class TW {
this.watched.length = 0
}
- restart(): void {
+ async restart(): Promise {
+ let isTestMode = this.initializeParams.initializationOptions?.testMode ?? false
+
console.log('----------\nRESTARTING\n----------')
this.dispose()
this.initPromise = undefined
- this.init()
+ await this.init()
+
+ if (isTestMode) {
+ this.connection.sendNotification('@/tailwindCSS/serverRestarted')
+ }
}
async softRestart(): Promise {
@@ -1021,13 +1213,3 @@ export class TW {
}
}
}
-
-function supportsDynamicRegistration(params: InitializeParams): boolean {
- return (
- params.capabilities.textDocument.hover?.dynamicRegistration &&
- params.capabilities.textDocument.colorProvider?.dynamicRegistration &&
- params.capabilities.textDocument.codeAction?.dynamicRegistration &&
- params.capabilities.textDocument.completion?.dynamicRegistration &&
- params.capabilities.textDocument.documentLink?.dynamicRegistration
- )
-}
diff --git a/packages/tailwindcss-language-server/src/util/isExcluded.ts b/packages/tailwindcss-language-server/src/util/isExcluded.ts
index beb4115a8..635473049 100644
--- a/packages/tailwindcss-language-server/src/util/isExcluded.ts
+++ b/packages/tailwindcss-language-server/src/util/isExcluded.ts
@@ -3,6 +3,7 @@ import * as path from 'node:path'
import type { TextDocument } from 'vscode-languageserver-textdocument'
import type { State } from '@tailwindcss/language-service/src/util/state'
import { getFileFsPath } from './uri'
+import { normalizePath, normalizeDriveLetter } from '../utils'
export default async function isExcluded(
state: State,
@@ -11,8 +12,16 @@ export default async function isExcluded(
): Promise {
let settings = await state.editor.getConfiguration(document.uri)
+ file = normalizePath(file)
+ file = normalizeDriveLetter(file)
+
for (let pattern of settings.tailwindCSS.files.exclude) {
- if (picomatch(path.join(state.editor.folder, pattern))(file)) {
+ pattern = path.join(state.editor.folder, pattern)
+ pattern = normalizePath(pattern)
+ pattern = normalizeDriveLetter(pattern)
+
+ // TODO: This should use the server-level path matcher
+ if (picomatch(pattern)(file)) {
return true
}
}
diff --git a/packages/tailwindcss-language-server/src/util/resolveFrom.ts b/packages/tailwindcss-language-server/src/util/resolveFrom.ts
index 2b62bbeb2..c4ef1a15c 100644
--- a/packages/tailwindcss-language-server/src/util/resolveFrom.ts
+++ b/packages/tailwindcss-language-server/src/util/resolveFrom.ts
@@ -56,5 +56,17 @@ export function resolveFrom(from?: string, id?: string): string {
let result = resolver.resolveSync({}, from, id)
if (result === false) throw Error()
+
+ // The `enhanced-resolve` package supports resolving paths with fragment
+ // identifiers. For example, it can resolve `foo/bar#baz` to `foo/bar.js`
+ // However, it's also possible that a path contains a `#` character as part
+ // of the path itself. For example, `foo#bar` might point to a file named
+ // `foo#bar.js`. The resolver distinguishes between these two cases by
+ // escaping the `#` character with a NUL byte when it's part of the path.
+ //
+ // Since the real path doesn't actually contain NUL bytes, we need to remove
+ // them to get the correct path otherwise readFileSync will throw an error.
+ result = result.replace(/\0(.)/g, '$1')
+
return result
}
diff --git a/packages/tailwindcss-language-server/src/util/v4/design-system.ts b/packages/tailwindcss-language-server/src/util/v4/design-system.ts
index f32305ce7..f312b95c0 100644
--- a/packages/tailwindcss-language-server/src/util/v4/design-system.ts
+++ b/packages/tailwindcss-language-server/src/util/v4/design-system.ts
@@ -9,6 +9,7 @@ import { Resolver } from '../../resolver'
import { pathToFileURL } from '../../utils'
import type { Jiti } from 'jiti/lib/types'
import { assets } from './assets'
+import { plugins } from './plugins'
const HAS_V4_IMPORT = /@import\s*(?:'tailwindcss'|"tailwindcss")/
const HAS_V4_THEME = /@theme\s*\{/
@@ -58,6 +59,28 @@ function createLoader({
return await jiti.import(url.href, { default: true })
} catch (err) {
+ // If the request was to load a first-party plugin and we can't resolve it
+ // locally, then fall back to the built-in plugins that we know about.
+ if (resourceType === 'plugin' && id in plugins) {
+ console.log('Loading bundled plugin for: ', id)
+ return await plugins[id]()
+ }
+
+ // This checks for an error thrown by enhanced-resolve
+ if (err && typeof err.details === 'string') {
+ let details: string = err.details
+ let pattern = /^resolve '([^']+)'/
+ let match = details.match(pattern)
+ if (match) {
+ let [_, importee] = match
+ if (importee in plugins) {
+ console.log(
+ `[error] Cannot load '${id}' plugins inside configs or plugins is not currently supported`,
+ )
+ }
+ }
+ }
+
return onError(id, err, resourceType)
}
}
@@ -196,6 +219,14 @@ export async function loadDesignSystem(
Object.assign(design, {
dependencies: () => dependencies,
+ // TODOs:
+ //
+ // 1. Remove PostCSS parsing — its roughly 60% of the processing time
+ // ex: compiling 19k classes take 650ms and 400ms of that is PostCSS
+ //
+ // - Replace `candidatesToCss` with a `candidatesToAst` API
+ // First step would be to convert to a PostCSS AST by transforming the nodes directly
+ // Then it would be to drop the PostCSS AST representation entirely in all v4 code paths
compile(classes: string[]): (postcss.Root | null)[] {
let css = design.candidatesToCss(classes)
let errors: any[] = []
diff --git a/packages/tailwindcss-language-server/src/util/v4/plugins.ts b/packages/tailwindcss-language-server/src/util/v4/plugins.ts
new file mode 100644
index 000000000..16efc4a5b
--- /dev/null
+++ b/packages/tailwindcss-language-server/src/util/v4/plugins.ts
@@ -0,0 +1,5 @@
+export const plugins = {
+ '@tailwindcss/forms': () => import('@tailwindcss/forms').then((m) => m.default),
+ '@tailwindcss/aspect-ratio': () => import('@tailwindcss/aspect-ratio').then((m) => m.default),
+ '@tailwindcss/typography': () => import('@tailwindcss/typography').then((m) => m.default),
+}
diff --git a/packages/tailwindcss-language-server/src/version-guesser.ts b/packages/tailwindcss-language-server/src/version-guesser.ts
index a151dea77..51b7782bd 100644
--- a/packages/tailwindcss-language-server/src/version-guesser.ts
+++ b/packages/tailwindcss-language-server/src/version-guesser.ts
@@ -10,6 +10,11 @@ export interface TailwindStylesheet {
* The likely Tailwind version used by the given file
*/
versions: TailwindVersion[]
+
+ /**
+ * Whether or not this stylesheet explicitly imports Tailwind CSS
+ */
+ explicitImport: boolean
}
// It's likely this is a v4 file if it has a v4 import:
@@ -44,7 +49,8 @@ const HAS_TAILWIND = /@tailwind\s*[^;]+;/
const HAS_COMMON_DIRECTIVE = /@(config|apply)\s*[^;{]+[;{]/
// If it's got imports at all it could be either
-const HAS_IMPORT = /@import\s*['"]/
+// Note: We only care about non-url imports
+const HAS_NON_URL_IMPORT = /@import\s*['"](?!([a-z]+:|\/\/))/
/**
* Determine the likely Tailwind version used by the given file
@@ -60,6 +66,7 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
return {
root: true,
versions: ['4'],
+ explicitImport: true,
}
}
@@ -71,6 +78,7 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
return {
root: true,
versions: ['4'],
+ explicitImport: false,
}
}
@@ -78,6 +86,7 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
// This file MUST be imported by another file to be a valid root
root: false,
versions: ['4'],
+ explicitImport: false,
}
}
@@ -87,6 +96,7 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
// This file MUST be imported by another file to be a valid root
root: false,
versions: ['4'],
+ explicitImport: false,
}
}
@@ -96,6 +106,7 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
// Roots are only a valid concept in v4
root: false,
versions: ['3'],
+ explicitImport: false,
}
}
@@ -104,6 +115,7 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
return {
root: true,
versions: ['4', '3'],
+ explicitImport: false,
}
}
@@ -112,14 +124,16 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
return {
root: false,
versions: ['4', '3'],
+ explicitImport: false,
}
}
// Files that import other files could be either and are potentially roots
- if (HAS_IMPORT.test(content)) {
+ if (HAS_NON_URL_IMPORT.test(content)) {
return {
root: true,
versions: ['4', '3'],
+ explicitImport: false,
}
}
@@ -127,5 +141,6 @@ export function analyzeStylesheet(content: string): TailwindStylesheet {
return {
root: false,
versions: [],
+ explicitImport: false,
}
}
diff --git a/packages/tailwindcss-language-server/src/watcher/index.js b/packages/tailwindcss-language-server/src/watcher/index.js
index ecf582e6a..46cec8e84 100644
--- a/packages/tailwindcss-language-server/src/watcher/index.js
+++ b/packages/tailwindcss-language-server/src/watcher/index.js
@@ -13,21 +13,24 @@ const uv = (process.versions.uv || '').split('.')[0]
const prebuilds = {
'darwin-arm64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/darwin-arm64/node.napi.glibc.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-darwin-arm64/watcher.node'),
},
'darwin-x64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/darwin-x64/node.napi.glibc.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-darwin-x64/watcher.node'),
},
'linux-x64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/linux-x64/node.napi.glibc.node'),
- 'node.napi.musl.node': () => require('@parcel/watcher/prebuilds/linux-x64/node.napi.musl.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-linux-x64-glibc/watcher.node'),
+ 'node.napi.musl.node': () => require('@parcel/watcher-linux-x64-musl/watcher.node'),
+ },
+ 'linux-arm64': {
+ 'node.napi.glibc.node': () => require('@parcel/watcher-linux-arm64-glibc/watcher.node'),
+ 'node.napi.musl.node': () => require('@parcel/watcher-linux-arm64-musl/watcher.node'),
},
'win32-x64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/win32-x64/node.napi.glibc.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-win32-x64/watcher.node'),
+ },
+ 'win32-arm64': {
+ 'node.napi.glibc.node': () => require('@parcel/watcher-win32-arm64/watcher.node'),
},
}
diff --git a/packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts b/packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts
new file mode 100644
index 000000000..55f6f22e6
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts
@@ -0,0 +1,101 @@
+import { expect } from 'vitest'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
+
+defineTest({
+ name: 'Code lenses are displayed for @source inline(…)',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ features: ['source-inline'],
+ }),
+ }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'css',
+ text: css`
+ @import 'tailwindcss';
+ @source inline("{,{hover,focus}:}{flex,underline,bg-red-{50,{100..900.100},950}}");
+ `,
+ })
+
+ let lenses = await document.codeLenses()
+
+ expect(lenses).toEqual([
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 81 },
+ },
+ command: {
+ title: 'Generates 15 classes',
+ command: '',
+ },
+ },
+ ])
+ },
+})
+
+defineTest({
+ name: 'The user is warned when @source inline(…) generates a lerge amount of CSS',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ features: ['source-inline'],
+ }),
+ }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'css',
+ text: css`
+ @import 'tailwindcss';
+ @source inline("{,dark:}{,{sm,md,lg,xl,2xl}:}{,{hover,focus,active}:}{flex,underline,bg-red-{50,{100..900.100},950}{,/{0..100}}}");
+ `,
+ })
+
+ let lenses = await document.codeLenses()
+
+ expect(lenses).toEqual([
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 129 },
+ },
+ command: {
+ title: 'Generates 14,784 classes',
+ command: '',
+ },
+ },
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 129 },
+ },
+ command: {
+ title: 'At least 3MB of CSS',
+ command: '',
+ },
+ },
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 129 },
+ },
+ command: {
+ title: 'This may slow down your bundler/browser',
+ command: '',
+ },
+ },
+ ])
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/colors/colors.test.js b/packages/tailwindcss-language-server/tests/colors/colors.test.js
index 5016bacc3..4780a4fb2 100644
--- a/packages/tailwindcss-language-server/tests/colors/colors.test.js
+++ b/packages/tailwindcss-language-server/tests/colors/colors.test.js
@@ -334,7 +334,7 @@ defineTest({
expect(c.project).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -373,7 +373,7 @@ defineTest({
expect(c.project).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
diff --git a/packages/tailwindcss-language-server/tests/completions/at-config.test.js b/packages/tailwindcss-language-server/tests/completions/at-config.test.js
index 15d99ac69..60ee14952 100644
--- a/packages/tailwindcss-language-server/tests/completions/at-config.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/at-config.test.js
@@ -271,6 +271,51 @@ withFixture('v4/dependencies', (c) => {
})
})
+ test.concurrent('@source not', async ({ expect }) => {
+ let result = await completion({
+ text: '@source not "',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 13,
+ },
+ })
+
+ expect(result).toEqual({
+ isIncomplete: false,
+ items: [
+ {
+ label: 'index.html',
+ kind: 17,
+ data: expect.anything(),
+ textEdit: {
+ newText: 'index.html',
+ range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
+ },
+ },
+ {
+ label: 'sub-dir/',
+ kind: 19,
+ command: { command: 'editor.action.triggerSuggest', title: '' },
+ data: expect.anything(),
+ textEdit: {
+ newText: 'sub-dir/',
+ range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
+ },
+ },
+ {
+ label: 'tailwind.config.js',
+ kind: 17,
+ data: expect.anything(),
+ textEdit: {
+ newText: 'tailwind.config.js',
+ range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
+ },
+ },
+ ],
+ })
+ })
+
test.concurrent('@source directory', async ({ expect }) => {
let result = await completion({
text: '@source "./sub-dir/',
@@ -297,6 +342,58 @@ withFixture('v4/dependencies', (c) => {
})
})
+ test.concurrent('@source not directory', async ({ expect }) => {
+ let result = await completion({
+ text: '@source not "./sub-dir/',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 23,
+ },
+ })
+
+ expect(result).toEqual({
+ isIncomplete: false,
+ items: [
+ {
+ label: 'colors.js',
+ kind: 17,
+ data: expect.anything(),
+ textEdit: {
+ newText: 'colors.js',
+ range: { start: { line: 0, character: 23 }, end: { line: 0, character: 23 } },
+ },
+ },
+ ],
+ })
+ })
+
+ test.concurrent('@source inline(…)', async ({ expect }) => {
+ let result = await completion({
+ text: '@source inline("',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 16,
+ },
+ })
+
+ expect(result).toEqual(null)
+ })
+
+ test.concurrent('@source not inline(…)', async ({ expect }) => {
+ let result = await completion({
+ text: '@source not inline("',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 20,
+ },
+ })
+
+ expect(result).toEqual(null)
+ })
+
test.concurrent('@import "…" source(…)', async ({ expect }) => {
let result = await completion({
text: '@import "tailwindcss" source("',
diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js
index 2727fcb0f..090ec872e 100644
--- a/packages/tailwindcss-language-server/tests/completions/completions.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js
@@ -1,5 +1,8 @@
-import { test } from 'vitest'
+import { test, expect } from 'vitest'
import { withFixture } from '../common'
+import { css, defineTest, html, js } from '../../src/testing'
+import { createClient } from '../utils/client'
+import { CompletionItemKind } from 'vscode-languageserver'
function buildCompletion(c) {
return async function completion({
@@ -310,8 +313,8 @@ withFixture('v4/basic', (c) => {
let result = await completion({ lang, text, position, settings })
let textEdit = expect.objectContaining({ range: { start: position, end: position } })
- expect(result.items.length).toBe(12314)
- expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(304)
+ expect(result.items.length).not.toBe(0)
+ expect(result.items.filter((item) => item.label.endsWith(':')).length).not.toBe(0)
expect(result).toEqual({
isIncomplete: false,
items: expect.arrayContaining([
@@ -485,7 +488,7 @@ withFixture('v4/basic', (c) => {
})
// Make sure `@slot` is NOT suggested by default
- expect(result.items.length).toBe(7)
+ expect(result.items.length).toBe(8)
expect(result.items).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ kind: 14, label: '@slot', sortText: '-0000000' }),
@@ -624,7 +627,7 @@ withFixture('v4/basic', (c) => {
expect(resolved).toEqual({
...item,
- detail: 'background-color: oklch(0.637 0.237 25.331);',
+ detail: 'background-color: oklch(63.7% 0.237 25.331);',
documentation: '#fb2c36',
})
})
@@ -670,3 +673,379 @@ withFixture('v4/workspaces', (c) => {
})
})
})
+
+defineTest({
+ name: 'v4: Completions show after a variant arbitrary value',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ //
+ // ^
+ let completion = await document.completions({ line: 0, character: 23 })
+
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show after an arbitrary variant',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let completion = await document.completions({ line: 0, character: 22 })
+
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show after a variant with a bare value',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let completion = await document.completions({ line: 0, character: 31 })
+
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show after a variant arbitrary value, using prefixes',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss' prefix(tw);
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let completion = await document.completions({ line: 0, character: 26 })
+
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Variant and utility suggestions show prefix when one has been typed',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss' prefix(tw);
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let completion = await document.completions({ line: 0, character: 12 })
+
+ expect(completion?.items.length).not.toBe(0)
+
+ // Verify that variants and utilities are all prefixed
+ let prefixed = completion.items.filter((item) => !item.label.startsWith('tw:'))
+ expect(prefixed).toHaveLength(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Variant and utility suggestions hide prefix when it has been typed',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss' prefix(tw);
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let completion = await document.completions({ line: 0, character: 15 })
+
+ expect(completion?.items.length).not.toBe(0)
+
+ // Verify that no variants and utilities have prefixes
+ let prefixed = completion.items.filter((item) => item.label.startsWith('tw:'))
+ expect(prefixed).toHaveLength(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show inside class functions in JS/TS files',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx'],
+ },
+ },
+ lang: 'javascript',
+ text: js`
+ let classes = clsx('');
+ `,
+ })
+
+ // let classes = clsx('');
+ // ^
+ let completion = await document.completions({ line: 0, character: 20 })
+
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show inside class functions in JS/TS contexts',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx'],
+ },
+ },
+ lang: 'html',
+ text: html`
+
+ `,
+ })
+
+ // let classes = clsx('')
+ // ^
+ let completion = await document.completions({ line: 1, character: 22 })
+
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4: Theme key completions show in var(…)',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-custom: #000;
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx'],
+ },
+ },
+ lang: 'css',
+ text: css`
+ .foo {
+ color: var();
+ }
+ `,
+ })
+
+ // color: var();
+ // ^
+ let completion = await document.completions({ line: 1, character: 13 })
+
+ expect(completion).toEqual({
+ isIncomplete: false,
+ items: expect.arrayContaining([
+ // From the default theme
+ expect.objectContaining({ label: '--font-sans' }),
+
+ // From the `@theme` block in the CSS file
+ expect.objectContaining({
+ label: '--color-custom',
+
+ // And it's shown as a color
+ kind: CompletionItemKind.Color,
+ documentation: '#000000',
+ }),
+ ]),
+ })
+ },
+})
+
+defineTest({
+ name: 'v4: class function completions mixed with class attribute completions work',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['className'],
+ classFunctions: ['cn', 'cva'],
+ },
+ },
+ lang: 'javascriptreact',
+ text: js`
+ let x = cva("")
+
+ export function Button() {
+ return
+ }
+
+ export function Button2() {
+ return
+ }
+
+ let y = cva("")
+ `,
+ })
+
+ // let x = cva("");
+ // ^
+ let completionA = await document.completions({ line: 0, character: 13 })
+
+ expect(completionA?.items.length).not.toBe(0)
+
+ // return
;
+ // ^
+ let completionB = await document.completions({ line: 3, character: 30 })
+
+ expect(completionB?.items.length).not.toBe(0)
+
+ // return
;
+ // ^
+ let completionC = await document.completions({ line: 7, character: 30 })
+
+ expect(completionC?.items.length).not.toBe(0)
+
+ // let y = cva("");
+ // ^
+ let completionD = await document.completions({ line: 10, character: 13 })
+
+ expect(completionD?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'Completions for several utilities have simplified details',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: html`
`,
+ })
+
+ //
+ // ^
+ let list = await document.completions({ line: 0, character: 12 })
+ let items = list?.items ?? []
+
+ let map = {
+ 'border-0': 'border-width: 0px;',
+ 'outline-0': 'outline-width: 0px;',
+ 'leading-0': 'line-height: 0rem /* 0px */;',
+ 'duration-1000': 'transition-duration: 1000ms;',
+ 'font-bold': 'font-weight: 700;',
+ 'ease-linear': 'transition-timing-function: linear;',
+ 'ease-initial': '--tw-ease: initial;',
+
+ 'space-x-0':
+ 'margin-inline-start: calc(0rem /* 0px */ * var(--tw-space-x-reverse)); margin-inline-end: calc(0rem /* 0px */ * calc(1 - var(--tw-space-x-reverse)));',
+ 'space-y-0':
+ 'margin-block-start: calc(0rem /* 0px */ * var(--tw-space-y-reverse)); margin-block-end: calc(0rem /* 0px */ * calc(1 - var(--tw-space-y-reverse)));',
+ 'divide-x-0':
+ 'border-inline-start-width: calc(0px * var(--tw-divide-x-reverse)); border-inline-end-width: calc(0px * calc(1 - var(--tw-divide-x-reverse)));',
+ 'divide-y-0':
+ 'border-top-width: calc(0px * var(--tw-divide-y-reverse)); border-bottom-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));',
+
+ 'tracking-wide': 'letter-spacing: 0.025em;',
+
+ 'from-red-500': '--tw-gradient-from: oklch(63.7% 0.237 25.331);',
+ 'via-red-500': '--tw-gradient-via: oklch(63.7% 0.237 25.331);',
+ 'to-red-500': '--tw-gradient-to: oklch(63.7% 0.237 25.331);',
+
+ 'scale-100': '--tw-scale-x: 100%; --tw-scale-y: 100%; --tw-scale-z: 100%;',
+ 'scale-z-100': '--tw-scale-z: 100%;',
+
+ 'translate-1': '--tw-translate-x: 0.25rem /* 4px */; --tw-translate-y: 0.25rem /* 4px */;',
+ 'translate-z-1': '--tw-translate-z: 0.25rem /* 4px */;',
+
+ 'bg-conic-0':
+ '--tw-gradient-position: from 0deg in oklab; background-image: conic-gradient(var(--tw-gradient-stops));',
+ }
+
+ let requests = await Promise.all(
+ Object.keys(map).map(async (label) => {
+ let item = items.find((item) => item.label === label)
+ if (!item) throw new Error(`Item not found for label: ${label}`)
+
+ let resolved = await client.conn.sendRequest('completionItem/resolve', item)
+
+ return [label, resolved.detail]
+ }),
+ )
+
+ expect(Object.fromEntries(requests)).toEqual(map)
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/css/css-server.test.ts b/packages/tailwindcss-language-server/tests/css/css-server.test.ts
index 02146cccb..388e94af7 100644
--- a/packages/tailwindcss-language-server/tests/css/css-server.test.ts
+++ b/packages/tailwindcss-language-server/tests/css/css-server.test.ts
@@ -38,7 +38,7 @@ defineTest({
uri: '{workspace:default}/file-1.css',
range: {
start: { line: 1, character: 0 },
- end: { line: 1, character: 31 },
+ end: { line: 1, character: 30 },
},
},
},
@@ -219,6 +219,7 @@ defineTest({
@theme {
--color-primary: #333;
--leading-*: initial;
+ --font-weight-*: initial;
}
`,
})
@@ -235,7 +236,7 @@ defineTest({
uri: '{workspace:default}/file-1.css',
range: {
start: { line: 1, character: 0 },
- end: { line: 4, character: 1 },
+ end: { line: 5, character: 1 },
},
},
},
@@ -688,3 +689,49 @@ defineTest({
expect(await doc.diagnostics()).toEqual([])
},
})
+
+defineTest({
+ name: 'completions are hidden inside @import source(…)/theme(…)/prefix(…) functions',
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ server: 'css',
+ root,
+ }),
+ }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'tailwindcss',
+ name: 'file-1.css',
+ text: css`
+ @import './file.css' source(none);
+ @import './file.css' theme(inline);
+ @import './file.css' prefix(tw);
+ @import './file.css' source(none) theme(inline) prefix(tw);
+ `,
+ })
+
+ // @import './file.css' source(none)
+ // ^
+ // @import './file.css' theme(inline);
+ // ^
+ // @import './file.css' prefix(tw);
+ // ^
+ let completionsA = await doc.completions({ line: 0, character: 29 })
+ let completionsB = await doc.completions({ line: 1, character: 28 })
+ let completionsC = await doc.completions({ line: 2, character: 29 })
+
+ expect(completionsA).toEqual({ isIncomplete: false, items: [] })
+ expect(completionsB).toEqual({ isIncomplete: false, items: [] })
+ expect(completionsC).toEqual({ isIncomplete: false, items: [] })
+
+ // @import './file.css' source(none) theme(inline) prefix(tw);
+ // ^ ^ ^
+ let completionsD = await doc.completions({ line: 3, character: 29 })
+ let completionsE = await doc.completions({ line: 3, character: 41 })
+ let completionsF = await doc.completions({ line: 3, character: 56 })
+
+ expect(completionsD).toEqual({ isIncomplete: false, items: [] })
+ expect(completionsE).toEqual({ isIncomplete: false, items: [] })
+ expect(completionsF).toEqual({ isIncomplete: false, items: [] })
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js b/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js
index 56fe9f7ff..afda88373 100644
--- a/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js
+++ b/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js
@@ -1,6 +1,8 @@
+import * as fs from 'node:fs/promises'
import { expect, test } from 'vitest'
import { withFixture } from '../common'
-import * as fs from 'node:fs/promises'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
withFixture('basic', (c) => {
function testFixture(fixture) {
@@ -383,3 +385,43 @@ withFixture('v4/basic', (c) => {
],
})
})
+
+defineTest({
+ name: 'Shows warning when using blocklisted classes',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ @source not inline("{,hover:}flex");
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ let diagnostics = await doc.diagnostics()
+
+ expect(diagnostics).toEqual([
+ {
+ code: 'usedBlocklistedClass',
+ message: 'The class "flex" will not be generated as it has been blocklisted',
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 16 },
+ },
+ severity: 2,
+ },
+ {
+ code: 'usedBlocklistedClass',
+ message: 'The class "hover:flex" will not be generated as it has been blocklisted',
+ range: {
+ start: { line: 0, character: 27 },
+ end: { line: 0, character: 37 },
+ },
+ severity: 2,
+ },
+ ])
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/document-links/document-links.test.js b/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
index 861f74c9e..f187cc468 100644
--- a/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
+++ b/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
@@ -1,6 +1,7 @@
import { test } from 'vitest'
-import { withFixture } from '../common'
import * as path from 'path'
+import { URI } from 'vscode-uri'
+import { withFixture } from '../common'
withFixture('basic', (c) => {
async function testDocumentLinks(name, { text, lang, expected }) {
@@ -19,9 +20,7 @@ withFixture('basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/basic/tailwind.config.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/basic/tailwind.config.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 28 } },
},
],
@@ -32,9 +31,7 @@ withFixture('basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/basic/does-not-exist.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/basic/does-not-exist.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } },
},
],
@@ -58,9 +55,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/tailwind.config.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/tailwind.config.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 28 } },
},
],
@@ -71,9 +66,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } },
},
],
@@ -84,9 +77,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/plugin.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/plugin.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 19 } },
},
],
@@ -97,9 +88,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } },
},
],
@@ -110,9 +99,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/index.html')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/index.html')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 20 } },
},
],
@@ -123,14 +110,46 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.html')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.html')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 29 } },
},
],
})
+ testDocumentLinks('source not: file exists', {
+ text: '@source not "index.html";',
+ lang: 'css',
+ expected: [
+ {
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/index.html')).toString(),
+ range: { start: { line: 0, character: 12 }, end: { line: 0, character: 24 } },
+ },
+ ],
+ })
+
+ testDocumentLinks('source not: file does not exist', {
+ text: '@source not "does-not-exist.html";',
+ lang: 'css',
+ expected: [
+ {
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.html')).toString(),
+ range: { start: { line: 0, character: 12 }, end: { line: 0, character: 33 } },
+ },
+ ],
+ })
+
+ testDocumentLinks('@source inline(…)', {
+ text: '@source inline("m-{1,2,3}");',
+ lang: 'css',
+ expected: [],
+ })
+
+ testDocumentLinks('@source not inline(…)', {
+ text: '@source not inline("m-{1,2,3}");',
+ lang: 'css',
+ expected: [],
+ })
+
testDocumentLinks('Directories in source(…) show links', {
text: `
@import "tailwindcss" source("../../");
@@ -139,11 +158,11 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path.resolve('./tests/fixtures').replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures')).toString(),
range: { start: { line: 1, character: 35 }, end: { line: 1, character: 43 } },
},
{
- target: `file://${path.resolve('./tests/fixtures').replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures')).toString(),
range: { start: { line: 2, character: 33 }, end: { line: 2, character: 41 } },
},
],
diff --git a/packages/tailwindcss-language-server/tests/env/capabilities.test.ts b/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
new file mode 100644
index 000000000..6799c0ba2
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
@@ -0,0 +1,246 @@
+import { expect } from 'vitest'
+import { defineTest, js } from '../../src/testing'
+import { createClient } from '../utils/client'
+import * as fs from 'node:fs/promises'
+
+defineTest({
+ name: 'Changing the separator registers new trigger characters',
+ fs: {
+ 'tailwind.config.js': js`
+ module.exports = {
+ separator: ':',
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ root, client }) => {
+ // Initially don't have any registered capabilities because dynamic
+ // registration is delayed until after project initialization
+ expect(client.serverCapabilities).toEqual([])
+
+ // We open a document so a project gets initialized
+ await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ // And now capabilities are registered
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+
+ let countBeforeChange = client.serverCapabilities.length
+ let capabilitiesDidChange = Promise.race([
+ new Promise
((_, reject) => {
+ setTimeout(() => reject('capabilities did not change within 5s'), 5_000)
+ }),
+
+ new Promise((resolve) => {
+ client.onServerCapabilitiesChanged(() => {
+ if (client.serverCapabilities.length !== countBeforeChange) return
+ resolve()
+ })
+ }),
+ ])
+
+ await fs.writeFile(
+ `${root}/tailwind.config.js`,
+ js`
+ module.exports = {
+ separator: '_',
+ }
+ `,
+ )
+
+ // After changing the config
+ client.notifyChangedFiles({
+ changed: [`${root}/tailwind.config.js`],
+ })
+
+ // We should see that the capabilities have changed
+ await capabilitiesDidChange
+
+ // Capabilities are now registered
+ expect(client.serverCapabilities).toContainEqual(
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+ )
+
+ expect(client.serverCapabilities).toContainEqual(
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', '_'],
+ },
+ }),
+ )
+
+ expect(client.serverCapabilities).not.toContainEqual(
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ )
+ },
+})
+
+defineTest({
+ name: 'Config updates do not register new trigger characters if the separator has not changed',
+ fs: {
+ 'tailwind.config.js': js`
+ module.exports = {
+ separator: ':',
+ theme: {
+ colors: {
+ primary: '#f00',
+ }
+ }
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ root, client }) => {
+ // Initially don't have any registered capabilities because dynamic
+ // registration is delayed until after project initialization
+ expect(client.serverCapabilities).toEqual([])
+
+ // We open a document so a project gets initialized
+ await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ // And now capabilities are registered
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+
+ let idsBefore = client.serverCapabilities.map((cap) => cap.id)
+
+ await fs.writeFile(
+ `${root}/tailwind.config.js`,
+ js`
+ module.exports = {
+ separator: ':',
+ theme: {
+ colors: {
+ primary: '#0f0',
+ }
+ }
+ }
+ `,
+ )
+
+ let didReload = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/projectReloaded', resolve)
+ })
+
+ // After changing the config
+ client.notifyChangedFiles({
+ changed: [`${root}/tailwind.config.js`],
+ })
+
+ // Wait for the project to finish building
+ await didReload
+
+ // No capabilities should have changed
+ let idsAfter = client.serverCapabilities.map((cap) => cap.id)
+
+ expect(idsBefore).toEqual(idsAfter)
+ },
+})
+
+defineTest({
+ name: 'Trigger characters are registered after a server restart',
+ fs: {
+ 'app.css': '@import "tailwindcss"',
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ root, client }) => {
+ // Initially don't have any registered capabilities because dynamic
+ // registration is delayed until after project initialization
+ expect(client.serverCapabilities).toEqual([])
+
+ // We open a document so a project gets initialized
+ await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ // And now capabilities are registered
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+
+ let didRestart = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ })
+
+ // Force a server restart by telling the server tsconfig.json changed
+ client.notifyChangedFiles({
+ changed: [`${root}/tsconfig.json`],
+ })
+
+ // Wait for the server initialization to finish
+ await didRestart
+
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/env/ignored.test.ts b/packages/tailwindcss-language-server/tests/env/ignored.test.ts
new file mode 100644
index 000000000..f5f7d01fb
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/env/ignored.test.ts
@@ -0,0 +1,111 @@
+import { expect } from 'vitest'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
+import dedent from 'dedent'
+
+let ignored = css`
+ @import 'tailwindcss';
+ @theme {
+ --color-primary: #c0ffee;
+ }
+`
+
+let found = css`
+ @import 'tailwindcss';
+ @theme {
+ --color-primary: rebeccapurple;
+ }
+`
+
+defineTest({
+ name: 'various build folders and caches are ignored by default',
+ fs: {
+ // All of these should be ignored
+ 'aaa/.git/app.css': ignored,
+ 'aaa/.hg/app.css': ignored,
+ 'aaa/.svn/app.css': ignored,
+ 'aaa/node_modules/app.css': ignored,
+ 'aaa/.yarn/app.css': ignored,
+ 'aaa/.venv/app.css': ignored,
+ 'aaa/venv/app.css': ignored,
+ 'aaa/.next/app.css': ignored,
+ 'aaa/.parcel-cache/app.css': ignored,
+ 'aaa/.svelte-kit/app.css': ignored,
+ 'aaa/.turbo/app.css': ignored,
+ 'aaa/__pycache__/app.css': ignored,
+
+ // But this one should not be
+ 'zzz/app.css': found,
+ },
+
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .bg-primary {
+ background-color: var(--color-primary) /* rebeccapurple = #663399 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 22 },
+ },
+ })
+ },
+})
+
+defineTest({
+ name: 'ignores can be overridden',
+ fs: {
+ 'aaa/app.css': ignored,
+ 'bbb/.git/app.css': found,
+ },
+
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ settings: {
+ tailwindCSS: {
+ files: {
+ exclude: ['**/aaa/**'],
+ },
+ },
+ },
+ }),
+ }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .bg-primary {
+ background-color: var(--color-primary) /* rebeccapurple = #663399 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 22 },
+ },
+ })
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js b/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
index 44b4cb64e..f9e7da2f9 100644
--- a/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
+++ b/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
@@ -1,38 +1,85 @@
-import { test } from 'vitest'
-import { withFixture } from '../common'
+import { expect } from 'vitest'
+import { css, defineTest, html, js, json, symlinkTo } from '../../src/testing'
+import dedent from 'dedent'
+import { createClient } from '../utils/client'
-withFixture('multi-config-content', (c) => {
- test.concurrent('multi-config with content config - 1', async ({ expect }) => {
- let textDocument = await c.openDocument({ text: '
', dir: 'one' })
- let res = await c.sendRequest('textDocument/hover', {
- textDocument,
- position: { line: 0, character: 13 },
+defineTest({
+ name: 'multi-config with content config',
+ fs: {
+ 'tailwind.config.one.js': js`
+ module.exports = {
+ content: ['./one/**/*'],
+ theme: {
+ extend: {
+ colors: {
+ foo: 'red',
+ },
+ },
+ },
+ }
+ `,
+ 'tailwind.config.two.js': js`
+ module.exports = {
+ content: ['./two/**/*'],
+ theme: {
+ extend: {
+ colors: {
+ foo: 'blue',
+ },
+ },
+ },
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let one = await client.open({
+ lang: 'html',
+ name: 'one/index.html',
+ text: '
',
})
- expect(res).toEqual({
+ let two = await client.open({
+ lang: 'html',
+ name: 'two/index.html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hoverOne = await one.hover({ line: 0, character: 13 })
+ let hoverTwo = await two.hover({ line: 0, character: 13 })
+
+ expect(hoverOne).toEqual({
contents: {
language: 'css',
- value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity, 1)) /* #ff0000 */;\n}',
+ value: dedent`
+ .bg-foo {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 0 0 / var(--tw-bg-opacity, 1)) /* #ff0000 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 18 },
},
- range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
- })
- })
-
- test.concurrent('multi-config with content config - 2', async ({ expect }) => {
- let textDocument = await c.openDocument({ text: '
', dir: 'two' })
- let res = await c.sendRequest('textDocument/hover', {
- textDocument,
- position: { line: 0, character: 13 },
})
- expect(res).toEqual({
+ expect(hoverTwo).toEqual({
contents: {
language: 'css',
- value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity, 1)) /* #0000ff */;\n}',
+ value: dedent`
+ .bg-foo {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 255 / var(--tw-bg-opacity, 1)) /* #0000ff */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 18 },
},
- range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
})
- })
+ },
})
diff --git a/packages/tailwindcss-language-server/tests/env/restart.test.ts b/packages/tailwindcss-language-server/tests/env/restart.test.ts
new file mode 100644
index 000000000..fa56cfb76
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/env/restart.test.ts
@@ -0,0 +1,268 @@
+import { expect } from 'vitest'
+import * as fs from 'node:fs/promises'
+import * as path from 'node:path'
+import { css, defineTest } from '../../src/testing'
+import dedent from 'dedent'
+import { createClient } from '../utils/client'
+
+defineTest({
+ name: 'The design system is reloaded when the CSS changes ($watcher)',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #c0ffee;
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ capabilities(caps) {
+ caps.workspace!.didChangeWatchedFiles!.dynamicRegistration = false
+ },
+ }),
+ }),
+ handle: async ({ root, client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #c0ffee */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+
+ let didReload = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/projectReloaded', resolve)
+ })
+
+ // Update the CSS
+ await fs.writeFile(
+ path.resolve(root, 'app.css'),
+ css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #bada55;
+ }
+ `,
+ )
+
+ await didReload
+
+ //
+ // ^
+ let hover2 = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover2).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #bada55 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+ },
+})
+
+defineTest({
+ options: {
+ retry: 3,
+
+ // This test passes on all platforms but it is super flaky
+ // The server needs some re-working to ensure everything is awaited
+ // properly with respect to messages and server responses
+ skip: true,
+ },
+ name: 'Server is "restarted" when a config file is removed',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #c0ffee;
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ capabilities(caps) {
+ caps.workspace!.didChangeWatchedFiles!.dynamicRegistration = false
+ },
+ }),
+ }),
+ handle: async ({ root, client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #c0ffee */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+
+ expect(client.serverCapabilities).not.toEqual([])
+ let ids1 = client.serverCapabilities.map((cap) => cap.id)
+
+ // Remove the CSS file
+ let didRestart = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ })
+ await fs.unlink(path.resolve(root, 'app.css'))
+ await didRestart
+
+ expect(client.serverCapabilities).not.toEqual([])
+ let ids2 = client.serverCapabilities.map((cap) => cap.id)
+
+ //
+ // ^
+ let hover2 = await doc.hover({ line: 0, character: 13 })
+ expect(hover2).toEqual(null)
+
+ // Re-create the CSS file
+ let didRestartAgain = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ })
+ await fs.writeFile(
+ path.resolve(root, 'app.css'),
+ css`
+ @import 'tailwindcss';
+ `,
+ )
+ await didRestartAgain
+
+ expect(client.serverCapabilities).not.toEqual([])
+ let ids3 = client.serverCapabilities.map((cap) => cap.id)
+
+ await new Promise((resolve) => setTimeout(resolve, 500))
+
+ //
+ // ^
+ let hover3 = await doc.hover({ line: 0, character: 13 })
+ expect(hover3).toEqual(null)
+
+ expect(ids1).not.toContainEqual(expect.toBeOneOf(ids2))
+ expect(ids1).not.toContainEqual(expect.toBeOneOf(ids3))
+
+ expect(ids2).not.toContainEqual(expect.toBeOneOf(ids1))
+ expect(ids2).not.toContainEqual(expect.toBeOneOf(ids3))
+
+ expect(ids3).not.toContainEqual(expect.toBeOneOf(ids1))
+ expect(ids3).not.toContainEqual(expect.toBeOneOf(ids2))
+ },
+})
+
+defineTest({
+ name: 'Creating a CSS config in an empty folder initalizes a project',
+ fs: {
+ 'app.css': css`
+ /* this file is not a Tailwind CSS config yet */
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({ root, log: true }),
+ }),
+ handle: async ({ root, client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual(null)
+
+ // Create a CSS config file
+ await fs.writeFile(
+ `${root}/app.css`,
+ css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #c0ffee;
+ }
+ `,
+ )
+
+ // Create a CSS config file
+ // Notify the server of the config change
+ let didRestart = Promise.race([
+ new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ }),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Did not restart in time')), 5000),
+ ),
+ ])
+
+ await client.notifyChangedFiles({
+ changed: [`${root}/app.css`],
+ })
+
+ await didRestart
+
+ // TODO: Sending a shutdown request immediately after a restart
+ // gets lost
+ // await client.shutdown()
+
+ //
+ // ^
+ hover = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #c0ffee */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index 1ae5caf4a..6104ef7ae 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -1,7 +1,7 @@
// @ts-check
import { expect } from 'vitest'
-import { css, defineTest, html, js, json } from '../../src/testing'
+import { css, defineTest, html, js, json, symlinkTo } from '../../src/testing'
import dedent from 'dedent'
import { createClient } from '../utils/client'
@@ -21,7 +21,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -49,7 +49,46 @@ defineTest({
},
})
- expect(completion?.items.length).toBe(12288)
+ expect(completion?.items.length).not.toBe(0)
+ },
+})
+
+defineTest({
+ name: 'v4, no npm, bundled plugins',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ @plugin "@tailwindcss/aspect-ratio";
+ @plugin "@tailwindcss/forms";
+ @plugin "@tailwindcss/typography";
+ `,
+ },
+
+ // Note this test MUST run in spawn mode because Vitest hooks into import,
+ // require, etc… already and we need to test that any hooks are working
+ // without outside interference.
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+ expect(hover).not.toEqual(null)
+
+ //
+ // ^
+ hover = await doc.hover({ line: 0, character: 25 })
+ expect(hover).not.toEqual(null)
+
+ //
+ // ^
+ hover = await doc.hover({ line: 0, character: 37 })
+ expect(hover).not.toEqual(null)
},
})
@@ -98,7 +137,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -149,7 +188,7 @@ defineTest({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.1"
+ "tailwindcss": "4.1.1"
}
}
`,
@@ -166,7 +205,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.1',
+ version: '4.1.1',
isDefaultVersion: false,
},
})
@@ -194,7 +233,7 @@ defineTest({
},
})
- expect(completion?.items.length).toBe(12288)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -204,7 +243,7 @@ defineTest({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.1"
+ "tailwindcss": "4.1.1"
}
}
`,
@@ -231,7 +270,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.1',
+ version: '4.1.1',
isDefaultVersion: false,
},
})
@@ -283,7 +322,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -315,7 +354,7 @@ defineTest({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.1"
+ "tailwindcss": "4.1.1"
}
}
`,
@@ -566,6 +605,12 @@ defineTest({
})
defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ retry: 3,
+ timeout: 30_000,
+ },
+
name: 'Plugins with a `#` in the name are loadable',
fs: {
'app.css': css`
@@ -611,6 +656,12 @@ defineTest({
})
defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ retry: 3,
+ timeout: 30_000,
+ },
+
name: 'v3: Presets with a `#` in the name are loadable',
fs: {
'package.json': json`
@@ -666,3 +717,181 @@ defineTest({
})
},
})
+
+defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ retry: 3,
+ timeout: 30_000,
+ },
+
+ // This test *always* passes inside Vitest because our custom version of
+ // `Module._resolveFilename` is not called. Our custom implementation is
+ // using enhanced-resolve under the hood which is affected by the `#`
+ // character issue being considered a fragment identifier.
+ //
+ // This most commonly happens when dealing with PNPM packages that point
+ // to a specific commit hash of a git repository.
+ //
+ // To simulate this, we need to:
+ // - Add a local package to package.json
+ // - Symlink that local package to a directory with `#` in the name
+ // - Then run the test in a separate process (`spawn` mode)
+ //
+ // We can't use `file:./a#b` because NPM considers `#` to be a fragment
+ // identifier and will not resolve the path the way we need it to.
+ name: 'v3: require() works when path is resolved to contain a `#`',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "3.4.17",
+ "some-pkg": "file:./packages/some-pkg"
+ }
+ }
+ `,
+ 'tailwind.config.js': js`
+ module.exports = {
+ presets: [require('some-pkg/config/tailwind.config.js').default]
+ }
+ `,
+ 'packages/some-pkg': symlinkTo('packages/some-pkg#c3f1e', 'dir'),
+ 'packages/some-pkg#c3f1e/package.json': json`
+ {
+ "name": "some-pkg",
+ "version": "1.0.0",
+ "main": "index.js"
+ }
+ `,
+ 'packages/some-pkg#c3f1e/config/tailwind.config.js': js`
+ export default {
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.example': {
+ color: 'red',
+ },
+ })
+ }
+ ]
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ mode: 'spawn',
+ }),
+ }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await document.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .example {
+ color: red;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 19 },
+ },
+ })
+ },
+})
+
+defineTest({
+ name: 'regex literals do not break language boundaries',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'javascriptreact',
+ text: js`
+ export default function Page() {
+ let styles = "str".match(/
+
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'html',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 1, character: 2 },
+ },
+ },
+ {
+ type: 'css',
+ range: {
+ start: { line: 1, character: 2 },
+ end: { line: 5, character: 2 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ start: { line: 5, character: 2 },
+ end: { line: 7, character: 6 },
+ },
+ },
+ ])
+})
+
+test('script tags in HTML are treated as a separate boundary', ({ expect }) => {
+ let file = createDocument({
+ name: 'file.html',
+ lang: 'html',
+ content: html`
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'html',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 1, character: 2 },
+ },
+ },
+ {
+ type: 'js',
+ range: {
+ start: { line: 1, character: 2 },
+ end: { line: 5, character: 2 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ start: { line: 5, character: 2 },
+ end: { line: 7, character: 6 },
+ },
+ },
+ ])
+})
+
+test('Vue files detect
,
+
+
+
+
+
+ Some documentation
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'none',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 0 },
+ },
+ },
+ {
+ type: 'js',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 2, character: 0 },
+ },
+ },
+ {
+ type: 'none',
+ range: {
+ start: { line: 2, character: 0 },
+ end: { line: 3, character: 0 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ start: { line: 3, character: 0 },
+ end: { line: 5, character: 0 },
+ },
+ },
+ {
+ type: 'none',
+ range: {
+ start: { line: 5, character: 0 },
+ end: { line: 6, character: 0 },
+ },
+ },
+ {
+ type: 'css',
+ range: {
+ start: { line: 6, character: 0 },
+ end: { line: 10, character: 0 },
+ },
+ },
+ {
+ type: 'none',
+ range: {
+ start: { line: 10, character: 0 },
+ end: { line: 13, character: 16 },
+ },
+ },
+ ])
+})
+
+test('Astro files default to HTML', ({ expect }) => {
+ let file = createDocument({
+ name: 'file.astro',
+ lang: 'astro',
+ content: html``,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'html',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 35 },
+ },
+ },
+ ])
+})
+
+test('Astro files front matter is parsed as JS', ({ expect }) => {
+ let file = createDocument({
+ name: 'file.astro',
+ lang: 'astro',
+ content: astro`
+ ---
+ console.log('test')
+ ---
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ // This block just shouldn't be here
+ {
+ type: 'html',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 0 },
+ },
+ },
+ {
+ type: 'js',
+ range: {
+ // This should probably be 0:3 instead of 0:0
+ start: { line: 0, character: 0 },
+
+ // This should probably be 2:0 instead of 1:19
+ end: { line: 1, character: 19 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ // This should probably be 2:3 instead of 1:19
+ start: { line: 1, character: 19 },
+ end: { line: 3, character: 35 },
+ },
+ },
+ ])
+})
diff --git a/packages/tailwindcss-language-service/src/util/languages.ts b/packages/tailwindcss-language-service/src/util/languages.ts
index 7a00d1f7b..458261cf4 100644
--- a/packages/tailwindcss-language-service/src/util/languages.ts
+++ b/packages/tailwindcss-language-service/src/util/languages.ts
@@ -1,6 +1,6 @@
import type { EditorState } from './state'
-export const htmlLanguages = [
+export const htmlLanguages: string[] = [
'aspnetcorerazor',
'astro',
'astro-markdown',
@@ -21,6 +21,7 @@ export const htmlLanguages = [
'html-eex',
'htmldjango',
'jade',
+ 'latte',
'leaf',
'liquid',
'markdown',
@@ -36,7 +37,7 @@ export const htmlLanguages = [
'twig',
]
-export const cssLanguages = [
+export const cssLanguages: string[] = [
'css',
'less',
'postcss',
@@ -47,7 +48,7 @@ export const cssLanguages = [
'tailwindcss',
]
-export const jsLanguages = [
+export const jsLanguages: string[] = [
'javascript',
'javascriptreact',
'reason',
@@ -58,16 +59,21 @@ export const jsLanguages = [
'glimmer-ts',
]
-export const specialLanguages = ['vue', 'svelte']
+export const specialLanguages: string[] = ['vue', 'svelte']
-export const languages = [...cssLanguages, ...htmlLanguages, ...jsLanguages, ...specialLanguages]
+export const languages: string[] = [
+ ...cssLanguages,
+ ...htmlLanguages,
+ ...jsLanguages,
+ ...specialLanguages,
+]
const semicolonlessLanguages = ['sass', 'sugarss', 'stylus']
export function isSemicolonlessCssLanguage(
languageId: string,
userLanguages: EditorState['userLanguages'] = {},
-) {
+): boolean {
return (
semicolonlessLanguages.includes(languageId) ||
semicolonlessLanguages.includes(userLanguages[languageId])
diff --git a/packages/tailwindcss-language-service/src/util/lexers.ts b/packages/tailwindcss-language-service/src/util/lexers.ts
index 38ecc2d7d..cfd062b5a 100644
--- a/packages/tailwindcss-language-service/src/util/lexers.ts
+++ b/packages/tailwindcss-language-service/src/util/lexers.ts
@@ -1,5 +1,5 @@
import moo from 'moo'
-import { lazy } from './lazy'
+import { Lazy, lazy } from './lazy'
const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
doubleClassList: {
@@ -29,6 +29,14 @@ const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
rbrace: { match: new RegExp('(? {
+export const getClassAttributeLexer: Lazy = lazy(() => {
let supportsNegativeLookbehind = true
try {
new RegExp('(? {
start2: { match: "'", push: 'singleClassList' },
start3: { match: '{', push: 'interpBrace' },
start4: { match: '`', push: 'tickClassList' },
+ start5: { match: '(', push: 'interpParen' },
},
...classAttributeStates(),
})
@@ -81,7 +90,7 @@ export const getClassAttributeLexer = lazy(() => {
return moo.states(simpleClassAttributeStates)
})
-export const getComputedClassAttributeLexer = lazy(() => {
+export const getComputedClassAttributeLexer: Lazy = lazy(() => {
let supportsNegativeLookbehind = true
try {
new RegExp('(? = Object.assign(
+ ({ comments, rootFontSize }: { comments: Comment[]; rootFontSize: number }): Plugin => {
+ return {
+ postcssPlugin: 'plugin',
+ AtRule: {
+ media(atRule) {
+ if (!atRule.params.includes('em')) {
+ return
+ }
+
+ comments.push(
+ ...getPixelEquivalentsForMediaQuery(atRule.params).map(({ index, value }) => ({
+ index: index + atRule.source.start.offset + `@media${atRule.raws.afterName}`.length,
+ value,
+ })),
+ )
+ },
+ },
+ Declaration(decl) {
+ if (!decl.value.includes('rem')) {
return
}
- comments.push(
- ...getPixelEquivalentsForMediaQuery(atRule.params).map(({ index, value }) => ({
- index: index + atRule.source.start.offset + `@media${atRule.raws.afterName}`.length,
- value,
- })),
- )
- },
- },
- Declaration(decl) {
- if (!decl.value.includes('rem')) {
- return
- }
-
- parseValue(decl.value).walk((node) => {
- if (node.type !== 'word') {
- return true
- }
+ parseValue(decl.value).walk((node) => {
+ if (node.type !== 'word') {
+ return true
+ }
- let unit = parseValue.unit(node.value)
- if (!unit || unit.unit !== 'rem') {
- return false
- }
+ let unit = parseValue.unit(node.value)
+ if (!unit || unit.unit !== 'rem') {
+ return false
+ }
- comments.push({
- index:
- decl.source.start.offset +
- `${decl.prop}${decl.raws.between}`.length +
- node.sourceEndIndex,
- value: `${parseFloat(unit.number) * rootFontSize}px`,
- })
+ comments.push({
+ index:
+ decl.source.start.offset +
+ `${decl.prop}${decl.raws.between}`.length +
+ node.sourceEndIndex,
+ value: `${parseFloat(unit.number) * rootFontSize}px`,
+ })
- return false
- })
- },
- }
-}
-equivalentPixelValues.postcss = true
+ return false
+ })
+ },
+ }
+ },
+ {
+ postcss: true as const,
+ },
+)
diff --git a/packages/tailwindcss-language-service/src/util/resolveRange.ts b/packages/tailwindcss-language-service/src/util/resolveRange.ts
deleted file mode 100644
index d90fa5b9b..000000000
--- a/packages/tailwindcss-language-service/src/util/resolveRange.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Range } from 'vscode-languageserver'
-
-export function resolveRange(range: Range, relativeTo?: Range) {
- return {
- start: {
- line: (relativeTo?.start.line || 0) + range.start.line,
- character:
- (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) + range.start.character,
- },
- end: {
- line: (relativeTo?.start.line || 0) + range.end.line,
- character:
- (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) + range.end.character,
- },
- }
-}
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts b/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts
index b0f30891c..c84d67510 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts
@@ -7,7 +7,7 @@ import { applyComments, Comment } from '../comments'
import { getEquivalentColor } from '../colorEquivalents'
import { resolveVariableValue } from './lookup'
-export function addThemeValues(css: string, state: State, settings: TailwindCssSettings) {
+export function addThemeValues(css: string, state: State, settings: TailwindCssSettings): string {
if (!state.designSystem) return css
let comments: Comment[] = []
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/calc.ts b/packages/tailwindcss-language-service/src/util/rewriting/calc.ts
index 2420800f8..e99490146 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/calc.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/calc.ts
@@ -1,56 +1,30 @@
-function parseLength(length: string): [number, string] | null {
- let regex = /^(-?\d*\.?\d+)([a-z%]*)$/i
- let match = length.match(regex)
-
- if (!match) return null
-
- let numberPart = parseFloat(match[1])
- if (isNaN(numberPart)) return null
-
- return [numberPart, match[2]]
-}
-
-function round(n: number, precision: number): number {
- return Math.round(n * Math.pow(10, precision)) / Math.pow(10, precision)
-}
+import { stringify, tokenize } from '@csstools/css-tokenizer'
+import { isFunctionNode, parseComponentValue } from '@csstools/css-parser-algorithms'
+import { calcFromComponentValues } from '@csstools/css-calc'
export function evaluateExpression(str: string): string | null {
- // We're only interested simple calc expressions of the form
- // A + B, A - B, A * B, A / B
+ let tokens = tokenize({ css: `calc(${str})` })
- let parts = str.split(/\s+([+*/-])\s+/)
+ let components = parseComponentValue(tokens, {})
+ if (!components) return null
- if (parts.length === 1) return null
- if (parts.length !== 3) return null
+ let result = calcFromComponentValues([[components]], {
+ // Ensure evaluation of random() is deterministic
+ randomSeed: 1,
- let a = parseLength(parts[0])
- let b = parseLength(parts[2])
+ // Limit precision to keep values environment independent
+ precision: 4,
+ })
- // Not parsable
- if (!a || !b) {
- return null
- }
-
- // Addition and subtraction require the same units
- if ((parts[1] === '+' || parts[1] === '-') && a[1] !== b[1]) {
- return null
- }
-
- // Multiplication and division require at least one unit to be empty
- if ((parts[1] === '*' || parts[1] === '/') && a[1] !== '' && b[1] !== '') {
- return null
- }
+ // The result array is the same shape as the original so we're guaranteed to
+ // have an element here
+ let node = result[0][0]
- switch (parts[1]) {
- case '+':
- return round(a[0] + b[0], 4).toString() + a[1]
- case '*':
- return round(a[0] * b[0], 4).toString() + a[1]
- case '-':
- return round(a[0] - b[0], 4).toString() + a[1]
- case '/':
- return round(a[0] / b[0], 4).toString() + a[1]
+ // If we have a top-level `calc(…)` node then the evaluation did not resolve
+ // to a single value and we consider it to be incomplete
+ if (isFunctionNode(node)) {
+ if (node.name[1] === 'calc(') return null
}
- return null
+ return stringify(...node.tokens())
}
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
index 6eb840efa..5e5744982 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
@@ -2,6 +2,7 @@ import { expect, test } from 'vitest'
import {
addThemeValues,
evaluateExpression,
+ inlineThemeValues,
replaceCssCalc,
replaceCssVarsWithFallbacks,
} from './index'
@@ -24,6 +25,7 @@ test('replacing CSS variables with their fallbacks (when they have them)', () =>
let state: State = {
enabled: true,
+ features: [],
designSystem: {
theme: { prefix: null } as any,
resolveThemeValue: (name) => map.get(name) ?? null,
@@ -79,9 +81,87 @@ test('replacing CSS variables with their fallbacks (when they have them)', () =>
expect(replaceCssVarsWithFallbacks(state, 'var(--level-3)')).toBe('blue')
// Circular replacements don't cause infinite loops
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-1)')).toBe('var(--circular-3)')
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-2)')).toBe('var(--circular-1)')
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-2)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-1)')).toBe('var(--circular-1)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-2)')).toBe('var(--circular-2)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-3)')
+})
+
+test('recursive theme replacements', () => {
+ let map = new Map([
+ ['--color-a', 'var(--color-a)'],
+ ['--color-b', 'rgb(var(--color-b))'],
+ ['--color-c', 'rgb(var(--channel) var(--channel) var(--channel))'],
+ ['--channel', '255'],
+
+ ['--color-d', 'rgb(var(--indirect) var(--indirect) var(--indirect))'],
+ ['--indirect', 'var(--channel)'],
+ ['--channel', '255'],
+
+ ['--mutual-a', 'calc(var(--mutual-b) * 1)'],
+ ['--mutual-b', 'calc(var(--mutual-a) + 1)'],
+ ])
+
+ let state: State = {
+ enabled: true,
+ features: [],
+ designSystem: {
+ theme: { prefix: null } as any,
+ resolveThemeValue: (name) => map.get(name) ?? null,
+ } as DesignSystem,
+ }
+
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-a)')).toBe('var(--color-a)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-b)')).toBe('rgb(var(--color-b))')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-c)')).toBe('rgb(255 255 255)')
+
+ // This one is wrong but fixing it without breaking the infinite recursion guard is complex
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-d)')).toBe(
+ 'rgb(255 var(--indirect) var(--indirect))',
+ )
+
+ expect(replaceCssVarsWithFallbacks(state, 'var(--mutual-a)')).toBe(
+ 'calc(calc(var(--mutual-a) + 1) * 1)',
+ )
+ expect(replaceCssVarsWithFallbacks(state, 'var(--mutual-b)')).toBe(
+ 'calc(calc(var(--mutual-b) * 1) + 1)',
+ )
+})
+
+test('recursive theme replacements (inlined)', () => {
+ let map = new Map([
+ ['--color-a', 'var(--color-a)'],
+ ['--color-b', 'rgb(var(--color-b))'],
+ ['--color-c', 'rgb(var(--channel) var(--channel) var(--channel))'],
+ ['--channel', '255'],
+
+ ['--color-d', 'rgb(var(--indirect) var(--indirect) var(--indirect))'],
+ ['--indirect', 'var(--channel)'],
+ ['--channel', '255'],
+
+ ['--mutual-a', 'calc(var(--mutual-b) * 1)'],
+ ['--mutual-b', 'calc(var(--mutual-a) + 1)'],
+ ])
+
+ let state: State = {
+ enabled: true,
+ features: [],
+ designSystem: {
+ theme: { prefix: null } as any,
+ resolveThemeValue: (name) => map.get(name) ?? null,
+ } as DesignSystem,
+ }
+
+ expect(inlineThemeValues('var(--color-a)', state)).toBe('var(--color-a)')
+ expect(inlineThemeValues('var(--color-b)', state)).toBe('rgb(var(--color-b))')
+ expect(inlineThemeValues('var(--color-c)', state)).toBe('rgb(255 255 255)')
+
+ // This one is wrong but fixing it without breaking the infinite recursion guard is complex
+ expect(inlineThemeValues('var(--color-d)', state)).toBe(
+ 'rgb(255 var(--indirect) var(--indirect))',
+ )
+
+ expect(inlineThemeValues('var(--mutual-a)', state)).toBe('calc(calc(var(--mutual-a) + 1) * 1)')
+ expect(inlineThemeValues('var(--mutual-b)', state)).toBe('calc(calc(var(--mutual-b) * 1) + 1)')
})
test('Evaluating CSS calc expressions', () => {
@@ -95,6 +175,8 @@ test('Evaluating CSS calc expressions', () => {
expect(replaceCssCalc('calc(1.25 / 0.875)', (node) => evaluateExpression(node.value))).toBe(
'1.4286',
)
+
+ expect(replaceCssCalc('calc(1/4 * 100%)', (node) => evaluateExpression(node.value))).toBe('25%')
})
test('Inlining calc expressions using the design system', () => {
@@ -105,6 +187,7 @@ test('Inlining calc expressions using the design system', () => {
let state: State = {
enabled: true,
+ features: [],
designSystem: {
theme: { prefix: null } as any,
resolveThemeValue: (name) => map.get(name) ?? null,
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts b/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts
index c002ab9cb..26349f685 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts
@@ -4,16 +4,23 @@ import { evaluateExpression } from './calc'
import { resolveVariableValue } from './lookup'
import { replaceCssVars, replaceCssCalc } from './replacements'
-export function inlineThemeValues(css: string, state: State) {
+export function inlineThemeValues(css: string, state: State): string {
if (!state.designSystem) return css
+ let seen = new Set()
+
css = replaceCssCalc(css, (expr) => {
let inlined = replaceCssVars(expr.value, {
replace({ name, fallback }) {
if (!name.startsWith('--')) return null
+ // TODO: This isn't quite right as we might skip expanding a variable
+ // that should be expanded
+ if (seen.has(name)) return null
+
let value = resolveVariableValue(state.designSystem, name)
if (value === null) return fallback
+ if (value.includes('var(')) seen.add(name)
return value
},
@@ -26,8 +33,13 @@ export function inlineThemeValues(css: string, state: State) {
replace({ name, fallback }) {
if (!name.startsWith('--')) return null
+ // TODO: This isn't quite right as we might skip expanding a variable
+ // that should be expanded
+ if (seen.has(name)) return null
+
let value = resolveVariableValue(state.designSystem, name)
if (value === null) return fallback
+ if (value.includes('var(')) seen.add(name)
return value
},
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts b/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
index ae8e70e71..5f7f353e6 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
@@ -1,12 +1,12 @@
import { DesignSystem } from '../v4'
// Resolve a variable value from the design system
-export function resolveVariableValue(design: DesignSystem, name: string) {
+export function resolveVariableValue(design: DesignSystem, name: string): string | null {
let prefix = design.theme.prefix ?? null
if (prefix && name.startsWith(`--${prefix}`)) {
name = `--${name.slice(prefix.length + 3)}`
}
- return design.resolveThemeValue?.(name) ?? null
+ return design.resolveThemeValue?.(name, true) ?? null
}
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
index 728b53bf9..2eb8c918c 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
@@ -3,20 +3,39 @@ import { resolveVariableValue } from './lookup'
import { replaceCssVars } from './replacements'
export function replaceCssVarsWithFallbacks(state: State, str: string): string {
+ let seen = new Set()
+
return replaceCssVars(str, {
replace({ name, fallback }) {
// Replace with the value from the design system first. The design system
// take precedences over other sources as that emulates the behavior of a
// browser where the fallback is only used if the variable is defined.
if (state.designSystem && name.startsWith('--')) {
+ // TODO: This isn't quite right as we might skip expanding a variable
+ // that should be expanded
+ if (seen.has(name)) return null
let value = resolveVariableValue(state.designSystem, name)
- if (value !== null) return value
+ if (value !== null) {
+ if (value.includes('var(')) {
+ seen.add(name)
+ }
+
+ return value
+ }
}
if (fallback) {
return fallback
}
+ if (
+ name === '--tw-text-shadow-alpha' ||
+ name === '--tw-drop-shadow-alpha' ||
+ name === '--tw-shadow-alpha'
+ ) {
+ return '100%'
+ }
+
// Don't touch it since there's no suitable replacement
return null
},
diff --git a/packages/tailwindcss-language-service/src/util/segment.ts b/packages/tailwindcss-language-service/src/util/segment.ts
index 018485dbb..70faec525 100644
--- a/packages/tailwindcss-language-service/src/util/segment.ts
+++ b/packages/tailwindcss-language-service/src/util/segment.ts
@@ -26,7 +26,7 @@ const closingBracketStack = new Uint8Array(256)
* x x x ╰──────── Split because top-level
* ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
*/
-export function segment(input: string, separator: string) {
+export function segment(input: string, separator: string): string[] {
// SAFETY: We can use an index into a shared buffer because this function is
// synchronous, non-recursive, and runs in a single-threaded environment.
let stackPos = 0
diff --git a/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts b/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts
index e1e6f1685..ec601708a 100644
--- a/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts
+++ b/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts
@@ -8,7 +8,7 @@ export interface StringChange {
* Apply the changes to the string such that a change in the length
* of the string does not break the indexes of the subsequent changes.
*/
-export function spliceChangesIntoString(str: string, changes: StringChange[]) {
+export function spliceChangesIntoString(str: string, changes: StringChange[]): string {
// If there are no changes, return the original string
if (!changes[0]) return str
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 95afe8ec9..ecd9d0d88 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -4,6 +4,7 @@ import type { Postcss } from 'postcss'
import type { KeywordColor } from './color'
import type * as culori from 'culori'
import type { DesignSystem } from './v4'
+import type { Feature } from '../features'
export type ClassNamesTree = {
[key: string]: ClassNamesTree
@@ -46,8 +47,10 @@ export type TailwindCssSettings = {
emmetCompletions: boolean
includeLanguages: Record
classAttributes: string[]
+ classFunctions: string[]
suggestions: boolean
hovers: boolean
+ codeLens: boolean
codeActions: boolean
validate: boolean
showPixelEquivalents: boolean
@@ -62,9 +65,10 @@ export type TailwindCssSettings = {
invalidTailwindDirective: DiagnosticSeveritySetting
invalidSourceDirective: DiagnosticSeveritySetting
recommendedVariantOrder: DiagnosticSeveritySetting
+ usedBlocklistedClass: DiagnosticSeveritySetting
}
experimental: {
- classRegex: string[]
+ classRegex: string[] | [string, string][]
configFile: string | Record | null
}
files: {
@@ -140,6 +144,7 @@ export interface State {
classListContainsMetadata?: boolean
pluginVersions?: string
completionItemData?: Record
+ features: Feature[]
// postcssPlugins?: { before: any[]; after: any[] }
}
@@ -157,7 +162,7 @@ export type DocumentClassName = {
}
export type DocumentHelperFunction = {
- helper: 'theme' | 'config'
+ helper: 'theme' | 'config' | 'var'
path: string
ranges: {
full: Range
@@ -171,3 +176,110 @@ export type ClassNameMeta = {
scope: string[]
context: string[]
}
+
+/**
+ * @internal
+ */
+export function getDefaultTailwindSettings(): Settings {
+ return {
+ editor: { tabSize: 2 },
+ tailwindCSS: {
+ inspectPort: null,
+ emmetCompletions: false,
+ classAttributes: ['class', 'className', 'ngClass', 'class:list'],
+ classFunctions: [],
+ codeActions: true,
+ codeLens: true,
+ hovers: true,
+ suggestions: true,
+ validate: true,
+ colorDecorators: true,
+ rootFontSize: 16,
+ lint: {
+ cssConflict: 'warning',
+ invalidApply: 'error',
+ invalidScreen: 'error',
+ invalidVariant: 'error',
+ invalidConfigPath: 'error',
+ invalidTailwindDirective: 'error',
+ invalidSourceDirective: 'error',
+ recommendedVariantOrder: 'warning',
+ usedBlocklistedClass: 'warning',
+ },
+ showPixelEquivalents: true,
+ includeLanguages: {},
+ files: {
+ exclude: [
+ // These paths need to be universally ignorable. This means that we
+ // should only consider hidden folders with a commonly understood
+ // meaning unless there is a very good reason to do otherwise.
+ //
+ // This means that things like `build`, `target`, `cache`, etc… are
+ // not appropriate to include even though _in many cases_ they might
+ // be ignorable. The names are too general and ignoring them could
+ // cause us to ignore actual project files.
+
+ // Version Control
+ '**/.git/**',
+ '**/.hg/**',
+ '**/.svn/**',
+
+ // NPM
+ '**/node_modules/**',
+
+ // Yarn v2+ metadata & caches
+ '**/.yarn/**',
+
+ // Python Virtual Environments
+ '**/.venv/**',
+ '**/venv/**',
+
+ // Build caches
+ '**/.next/**',
+ '**/.parcel-cache/**',
+ '**/.svelte-kit/**',
+ '**/.turbo/**',
+ '**/__pycache__/**',
+ ],
+ },
+ experimental: {
+ classRegex: [],
+ configFile: null,
+ },
+ },
+ }
+}
+
+/**
+ * @internal
+ */
+export function createState(
+ partial: Omit, 'editor'> & {
+ editor?: Partial
+ },
+): State {
+ return {
+ enabled: true,
+ features: [],
+ blocklist: [],
+ ...partial,
+ editor: {
+ get connection(): Connection {
+ throw new Error('Not implemented')
+ },
+ folder: '/',
+ userLanguages: {},
+ capabilities: {
+ configuration: true,
+ diagnosticRelatedInformation: true,
+ itemDefaults: [],
+ },
+ getConfiguration: () => {
+ throw new Error('Not implemented')
+ },
+ getDocumentSymbols: async () => [],
+ readDirectory: async () => [],
+ ...partial.editor,
+ },
+ }
+}
diff --git a/packages/tailwindcss-language-service/src/util/test-utils.ts b/packages/tailwindcss-language-service/src/util/test-utils.ts
new file mode 100644
index 000000000..5b47bc96f
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/test-utils.ts
@@ -0,0 +1,67 @@
+import { createState, getDefaultTailwindSettings, Settings, State } from './state'
+import { TextDocument } from 'vscode-languageserver-textdocument'
+import type { DeepPartial } from '../types'
+import dedent, { type Dedent } from 'dedent'
+
+export const js: Dedent = dedent
+export const jsx: Dedent = dedent
+export const ts: Dedent = dedent
+export const tsx: Dedent = dedent
+export const css: Dedent = dedent
+export const html: Dedent = dedent
+export const astro: Dedent = dedent
+export const pug: Dedent = dedent
+
+export function createDocument({
+ name,
+ lang,
+ content,
+ settings,
+}: {
+ name: string
+ lang: string
+ content: string | string[]
+ settings?: DeepPartial
+}): { doc: TextDocument; state: State } {
+ let doc = TextDocument.create(
+ `file://${name}`,
+ lang,
+ 1,
+ typeof content === 'string' ? content : content.join('\n'),
+ )
+ let defaults = getDefaultTailwindSettings()
+ settings ??= {}
+ let state = createState({
+ editor: {
+ getConfiguration: async () => ({
+ ...defaults,
+ ...settings,
+ tailwindCSS: {
+ ...defaults.tailwindCSS,
+ ...settings.tailwindCSS,
+ lint: {
+ ...defaults.tailwindCSS.lint,
+ ...(settings.tailwindCSS?.lint ?? {}),
+ },
+ experimental: {
+ ...defaults.tailwindCSS.experimental,
+ ...(settings.tailwindCSS?.experimental ?? {}),
+ },
+ files: {
+ ...defaults.tailwindCSS.files,
+ ...(settings.tailwindCSS?.files ?? {}),
+ },
+ },
+ editor: {
+ ...defaults.editor,
+ ...settings.editor,
+ },
+ }),
+ },
+ })
+
+ return {
+ doc,
+ state,
+ }
+}
diff --git a/packages/tailwindcss-language-service/src/util/v4/ast.ts b/packages/tailwindcss-language-service/src/util/v4/ast.ts
index 452f35ffb..a60d79345 100644
--- a/packages/tailwindcss-language-service/src/util/v4/ast.ts
+++ b/packages/tailwindcss-language-service/src/util/v4/ast.ts
@@ -22,7 +22,7 @@ export function visit(
nodes: AstNode[],
cb: (node: AstNode, path: AstNode[]) => void,
path: AstNode[] = [],
-) {
+): void {
for (let child of nodes) {
path = [...path, child]
cb(child, path)
diff --git a/packages/tailwindcss-language-service/src/util/v4/design-system.ts b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
index cce64d4b1..13c657c33 100644
--- a/packages/tailwindcss-language-service/src/util/v4/design-system.ts
+++ b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
@@ -39,11 +39,12 @@ export interface DesignSystem {
getVariants(): VariantEntry[]
// Optional because it did not exist in earlier v4 alpha versions
- resolveThemeValue?(path: string): string | undefined
+ resolveThemeValue?(path: string, forceInline?: boolean): string | undefined
+ invalidCandidates?: Set
}
export interface DesignSystem {
dependencies(): Set
- compile(classes: string[]): postcss.Root[]
+ compile(classes: string[]): (postcss.Root | null)[]
toCss(nodes: postcss.Root | postcss.Node[]): string
}
diff --git a/packages/tailwindcss-language-service/tsconfig.json b/packages/tailwindcss-language-service/tsconfig.json
index 605ece3c6..10bf6a9f2 100644
--- a/packages/tailwindcss-language-service/tsconfig.json
+++ b/packages/tailwindcss-language-service/tsconfig.json
@@ -1,8 +1,7 @@
{
"include": ["src", "../../types"],
- "exclude": ["src/**/*.test.ts"],
"compilerOptions": {
- "module": "NodeNext",
+ "module": "ES2022",
"lib": ["ES2022"],
"target": "ES2022",
"importHelpers": true,
@@ -14,8 +13,10 @@
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
- "moduleResolution": "NodeNext",
+ "moduleResolution": "Bundler",
+ "skipLibCheck": true,
"jsx": "react",
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "isolatedDeclarations": true
}
}
diff --git a/packages/tailwindcss-language-service/vitest.config.ts b/packages/tailwindcss-language-service/vitest.config.mts
similarity index 81%
rename from packages/tailwindcss-language-service/vitest.config.ts
rename to packages/tailwindcss-language-service/vitest.config.mts
index a079f2e6c..51ef9aab2 100644
--- a/packages/tailwindcss-language-service/vitest.config.ts
+++ b/packages/tailwindcss-language-service/vitest.config.mts
@@ -3,5 +3,6 @@ import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
testTimeout: 15000,
+ silent: 'passed-only',
},
})
diff --git a/packages/tailwindcss-language-syntax/package.json b/packages/tailwindcss-language-syntax/package.json
new file mode 100644
index 000000000..c8845c851
--- /dev/null
+++ b/packages/tailwindcss-language-syntax/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@tailwindcss/language-syntax",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "test": "vitest",
+ "build": " "
+ },
+ "devDependencies": {
+ "@types/node": "^18.19.33",
+ "dedent": "^1.5.3",
+ "vitest": "^3.2.1",
+ "vscode-oniguruma": "^2.0.1",
+ "vscode-textmate": "^9.2.0"
+ }
+}
diff --git a/packages/tailwindcss-language-syntax/syntaxes/css.json b/packages/tailwindcss-language-syntax/syntaxes/css.json
new file mode 100644
index 000000000..79ef22eb4
--- /dev/null
+++ b/packages/tailwindcss-language-syntax/syntaxes/css.json
@@ -0,0 +1,1871 @@
+{
+ "__tailwind_notes__": [
+ "This was copied from VSCode and is used for testing purposes",
+ "https://github.com/microsoft/vscode/blob/2e59a779912bc1b7b2505ae52aff3a7648c24857/extensions/css/syntaxes/css.tmLanguage.json",
+ "It is covered by the MIT license:",
+ "https://github.com/microsoft/vscode/blob/2e59a779912bc1b7b2505ae52aff3a7648c24857/LICENSE.txt"
+ ],
+ "information_for_contributors": [
+ "This file has been converted from https://github.com/microsoft/vscode-css/blob/master/grammars/css.cson",
+ "If you want to provide a fix or improvement, please create a pull request against the original repository.",
+ "Once accepted there, we are happy to receive an update request."
+ ],
+ "version": "https://github.com/microsoft/vscode-css/commit/a927fe2f73927bf5c25d0b0c4dd0e63d69fd8887",
+ "name": "CSS",
+ "scopeName": "source.css",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ },
+ {
+ "include": "#escapes"
+ },
+ {
+ "include": "#combinators"
+ },
+ {
+ "include": "#selector"
+ },
+ {
+ "include": "#at-rules"
+ },
+ {
+ "include": "#rule-list"
+ }
+ ],
+ "repository": {
+ "at-rules": {
+ "patterns": [
+ {
+ "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))",
+ "end": ";|(?=$)",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.terminator.rule.css"
+ }
+ },
+ "name": "meta.at-rule.charset.css",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "invalid.illegal.not-lowercase.charset.css"
+ },
+ "2": {
+ "name": "invalid.illegal.leading-whitespace.charset.css"
+ },
+ "3": {
+ "name": "invalid.illegal.no-whitespace.charset.css"
+ },
+ "4": {
+ "name": "invalid.illegal.whitespace.charset.css"
+ },
+ "5": {
+ "name": "invalid.illegal.not-double-quoted.charset.css"
+ },
+ "6": {
+ "name": "invalid.illegal.unclosed-string.charset.css"
+ },
+ "7": {
+ "name": "invalid.illegal.unexpected-characters.charset.css"
+ }
+ },
+ "match": "(?x) # Possible errors:\n\\G\n((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive)\n|\n\\G(\\s+) # Preceding whitespace\n|\n(@charset\\S[^;]*) # No whitespace after @charset\n|\n(?<=@charset) # Before quoted charset name\n(\\x20{2,}|\\t+) # More than one space used, or a tab\n|\n(?<=@charset\\x20) # Beginning of charset name\n([^\";]+) # Not double-quoted\n|\n(\"[^\"]+$) # Unclosed quote\n|\n(?<=\") # After charset name\n([^;]+) # Unexpected junk instead of semicolon"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "keyword.control.at-rule.charset.css"
+ },
+ "2": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "match": "((@)charset)(?=\\s)"
+ },
+ {
+ "begin": "\"",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.begin.css"
+ }
+ },
+ "end": "\"|$",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.end.css"
+ }
+ },
+ "name": "string.quoted.double.css",
+ "patterns": [
+ {
+ "begin": "(?:\\G|^)(?=(?:[^\"])+$)",
+ "end": "$",
+ "name": "invalid.illegal.unclosed.string.css"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "begin": "(?i)((@)import)(?:\\s+|$|(?=['\"]|/\\*))",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.at-rule.import.css"
+ },
+ "2": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "end": ";",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.terminator.rule.css"
+ }
+ },
+ "name": "meta.at-rule.import.css",
+ "patterns": [
+ {
+ "begin": "\\G\\s*(?=/\\*)",
+ "end": "(?<=\\*/)\\s*",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ }
+ ]
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#url"
+ },
+ {
+ "include": "#media-query-list"
+ }
+ ]
+ },
+ {
+ "begin": "(?i)((@)font-face)(?=\\s*|{|/\\*|$)",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.at-rule.font-face.css"
+ },
+ "2": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "end": "(?!\\G)",
+ "name": "meta.at-rule.font-face.css",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ },
+ {
+ "include": "#escapes"
+ },
+ {
+ "include": "#rule-list"
+ }
+ ]
+ },
+ {
+ "begin": "(?i)(@)page(?=[\\s:{]|/\\*|$)",
+ "captures": {
+ "0": {
+ "name": "keyword.control.at-rule.page.css"
+ },
+ "1": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "end": "(?=\\s*($|[:{;]))",
+ "name": "meta.at-rule.page.css",
+ "patterns": [
+ {
+ "include": "#rule-list"
+ }
+ ]
+ },
+ {
+ "begin": "(?i)(?=@media(\\s|\\(|/\\*|$))",
+ "end": "(?<=})(?!\\G)",
+ "patterns": [
+ {
+ "begin": "(?i)\\G(@)media",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword.control.at-rule.media.css"
+ },
+ "1": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "end": "(?=\\s*[{;])",
+ "name": "meta.at-rule.media.header.css",
+ "patterns": [
+ {
+ "include": "#media-query-list"
+ }
+ ]
+ },
+ {
+ "begin": "{",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.section.media.begin.bracket.curly.css"
+ }
+ },
+ "end": "}",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.section.media.end.bracket.curly.css"
+ }
+ },
+ "name": "meta.at-rule.media.body.css",
+ "patterns": [
+ {
+ "include": "$self"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "begin": "(?i)(?=@counter-style([\\s'\"{;]|/\\*|$))",
+ "end": "(?<=})(?!\\G)",
+ "patterns": [
+ {
+ "begin": "(?i)\\G(@)counter-style",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword.control.at-rule.counter-style.css"
+ },
+ "1": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "end": "(?=\\s*{)",
+ "name": "meta.at-rule.counter-style.header.css",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ },
+ {
+ "include": "#escapes"
+ },
+ {
+ "captures": {
+ "0": {
+ "patterns": [
+ {
+ "include": "#escapes"
+ }
+ ]
+ }
+ },
+ "match": "(?x)\n(?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter\n(?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n |\\\\(?:[0-9a-fA-F]{1,6}|.)\n)*",
+ "name": "variable.parameter.style-name.css"
+ }
+ ]
+ },
+ {
+ "begin": "{",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.section.property-list.begin.bracket.curly.css"
+ }
+ },
+ "end": "}",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.section.property-list.end.bracket.curly.css"
+ }
+ },
+ "name": "meta.at-rule.counter-style.body.css",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ },
+ {
+ "include": "#escapes"
+ },
+ {
+ "include": "#rule-list-innards"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "begin": "(?i)(?=@document([\\s'\"{;]|/\\*|$))",
+ "end": "(?<=})(?!\\G)",
+ "patterns": [
+ {
+ "begin": "(?i)\\G(@)document",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword.control.at-rule.document.css"
+ },
+ "1": {
+ "name": "punctuation.definition.keyword.css"
+ }
+ },
+ "end": "(?=\\s*[{;])",
+ "name": "meta.at-rule.document.header.css",
+ "patterns": [
+ {
+ "begin": "(?i)(?>>",
+ "name": "invalid.deprecated.combinator.css"
+ },
+ {
+ "match": ">>|>|\\+|~",
+ "name": "keyword.operator.combinator.css"
+ }
+ ]
+ },
+ "commas": {
+ "match": ",",
+ "name": "punctuation.separator.list.comma.css"
+ },
+ "comment-block": {
+ "begin": "/\\*",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.comment.begin.css"
+ }
+ },
+ "end": "\\*/",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.comment.end.css"
+ }
+ },
+ "name": "comment.block.css"
+ },
+ "escapes": {
+ "patterns": [
+ {
+ "match": "\\\\[0-9a-fA-F]{1,6}",
+ "name": "constant.character.escape.codepoint.css"
+ },
+ {
+ "begin": "\\\\$\\s*",
+ "end": "^(?<:=]|\\)|/\\*) # Terminates cleanly"
+ },
+ "media-feature-keywords": {
+ "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)",
+ "name": "support.constant.property-value.css"
+ },
+ "media-query": {
+ "begin": "\\G",
+ "end": "(?=\\s*[{;])",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ },
+ {
+ "include": "#escapes"
+ },
+ {
+ "include": "#media-types"
+ },
+ {
+ "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)",
+ "name": "keyword.operator.logical.$1.media.css"
+ },
+ {
+ "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)",
+ "name": "keyword.operator.logical.and.media.css"
+ },
+ {
+ "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))",
+ "name": "invalid.illegal.comma.css"
+ },
+ {
+ "include": "#commas"
+ },
+ {
+ "begin": "\\(",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.parameters.begin.bracket.round.css"
+ }
+ },
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.parameters.end.bracket.round.css"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#media-features"
+ },
+ {
+ "include": "#media-feature-keywords"
+ },
+ {
+ "match": ":",
+ "name": "punctuation.separator.key-value.css"
+ },
+ {
+ "match": ">=|<=|=|<|>",
+ "name": "keyword.operator.comparison.css"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "constant.numeric.css"
+ },
+ "2": {
+ "name": "keyword.operator.arithmetic.css"
+ },
+ "3": {
+ "name": "constant.numeric.css"
+ }
+ },
+ "match": "(\\d+)\\s*(/)\\s*(\\d+)",
+ "name": "meta.ratio.css"
+ },
+ {
+ "include": "#numeric-values"
+ },
+ {
+ "include": "#comment-block"
+ }
+ ]
+ }
+ ]
+ },
+ "media-query-list": {
+ "begin": "(?=\\s*[^{;])",
+ "end": "(?=\\s*[{;])",
+ "patterns": [
+ {
+ "include": "#media-query"
+ }
+ ]
+ },
+ "media-types": {
+ "captures": {
+ "1": {
+ "name": "support.constant.media.css"
+ },
+ "2": {
+ "name": "invalid.deprecated.constant.media.css"
+ }
+ },
+ "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)"
+ },
+ "numeric-values": {
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.constant.css"
+ }
+ },
+ "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b",
+ "name": "constant.other.color.rgb-value.hex.css"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "keyword.other.unit.percentage.css"
+ },
+ "2": {
+ "name": "keyword.other.unit.${2:/downcase}.css"
+ }
+ },
+ "match": "(?xi) (?+~|] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%&(*;@^`|\\]}] # - NOTE: We exempt `)` from the list of checked\n | # symbols to avoid matching `:not(.invalid)`\n / (?!\\*) # - Avoid invalidating the start of a comment\n )+\n )\n # Mark remainder of selector invalid\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Otherwise valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n)",
+ "name": "invalid.illegal.bad-identifier.css"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.entity.css"
+ },
+ "2": {
+ "patterns": [
+ {
+ "include": "#escapes"
+ }
+ ]
+ }
+ },
+ "match": "(?x)\n(\\.) # Valid class-name\n(\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n )+\n) # Followed by either:\n(?= $ # - End of the line\n | [\\s,.\\#)\\[:{>+~|] # - Another selector\n | /\\* # - A block comment\n)",
+ "name": "entity.other.attribute-name.class.css"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.entity.css"
+ },
+ "2": {
+ "patterns": [
+ {
+ "include": "#escapes"
+ }
+ ]
+ }
+ },
+ "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|]|/\\*)",
+ "name": "entity.other.attribute-name.id.css"
+ },
+ {
+ "begin": "\\[",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.entity.begin.bracket.square.css"
+ }
+ },
+ "end": "\\]",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.entity.end.bracket.square.css"
+ }
+ },
+ "name": "meta.attribute-selector.css",
+ "patterns": [
+ {
+ "include": "#comment-block"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "storage.modifier.ignore-case.css"
+ }
+ },
+ "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "string.unquoted.attribute-value.css",
+ "patterns": [
+ {
+ "include": "#escapes"
+ }
+ ]
+ }
+ },
+ "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)"
+ },
+ {
+ "include": "#escapes"
+ },
+ {
+ "match": "[~|^$*]?=",
+ "name": "keyword.operator.pattern.css"
+ },
+ {
+ "match": "\\|",
+ "name": "punctuation.separator.css"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "entity.other.namespace-prefix.css",
+ "patterns": [
+ {
+ "include": "#escapes"
+ }
+ ]
+ }
+ },
+ "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "entity.other.attribute-name.css",
+ "patterns": [
+ {
+ "include": "#escapes"
+ }
+ ]
+ }
+ },
+ "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)"
+ }
+ ]
+ },
+ {
+ "include": "#pseudo-classes"
+ },
+ {
+ "include": "#pseudo-elements"
+ },
+ {
+ "include": "#functional-pseudo-classes"
+ },
+ {
+ "match": "(?x) (?\\s,.\\#|){:\\[]|/\\*|$)",
+ "name": "entity.name.tag.css"
+ },
+ "unicode-range": {
+ "captures": {
+ "0": {
+ "name": "constant.other.unicode-range.css"
+ },
+ "1": {
+ "name": "punctuation.separator.dash.unicode-range.css"
+ }
+ },
+ "match": "(? extends Map {
+ constructor(private factory: (key: T, self: DefaultMap) => V) {
+ super()
+ }
+
+ get(key: T): V {
+ let value = super.get(key)
+
+ if (value === undefined) {
+ value = this.factory(key, this)
+ this.set(key, value)
+ }
+
+ return value
+ }
+}
diff --git a/packages/tailwindcss-language-syntax/tests/scopes.ts b/packages/tailwindcss-language-syntax/tests/scopes.ts
new file mode 100644
index 000000000..eec85ef51
--- /dev/null
+++ b/packages/tailwindcss-language-syntax/tests/scopes.ts
@@ -0,0 +1,41 @@
+export interface ScopeEntry {
+ content: Promise<{ default: object }>
+ inject: string[]
+}
+
+export const KNOWN_SCOPES: Record = {
+ 'source.css': {
+ content: import('../syntaxes/css.json'),
+ inject: [
+ 'tailwindcss.at-rules.injection',
+ 'tailwindcss.at-apply.injection',
+ 'tailwindcss.theme-fn.injection',
+ 'tailwindcss.screen-fn.injection',
+ ],
+ },
+
+ 'source.css.tailwind': {
+ content: import('../../vscode-tailwindcss/syntaxes/source.css.tailwind.tmLanguage.json'),
+ inject: [],
+ },
+
+ 'tailwindcss.at-apply.injection': {
+ content: import('../../vscode-tailwindcss/syntaxes/at-apply.tmLanguage.json'),
+ inject: [],
+ },
+
+ 'tailwindcss.at-rules.injection': {
+ content: import('../../vscode-tailwindcss/syntaxes/at-rules.tmLanguage.json'),
+ inject: [],
+ },
+
+ 'tailwindcss.theme-fn.injection': {
+ content: import('../../vscode-tailwindcss/syntaxes/theme-fn.tmLanguage.json'),
+ inject: [],
+ },
+
+ 'tailwindcss.screen-fn.injection': {
+ content: import('../../vscode-tailwindcss/syntaxes/screen-fn.tmLanguage.json'),
+ inject: [],
+ },
+}
diff --git a/packages/tailwindcss-language-syntax/tests/syntax.test.ts b/packages/tailwindcss-language-syntax/tests/syntax.test.ts
new file mode 100644
index 000000000..20941760a
--- /dev/null
+++ b/packages/tailwindcss-language-syntax/tests/syntax.test.ts
@@ -0,0 +1,297 @@
+import { test } from 'vitest'
+import dedent, { type Dedent } from 'dedent'
+import { loadGrammar } from './utils'
+
+const css: Dedent = dedent
+
+let grammar = await loadGrammar()
+
+test('@theme', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @theme {
+ --color: red;
+ }
+ @theme static {
+ --color: red;
+ }
+ @theme inline deprecated {
+ --color: red;
+ }
+ @theme prefix(tw) inline {
+ --color: red;
+ }
+
+ @theme {
+ --spacing: initial;
+ --color-*: initial;
+ --animate-pulse: 1s pulse infinite;
+
+ @keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ }
+ }
+
+ @theme {
+ /** Comment 0 */
+
+ /** Comment 1 */
+ --color-1: red;
+
+ /** Comment 2 */
+ --color-2: green;
+
+ /** Comment 3 */
+ --color-2: blue;
+
+ /** Comment 4 */
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@import', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @import './test.css';
+
+ @import './test.css' prefix(tw);
+ @import './test.css' layer(utilities) prefix(tw);
+
+ @import './test.css' source(none);
+ @import './test.css' source('./foo');
+ @import './test.css' layer(utilities) source('./foo');
+
+ @import './test.css' theme(static);
+ @import './test.css' theme(static default inline);
+ @import './test.css' theme(reference deprecated);
+ @import './test.css' theme(prefix(tw) reference);
+ @import './test.css' theme(default invalid reference);
+
+ @reference './test.css';
+
+ @reference './test.css' prefix(tw);
+ @reference './test.css' layer(utilities) prefix(tw);
+
+ @reference './test.css' source(none);
+ @reference './test.css' source('./foo');
+ @reference './test.css' layer(utilities) source('./foo');
+
+ @reference './test.css' theme(static);
+ @reference './test.css' theme(static default inline);
+ @reference './test.css' theme(reference deprecated);
+ @reference './test.css' theme(prefix(tw) reference);
+ @reference './test.css' theme(default invalid reference);
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@plugin statement', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @plugin "./foo";
+ @plugin "./bar";
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@plugin with options', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @import 'tailwindcss';
+ @plugin "testing" {
+ color: red;
+ }
+
+ html,
+ body {
+ color: red;
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@config statement', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @config "./foo";
+ @config "./bar";
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@tailwind', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ @tailwind utilities source(none);
+ @tailwind utilities source("./**/*");
+ @tailwind screens;
+ @tailwind variants;
+ @tailwind unknown;
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@source', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @source "./dir";
+ @source "./file.ts";
+ @source "./dir/**/file-{a,b}.ts";
+ @source not "./dir";
+ @source not "./file.ts";
+ @source not "./dir/**/file-{a,b}.ts";
+
+ @source inline("flex");
+ @source inline("flex bg-red-{50,{100..900..100},950}");
+ @source not inline("flex");
+ @source not inline("flex bg-red-{50,{100..900..100},950}");
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@layer', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @layer theme, base, components, utilities;
+ @layer utilities {
+ .custom {
+ width: 12px;
+ }
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@utility', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @utility custom {
+ width: 12px;
+ }
+
+ @utility functional-* {
+ width: calc(--value(number) * 1px);
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('--value(…)', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @utility functional-* {
+ width: --value(
+ --size,
+ 'literal',
+ integer,
+ number,
+ percentage,
+ ratio,
+ [integer],
+ [number],
+ [percentage],
+ [ratio]
+ );
+
+ height: --modifier(
+ --size,
+ 'literal',
+ integer,
+ number,
+ percentage,
+ ratio,
+ [integer],
+ [number],
+ [percentage],
+ [ratio]
+ );
+
+ color: --alpha(--value([color]) / --modifier(number));
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@variant', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @variant dark {
+ .foo {
+ color: white;
+ }
+ }
+
+ .bar {
+ @variant dark {
+ color: white;
+ }
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('@custom-variant', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @custom-variant dark (&:is(.dark, .dark *));
+ @custom-variant dark {
+ &:is(.dark, .dark *) {
+ @slot;
+ }
+ }
+ @custom-variant around {
+ color: '';
+ &::before,
+ &::after {
+ @slot;
+ }
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('legacy: @responsive', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @responsive {
+ .foo {
+ color: red;
+ }
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('legacy: @variants', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @variants hover, focus {
+ .foo {
+ color: red;
+ }
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
+
+test('legacy: @screen', async ({ expect }) => {
+ let result = await grammar.tokenize(css`
+ @screen sm {
+ .foo {
+ color: red;
+ }
+ }
+ `)
+
+ expect(result.toString()).toMatchSnapshot()
+})
diff --git a/packages/tailwindcss-language-syntax/tests/utils.ts b/packages/tailwindcss-language-syntax/tests/utils.ts
new file mode 100644
index 000000000..cd6d3ccd1
--- /dev/null
+++ b/packages/tailwindcss-language-syntax/tests/utils.ts
@@ -0,0 +1,150 @@
+import { readFile } from 'node:fs/promises'
+import path from 'node:path'
+import vsctm from 'vscode-textmate'
+import oniguruma from 'vscode-oniguruma'
+import { createRequire } from 'node:module'
+import { fileURLToPath } from 'node:url'
+import { KNOWN_SCOPES } from './scopes'
+import { DefaultMap } from './default-map'
+
+const require = createRequire(import.meta.url)
+
+export interface TokenizeResult {
+ toString(): string
+}
+
+export interface TokenizedScope {
+ /**
+ * The name of the scope
+ */
+ name: string
+
+ /**
+ * An ordered list of ranges as they appear, one per token
+ */
+ ranges: [start: number, end: number][]
+}
+
+export interface Grammar {
+ tokenize(text: string, scope?: string): Promise
+}
+
+// 1. Each line has a list of scopes
+// 2. Each scope has a set of ranges, one per token
+// 3. If two consecutive scopes have identical range lists they can be merged
+
+// @utility custom {
+// ^^^^^^^^^^^^^^^^^ 11 tok: source.css.tailwind
+// ^^^^^^^^ 2 tok: keyword.control.at-rule.utility.tailwind
+// ^ 1 tok: punctuation.definition.keyword.css
+// ^^^^^^ 6 tok: variable.parameter.utility.tailwind
+// ^ 1 tok: meta.at-rule.utility.body.tailwind punctuation.section.utility.begin.bracket.curly.tailwind
+
+function tokenizeText(grammar: vsctm.IGrammar, text: string): TokenizeResult {
+ let str = ''
+
+ let results: [string, vsctm.ITokenizeLineResult][] = []
+
+ let ruleStack = vsctm.INITIAL
+ let maxEndIndex = 0
+ for (let line of text.split(/\r\n|\r|\n/g)) {
+ let result = grammar.tokenizeLine(line, ruleStack)
+ ruleStack = result.ruleStack
+ maxEndIndex = Math.max(maxEndIndex, ...result.tokens.map((t) => t.endIndex))
+ results.push([line, result])
+ }
+
+ for (let [line, result] of results) {
+ // 1. Collect the scope information for this line
+ let scopes = new DefaultMap((name) => ({ name, ranges: [] }))
+
+ for (let token of result.tokens) {
+ let range = [token.startIndex, token.endIndex] as [number, number]
+ for (let name of token.scopes) {
+ scopes.get(name).ranges.push(range)
+ }
+ }
+
+ let maxTokenCount = Math.max(...Array.from(scopes.values(), (s) => s.ranges.length))
+ let tokenCountSpace = Math.max(2, maxTokenCount.toString().length)
+
+ // 2. Write information to the output
+ str += '\n'
+ str += line
+
+ let lastRangeKey = ''
+
+ for (let scope of scopes.values()) {
+ let currentRangeKey = scope.ranges.map((r) => `${r[0]}:${r[1]}`).join(',')
+ if (lastRangeKey === currentRangeKey) {
+ str += ' '
+ str += scope.name
+ continue
+ }
+ lastRangeKey = currentRangeKey
+
+ str += '\n'
+
+ let lastRangeEnd = 0
+ for (let range of scope.ranges) {
+ str += ' '.repeat(range[0] - lastRangeEnd)
+ str += '^'.repeat(range[1] - range[0])
+ lastRangeEnd = range[1]
+ }
+ str += ' '.repeat(maxEndIndex - lastRangeEnd)
+
+ str += ' '
+ str += scope.ranges.length.toString().padStart(tokenCountSpace)
+ str += ': '
+ str += scope.name
+ }
+
+ str += '\n'
+ }
+
+ return {
+ toString: () => str,
+ }
+}
+
+export async function loadGrammar() {
+ let wasm = await readFile(require.resolve('vscode-oniguruma/release/onig.wasm'))
+ await oniguruma.loadWASM(wasm)
+
+ let registry = new vsctm.Registry({
+ onigLib: Promise.resolve({
+ createOnigScanner: (patterns) => new oniguruma.OnigScanner(patterns),
+ createOnigString: (s) => new oniguruma.OnigString(s),
+ }),
+
+ async loadGrammar(scope) {
+ let meta = KNOWN_SCOPES[scope]
+ if (!meta) throw new Error(`Unknown scope name: ${scope}`)
+
+ let grammar = await meta.content.then((m) => m.default)
+
+ return vsctm.parseRawGrammar(JSON.stringify(grammar), `${scope}.json`)
+ },
+
+ getInjections(scope) {
+ let parts = scope.split('.')
+
+ let injections: string[] = []
+ for (let i = 1; i <= parts.length; i++) {
+ let subscope = parts.slice(0, i).join('.')
+ injections.push(...(KNOWN_SCOPES[subscope]?.inject ?? []))
+ }
+
+ return injections
+ },
+ })
+
+ async function tokenize(text: string, scope?: string): Promise {
+ let grammar = await registry.loadGrammar(scope ?? 'source.css.tailwind')
+ return tokenizeText(grammar, text)
+ }
+
+ return {
+ tokenize,
+ }
+}
diff --git a/packages/tailwindcss-language-syntax/tsconfig.json b/packages/tailwindcss-language-syntax/tsconfig.json
new file mode 100755
index 000000000..7d01f0c54
--- /dev/null
+++ b/packages/tailwindcss-language-syntax/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "target": "ES2022",
+ "lib": ["ES2022"],
+ "rootDir": "..",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "allowJs": true,
+ "resolveJsonModule": true,
+ "baseUrl": ".."
+ },
+ "include": ["tests"]
+}
diff --git a/packages/tailwindcss-language-server/vitest.config.ts b/packages/tailwindcss-language-syntax/vitest.config.mts
similarity index 55%
rename from packages/tailwindcss-language-server/vitest.config.ts
rename to packages/tailwindcss-language-syntax/vitest.config.mts
index c0105d1b1..51ef9aab2 100644
--- a/packages/tailwindcss-language-server/vitest.config.ts
+++ b/packages/tailwindcss-language-syntax/vitest.config.mts
@@ -1,11 +1,8 @@
import { defineConfig } from 'vitest/config'
-import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
test: {
testTimeout: 15000,
- css: true,
+ silent: 'passed-only',
},
-
- plugins: [tsconfigPaths()],
})
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index be1972501..9cb9b04e8 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,121 @@
## Prerelease
+- Nothing yet!
+
+## 0.14.23
+
+- Highlight CSS variables correctly inside `@theme` ([#1409](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1409))
+- Highlight comments inside `@theme` ([#1409](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1409))
+- Highlight at-rules inside `@theme` ([#1409](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1409))
+- Detect class functions and class attributes inside Astro code fences ([#1386](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1386))
+
+## 0.14.22
+
+- Fix matching files when config is not in the workspace root ([#1412](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1412))
+
+## 0.14.21
+
+- Bump bundled CSS language service ([#1395](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1395))
+- Fix high CPU usage when given non-file URI workspace folders ([#1396](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1396))
+- Ignore workspace folders that are the filesystem root ([#1396](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1396))
+- Fix infinite loop when resolving completion details with recursive theme keys ([#1400](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1400))
+- Simplify completion details for more utilities ([#1397](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1397))
+- Improve project stylesheet detection ([#1401](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1401))
+
+## 0.14.20
+
+- Simplify completion details for border and outline utilities ([#1384](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1384))
+- Fix error initializing a new project when editing a CSS file ([#1387](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1387))
+- Improve syntax highlighting for CSS ([#1367](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1367))
+
+# 0.14.19
+
+- Speed up project selector matching in large projects ([#1381](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1381))
+
+# 0.14.18
+
+- Display color swatches when using `before`/`after` variants ([#1374](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1374))
+- Clear trigger characters when restarting server ([#1375](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1375))
+- Don't register ability to hover, request colors, etc… more than once ([#1378](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1378))
+
+# 0.14.17
+
+- Improve dynamic capability registration in the language server ([#1327](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1327))
+- Ignore Python virtual env directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
+- Ignore Yarn v2+ metadata & cache directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
+- Ignore some build caches by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
+- Gracefully handle color parsing failures ([#1363](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1363))
+- Calculate swatches for HSL colors with angular units ([#1360](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1360))
+- Fix error when using VSCode < 1.78 ([#1353](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1353))
+- Don’t skip suggesting empty variant implementations ([#1352](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1352))
+- Handle helper function lookups in nested parens ([#1354](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1354))
+- Hide `@property` declarations from completion details ([#1356](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1356))
+- Hide variant-provided declarations from completion details for a utility ([#1356](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1356))
+- Compute correct document selectors when a project is initialized ([#1335](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1335))
+- Fix matching of some content file paths on Windows ([#1335](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1335))
+
+# 0.14.16
+
+- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
+- Support class function hovers in Svelte and HTML `` blocks ([#1311](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1311))
+- Evaluate complex `calc(…)` expressions in completions and equivalents ([#1316](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1316))
+- Guard against recursive theme key lookup ([#1332](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1332))
+
+# 0.14.15
+
+- Prevent infinite loop when any file exclusion starts with `/` ([#1307](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1307))
+
+# 0.14.14
+
+- Only scan the file system once when needed ([#1287](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1287))
+- Don't follow recursive symlinks when searching for projects ([#1270](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1270))
+- Correctly re-create a project when its main config file is removed then re-created ([#1300](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1300))
+- Bump `@parcel/watcher` used by the language server ([#1269](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1269))
+
+# 0.14.13
+
+- Hide completions from CSS language server inside `@import "…" source(…)` ([#1091](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1091))
+- Bump bundled v4 fallback to v4.1.1 ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294))
+- Show color swatches for most new v4.1 utilities ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294))
+- Support theme key hovers in the CSS `var()` function ([#1289](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1289))
+- Show theme key hovers inside `@theme` for better context and syntax highlighting ([#1289](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1289))
+
+# 0.14.12
+
+- Fix content detection when using v4.0+ ([#1280](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1280))
+- Ensure file exclusions always work on Windows ([#1281](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1281))
+- Prep for new Oxide API in v4.1 ([#1284](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1284))
+- Handle negated sources during project discovery in v4.1 ([#1288](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1288))
+
+# 0.14.11
+
+- Fix completions not showing for some class attributes when a class function exists in the document ([#1278](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1278))
+
+# 0.14.10
+
+- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258), [#1272](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1272))
+- v4: Make sure completions show after variants using arbitrary and bare values ([#1263](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1263))
+- v4: Add support for upcoming `@source not` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
+- v4: Add support for upcoming `@source inline(…)` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
+- LSP: Refresh internal caches when settings are updated ([#1273](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1273))
+- LSP: Improve error message when a workspace folder does not exist or is inaccesible to the current user ([#1276](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1276))
+- v4: Show theme key completions in `var(…)` in CSS ([#1274](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1274))
+
+# 0.14.9
+
+- v4: Support loading bundled versions of some first-party plugins ([#1240](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1240))
+- Cancel initial file search if it takes too long ([#1242](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1242))
+- LSP: Don’t throw when the client does not provide `textDocument` in capabilities ([#1252](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1252))
+- v4: Allow `*` anywhere in a CSS variable name ([#1256](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1256))
+
+# 0.14.8
+
+- Don't throw when requiring() packages that resolve to a path containing a `#` character ([#1235](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1235))
+- Fix syntax error when resetting multi-word theme key namespaces ([#1237](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1237))
+
+# 0.14.7
+
- LSP: Declare capability for handling workspace folder change notifications ([#1223](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1223))
- Don't throw when resolving paths containing a `#` character ([#1225](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1225))
- Show `@theme` in symbol list in CSS language mode ([#1227](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1227))
diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md
index 621653085..6ce777d1b 100644
--- a/packages/vscode-tailwindcss/README.md
+++ b/packages/vscode-tailwindcss/README.md
@@ -6,7 +6,11 @@ Tailwind CSS IntelliSense enhances the Tailwind development experience by provid
**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**
-In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation) and a [Tailwind config file](https://tailwindcss.com/docs/installation#create-your-configuration-file) named `tailwind.config.{js,cjs,mjs,ts,cts,mts}` in your workspace.
+In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation) and one of these:
+
+- For v4 and later, a `.css` file that imports a Tailwind CSS stylesheet (e.g. `@import "tailwindcss"`)
+- For v3 and earlier, a [Tailwind CSS config file](https://v3.tailwindcss.com/docs/configuration#creating-your-configuration-file) named `tailwind.config.{js,cjs,mjs,ts,cts,mts}` in your workspace.
+- For v3 and earlier, a stylesheet that points to a config file via `@config`
## Features
@@ -90,6 +94,29 @@ Enable completions when using [Emmet](https://emmet.io/)-style syntax, for examp
The HTML attributes for which to provide class completions, hover previews, linting etc. **Default: `class`, `className`, `ngClass`, `class:list`**
+### `tailwindCSS.classFunctions`
+
+Functions in which to provide completions, hover previews, linting etc. Currently, this works for both function calls and tagged template literals in JavaScript / TypeScript.
+
+Each entry is treated as regex pattern that matches on a function name. You *cannot* match on content before or after the function name — matches are limited to function names only.
+
+Example:
+
+```json
+{
+ "tailwindCSS.classFunctions": ["tw", "clsx", "tw\\.[a-z-]+"]
+}
+```
+
+```javascript
+let classes = tw`flex bg-red-500`
+let classes2 = clsx([
+ "flex bg-red-500",
+ { "text-red-500": true }
+])
+let element = tw.div`flex bg-red-500`
+```
+
### `tailwindCSS.colorDecorators`
Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions. **Default: `true`**
@@ -152,6 +179,10 @@ Class names on the same HTML element which apply the same CSS property or proper
Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only). **Default: `warning`**
+#### `tailwindCSS.lint.usedBlocklistedClass`
+
+Usage of class names that have been blocklisted via `@source not inline(…)`. **Default: `warning`**
+
### `tailwindCSS.inspectPort`
Enable the Node.js inspector agent for the language server and listen on the specified port. **Default: `null`**
@@ -213,7 +244,9 @@ For projects with multiple config files, use an object where each key is a confi
If you’re having issues getting the IntelliSense features to activate, there are a few things you can check:
-- Ensure that you have a Tailwind config file in your workspace and that this is named `tailwind.config.{js,cjs,mjs,ts,cts,mts}`. Check out the Tailwind documentation for details on [creating a config file](https://tailwindcss.com/docs/configuration#creating-your-configuration-file).
-- Ensure that the `tailwindcss` module is installed in your workspace, via `npm`, `yarn`, or `pnpm`.
-- Make sure your VS Code settings aren’t causing your Tailwind config file to be hidden/ignored, for example via the `files.exclude` or `files.watcherExclude` settings.
+- You must have `tailwindcss` installed in your workspace via `npm`, `pnpm`, or `yarn`. The extension will then attempt to detect your Tailwind CSS configuration, which can be located in one of the following:
+ - For Tailwind CSS **v4** projects, configuration defined directly within your main CSS file using directives like `@import "tailwindcss";` and `@theme { ... }`. Preprocessor files like Less, Sass, or Stylus are not supported. A `.css` file is **required** for IntelliSense to function.
+ - For Tailwind CSS **v3 and earlier**, a Tailwind CSS config file in your workspace whose name matches (`tailwind.config.{js,cjs,mjs,ts,cts,mts}`), or a stylesheet that points to a config file via `@config`.
+- Make sure your VS Code settings aren’t causing your stylesheet or your Tailwind CSS config file to be hidden/ignored, for example via the `files.exclude`, `files.watcherExclude`, or `tailwindCSS.files.exclude` settings.
- Take a look at the language server output by running the `Tailwind CSS: Show Output` command from the command palette. This may show errors that are preventing the extension from activating.
+- For projects with multiple installations of Tailwind CSS, multiple config files, or several stylesheets with `@import "tailwindcss"` we recommend using the `tailwindCSS.experimental.configFile` setting to explicitly state your stylesheet or config paths.
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 02cd21b78..6d43fff66 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.6",
+ "version": "0.14.23",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
@@ -184,6 +184,14 @@
],
"markdownDescription": "The HTML attributes for which to provide class completions, hover previews, linting etc."
},
+ "tailwindCSS.classFunctions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": [],
+ "markdownDescription": "The function or tagged template literal names for which to provide class completions, hover previews, linting etc."
+ },
"tailwindCSS.suggestions": {
"type": "boolean",
"default": true,
@@ -202,6 +210,12 @@
"markdownDescription": "Enable code actions.",
"scope": "language-overridable"
},
+ "tailwindCSS.codeLens": {
+ "type": "boolean",
+ "default": true,
+ "markdownDescription": "Enable code lens.",
+ "scope": "language-overridable"
+ },
"tailwindCSS.colorDecorators": {
"type": "boolean",
"default": true,
@@ -291,6 +305,17 @@
"markdownDescription": "Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only)",
"scope": "language-overridable"
},
+ "tailwindCSS.lint.usedBlocklistedClass": {
+ "type": "string",
+ "enum": [
+ "ignore",
+ "warning",
+ "error"
+ ],
+ "default": "warning",
+ "markdownDescription": "Usage of class names that have been blocklisted via `@source not inline(…)`",
+ "scope": "language-overridable"
+ },
"tailwindCSS.experimental.classRegex": {
"type": "array",
"scope": "language-overridable"
@@ -357,14 +382,14 @@
"@vscode/vsce": "2.21.1",
"braces": "3.0.3",
"color-name": "1.1.4",
- "concurrently": "7.0.0",
- "esbuild": "^0.25.0",
+ "concurrently": "9.1.2",
+ "esbuild": "^0.25.5",
"minimist": "^1.2.8",
"move-file-cli": "3.0.0",
"normalize-path": "3.0.0",
"picomatch": "^4.0.1",
"rimraf": "3.0.2",
- "typescript": "5.3.3",
+ "typescript": "5.8.3",
"vscode-languageclient": "8.0.2"
}
}
diff --git a/packages/vscode-tailwindcss/src/analyze.ts b/packages/vscode-tailwindcss/src/analyze.ts
new file mode 100644
index 000000000..ec9b2b9a3
--- /dev/null
+++ b/packages/vscode-tailwindcss/src/analyze.ts
@@ -0,0 +1,93 @@
+import { workspace, RelativePattern, CancellationToken, Uri, WorkspaceFolder } from 'vscode'
+import braces from 'braces'
+import { CONFIG_GLOB, CSS_GLOB } from '@tailwindcss/language-server/src/lib/constants'
+import { getExcludePatterns } from './exclusions'
+
+export interface SearchOptions {
+ folders: readonly WorkspaceFolder[]
+ token: CancellationToken
+}
+
+export async function anyWorkspaceFoldersNeedServer({ folders, token }: SearchOptions) {
+ // An explicit config file setting means we need the server
+ for (let folder of folders) {
+ let settings = workspace.getConfiguration('tailwindCSS', folder)
+ let configFilePath = settings.get('experimental.configFile')
+
+ // No setting provided
+ if (!configFilePath) continue
+
+ // Ths config file may be a string:
+ // A path pointing to a CSS or JS config file
+ if (typeof configFilePath === 'string') return true
+
+ // Ths config file may be an object:
+ // A map of config files to one or more globs
+ //
+ // If we get an empty object the language server will do a search anyway so
+ // we'll act as if no option was passed to be consistent
+ if (typeof configFilePath === 'object' && Object.values(configFilePath).length > 0) return true
+ }
+
+ let configs: Array<() => Thenable> = []
+ let stylesheets: Array<() => Thenable> = []
+
+ for (let folder of folders) {
+ let exclusions = getExcludePatterns(folder).flatMap((pattern) => braces.expand(pattern))
+ let exclude = `{${exclusions.join(',').replace(/{/g, '%7B').replace(/}/g, '%7D')}}`
+
+ configs.push(() =>
+ workspace.findFiles(
+ new RelativePattern(folder, `**/${CONFIG_GLOB}`),
+ exclude,
+ undefined,
+ token,
+ ),
+ )
+
+ stylesheets.push(() =>
+ workspace.findFiles(new RelativePattern(folder, `**/${CSS_GLOB}`), exclude, undefined, token),
+ )
+ }
+
+ // If we find a config file then we need the server
+ let configUrls = await Promise.all(configs.map((fn) => fn()))
+ for (let group of configUrls) {
+ if (group.length > 0) {
+ return true
+ }
+ }
+
+ // If we find a possibly-related stylesheet then we need the server
+ // The step is done last because it requires reading individual files
+ // to determine if the server should be started.
+ //
+ // This is also, unfortunately, prone to starting the server unncessarily
+ // in projects that don't use TailwindCSS so we do this one-by-one instead
+ // of all at once to keep disk I/O low.
+ let stylesheetUrls = await Promise.all(stylesheets.map((fn) => fn()))
+ for (let group of stylesheetUrls) {
+ for (let file of group) {
+ if (await fileMayBeTailwindRelated(file)) {
+ return true
+ }
+ }
+ }
+}
+
+let HAS_CONFIG = /@config\s*['"]/
+let HAS_IMPORT = /@import\s*['"]/
+let HAS_TAILWIND = /@tailwind\s*[^;]+;/
+let HAS_THEME = /@theme\s*\{/
+
+export async function fileMayBeTailwindRelated(uri: Uri) {
+ let buffer = await workspace.fs.readFile(uri)
+ let contents = buffer.toString()
+
+ return (
+ HAS_CONFIG.test(contents) ||
+ HAS_IMPORT.test(contents) ||
+ HAS_TAILWIND.test(contents) ||
+ HAS_THEME.test(contents)
+ )
+}
diff --git a/packages/vscode-tailwindcss/src/api.ts b/packages/vscode-tailwindcss/src/api.ts
new file mode 100644
index 000000000..d7f67de64
--- /dev/null
+++ b/packages/vscode-tailwindcss/src/api.ts
@@ -0,0 +1,49 @@
+import { workspace, CancellationTokenSource, OutputChannel, ExtensionContext, Uri } from 'vscode'
+import { anyWorkspaceFoldersNeedServer, fileMayBeTailwindRelated } from './analyze'
+
+interface ApiOptions {
+ context: ExtensionContext
+ outputChannel: OutputChannel
+}
+
+export async function createApi({ context, outputChannel }: ApiOptions) {
+ let folderAnalysis: Promise | null = null
+
+ async function workspaceNeedsLanguageServer() {
+ if (folderAnalysis) return folderAnalysis
+
+ let source: CancellationTokenSource | null = new CancellationTokenSource()
+ source.token.onCancellationRequested(() => {
+ source?.dispose()
+ source = null
+
+ outputChannel.appendLine(
+ 'Server was not started. Search for Tailwind CSS-related files was taking too long.',
+ )
+ })
+
+ // Cancel the search after roughly 15 seconds
+ setTimeout(() => source?.cancel(), 15_000)
+ context.subscriptions.push(source)
+
+ folderAnalysis ??= anyWorkspaceFoldersNeedServer({
+ token: source.token,
+ folders: workspace.workspaceFolders ?? [],
+ })
+
+ let result = await folderAnalysis
+ source?.dispose()
+ return result
+ }
+
+ async function stylesheetNeedsLanguageServer(uri: Uri) {
+ outputChannel.appendLine(`Checking if ${uri.fsPath} may be Tailwind-related…`)
+
+ return fileMayBeTailwindRelated(uri)
+ }
+
+ return {
+ workspaceNeedsLanguageServer,
+ stylesheetNeedsLanguageServer,
+ }
+}
diff --git a/packages/vscode-tailwindcss/src/exclusions.ts b/packages/vscode-tailwindcss/src/exclusions.ts
new file mode 100644
index 000000000..46ffd599a
--- /dev/null
+++ b/packages/vscode-tailwindcss/src/exclusions.ts
@@ -0,0 +1,49 @@
+import {
+ workspace,
+ type WorkspaceConfiguration,
+ type ConfigurationScope,
+ type WorkspaceFolder,
+} from 'vscode'
+import picomatch from 'picomatch'
+import * as path from 'node:path'
+
+function getGlobalExcludePatterns(scope: ConfigurationScope | null): string[] {
+ return Object.entries(workspace.getConfiguration('files', scope)?.get('exclude') ?? [])
+ .filter(([, value]) => value === true)
+ .map(([key]) => key)
+ .filter(Boolean)
+}
+
+export function getExcludePatterns(scope: ConfigurationScope | null): string[] {
+ return [
+ ...getGlobalExcludePatterns(scope),
+ ...(workspace.getConfiguration('tailwindCSS', scope).get('files.exclude')).filter(
+ Boolean,
+ ),
+ ]
+}
+
+export function isExcluded(file: string, folder: WorkspaceFolder): boolean {
+ for (let pattern of getExcludePatterns(folder)) {
+ let matcher = picomatch(path.join(folder.uri.fsPath, pattern))
+
+ if (matcher(file)) {
+ return true
+ }
+ }
+
+ return false
+}
+
+export function mergeExcludes(
+ settings: WorkspaceConfiguration,
+ scope: ConfigurationScope | null,
+): any {
+ return {
+ ...settings,
+ files: {
+ ...settings.files,
+ exclude: getExcludePatterns(scope),
+ },
+ }
+}
diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts
index c0c7b9e73..467d4e085 100755
--- a/packages/vscode-tailwindcss/src/extension.ts
+++ b/packages/vscode-tailwindcss/src/extension.ts
@@ -4,7 +4,6 @@ import type {
TextDocument,
WorkspaceFolder,
ConfigurationScope,
- WorkspaceConfiguration,
Selection,
} from 'vscode'
import {
@@ -15,7 +14,6 @@ import {
SymbolInformation,
Position,
Range,
- RelativePattern,
} from 'vscode'
import type {
DocumentFilter,
@@ -32,11 +30,11 @@ import { languages as defaultLanguages } from '@tailwindcss/language-service/src
import * as semver from '@tailwindcss/language-service/src/util/semver'
import isObject from '@tailwindcss/language-service/src/util/isObject'
import namedColors from 'color-name'
-import picomatch from 'picomatch'
import { CONFIG_GLOB, CSS_GLOB } from '@tailwindcss/language-server/src/lib/constants'
-import braces from 'braces'
import normalizePath from 'normalize-path'
import * as servers from './servers/index'
+import { isExcluded, mergeExcludes } from './exclusions'
+import { createApi } from './api'
const colorNames = Object.keys(namedColors)
@@ -50,60 +48,6 @@ function getUserLanguages(folder?: WorkspaceFolder): Record {
return isObject(langs) ? langs : {}
}
-function getGlobalExcludePatterns(scope: ConfigurationScope | null): string[] {
- return Object.entries(Workspace.getConfiguration('files', scope)?.get('exclude') ?? [])
- .filter(([, value]) => value === true)
- .map(([key]) => key)
- .filter(Boolean)
-}
-
-function getExcludePatterns(scope: ConfigurationScope | null): string[] {
- return [
- ...getGlobalExcludePatterns(scope),
- ...(Workspace.getConfiguration('tailwindCSS', scope).get('files.exclude')).filter(
- Boolean,
- ),
- ]
-}
-
-function isExcluded(file: string, folder: WorkspaceFolder): boolean {
- for (let pattern of getExcludePatterns(folder)) {
- let matcher = picomatch(path.join(folder.uri.fsPath, pattern))
-
- if (matcher(file)) {
- return true
- }
- }
-
- return false
-}
-
-function mergeExcludes(settings: WorkspaceConfiguration, scope: ConfigurationScope | null): any {
- return {
- ...settings,
- files: {
- ...settings.files,
- exclude: getExcludePatterns(scope),
- },
- }
-}
-
-async function fileMayBeTailwindRelated(uri: Uri) {
- let contents = (await Workspace.fs.readFile(uri)).toString()
-
- let HAS_CONFIG = /@config\s*['"]/
- let HAS_IMPORT = /@import\s*['"]/
- let HAS_TAILWIND = /@tailwind\s*[^;]+;/
- let HAS_THEME = /@theme\s*\{/
-
- return (
- HAS_CONFIG.test(contents) ||
- HAS_IMPORT.test(contents) ||
- HAS_TAILWIND.test(contents) ||
- HAS_THEME.test(contents)
- )
-}
-
function selectionsAreEqual(
aSelections: readonly Selection[],
bSelections: readonly Selection[],
@@ -175,6 +119,12 @@ function resetActiveTextEditorContext(): void {
export async function activate(context: ExtensionContext) {
let outputChannel = Window.createOutputChannel(CLIENT_NAME)
+
+ let api = await createApi({
+ context,
+ outputChannel,
+ })
+
context.subscriptions.push(outputChannel)
context.subscriptions.push(
commands.registerCommand('tailwindCSS.showOutput', () => {
@@ -264,10 +214,10 @@ export async function activate(context: ExtensionContext) {
let configWatcher = Workspace.createFileSystemWatcher(`**/${CONFIG_GLOB}`, false, true, true)
configWatcher.onDidCreate(async (uri) => {
+ if (currentClient) return
let folder = Workspace.getWorkspaceFolder(uri)
- if (!folder || isExcluded(uri.fsPath, folder)) {
- return
- }
+ if (!folder || isExcluded(uri.fsPath, folder)) return
+
await bootWorkspaceClient()
})
@@ -276,13 +226,12 @@ export async function activate(context: ExtensionContext) {
let cssWatcher = Workspace.createFileSystemWatcher(`**/${CSS_GLOB}`, false, false, true)
async function bootClientIfCssFileMayBeTailwindRelated(uri: Uri) {
+ if (currentClient) return
let folder = Workspace.getWorkspaceFolder(uri)
- if (!folder || isExcluded(uri.fsPath, folder)) {
- return
- }
- if (await fileMayBeTailwindRelated(uri)) {
- await bootWorkspaceClient()
- }
+ if (!folder || isExcluded(uri.fsPath, folder)) return
+ if (!(await api.stylesheetNeedsLanguageServer(uri))) return
+
+ await bootWorkspaceClient()
}
cssWatcher.onDidCreate(bootClientIfCssFileMayBeTailwindRelated)
@@ -576,86 +525,34 @@ export async function activate(context: ExtensionContext) {
return client
}
- async function bootClientIfNeeded(): Promise {
- if (currentClient) {
- return
- }
-
- if (!(await anyFolderNeedsLanguageServer(Workspace.workspaceFolders ?? []))) {
- return
- }
-
- await bootWorkspaceClient()
- }
-
- async function anyFolderNeedsLanguageServer(
- folders: readonly WorkspaceFolder[],
- ): Promise {
- for (let folder of folders) {
- if (await folderNeedsLanguageServer(folder)) {
- return true
- }
- }
-
- return false
- }
-
- async function folderNeedsLanguageServer(folder: WorkspaceFolder): Promise {
- let settings = Workspace.getConfiguration('tailwindCSS', folder)
- if (settings.get('experimental.configFile') !== null) {
- return true
- }
-
- let exclude = `{${getExcludePatterns(folder)
- .flatMap((pattern) => braces.expand(pattern))
- .join(',')
- .replace(/{/g, '%7B')
- .replace(/}/g, '%7D')}}`
-
- let configFiles = await Workspace.findFiles(
- new RelativePattern(folder, `**/${CONFIG_GLOB}`),
- exclude,
- 1,
- )
-
- for (let file of configFiles) {
- return true
- }
-
- let cssFiles = await Workspace.findFiles(new RelativePattern(folder, `**/${CSS_GLOB}`), exclude)
-
- for (let file of cssFiles) {
- outputChannel.appendLine(`Checking if ${file.fsPath} may be Tailwind-related…`)
-
- if (await fileMayBeTailwindRelated(file)) {
- return true
- }
- }
-
- return false
- }
-
+ /**
+ * Note that this method can fire *many* times even for documents that are
+ * not in a visible editor. It's critical that this doesn't start any
+ * expensive operations more than is necessary.
+ */
async function didOpenTextDocument(document: TextDocument): Promise {
if (document.languageId === 'tailwindcss') {
servers.css.boot(context, outputChannel)
}
+ if (currentClient) return
+
// We are only interested in language mode text
- if (document.uri.scheme !== 'file') {
- return
- }
+ if (document.uri.scheme !== 'file') return
- let uri = document.uri
- let folder = Workspace.getWorkspaceFolder(uri)
+ let folder = Workspace.getWorkspaceFolder(document.uri)
// Files outside a folder can't be handled. This might depend on the language.
// Single file languages like JSON might handle files outside the workspace folders.
- if (!folder) return
+ if (!folder || isExcluded(document.uri.fsPath, folder)) return
+
+ if (!(await api.workspaceNeedsLanguageServer())) return
- await bootClientIfNeeded()
+ await bootWorkspaceClient()
}
context.subscriptions.push(Workspace.onDidOpenTextDocument(didOpenTextDocument))
+
Workspace.textDocuments.forEach(didOpenTextDocument)
context.subscriptions.push(
Workspace.onDidChangeWorkspaceFolders(async () => {
diff --git a/packages/vscode-tailwindcss/syntaxes/at-rules.tmLanguage.json b/packages/vscode-tailwindcss/syntaxes/at-rules.tmLanguage.json
index 6f09958f8..a029bd4cd 100644
--- a/packages/vscode-tailwindcss/syntaxes/at-rules.tmLanguage.json
+++ b/packages/vscode-tailwindcss/syntaxes/at-rules.tmLanguage.json
@@ -82,7 +82,7 @@
"include": "#source-fn"
},
{
- "match": "[^\\s;]+?",
+ "match": "[^\\s;]+",
"name": "variable.parameter.tailwind.tailwind"
}
]
@@ -103,7 +103,7 @@
"include": "source.css#comment-block"
},
{
- "match": "[^\\s{]+?",
+ "match": "[^\\s{]+",
"name": "variable.parameter.screen.tailwind"
},
{
@@ -144,7 +144,7 @@
"include": "source.css#comment-block"
},
{
- "match": "[^\\s{;,]+?",
+ "match": "[^\\s{;,]+",
"name": "variable.parameter.layer.tailwind"
},
{
@@ -189,8 +189,30 @@
},
"end": "(?<=}|;)(?!\\G)",
"patterns": [
+ { "include": "#theme-options" },
{
- "include": "source.css#rule-list"
+ "match": "[^{\\s]+",
+ "name": "invalid.illegal.theme-option.css"
+ },
+ {
+ "begin": "{",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.section.theme.begin.bracket.curly.tailwind"
+ }
+ },
+ "end": "}",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.section.theme.end.bracket.curly.tailwind"
+ }
+ },
+ "name": "meta.at-rule.theme.body.tailwind",
+ "patterns": [
+ {
+ "include": "#property-list"
+ }
+ ]
}
]
},
@@ -204,7 +226,7 @@
"name": "punctuation.definition.keyword.tailwind"
}
},
- "end": ";",
+ "end": ";|(?=[@{])",
"endCaptures": {
"0": {
"name": "punctuation.terminator.rule.css"
@@ -257,8 +279,15 @@
"match": "none(?=;)",
"name": "invalid.illegal.invalid-source.css"
},
+ {
+ "match": "not(?=\\s)",
+ "name": "support.constant.not.css"
+ },
{
"include": "source.css#string"
+ },
+ {
+ "include": "#inline-fn"
}
]
},
@@ -303,7 +332,7 @@
"include": "source.css#commas"
},
{
- "match": "[^\\s{,]+?",
+ "match": "[^\\s{,]+",
"name": "variable.parameter.variants.tailwind"
},
{
@@ -341,7 +370,7 @@
"end": "(?<=})(?!\\G)",
"patterns": [
{
- "match": "[^\\s{,]+?",
+ "match": "[^\\s{,]+",
"name": "variable.parameter.utility.tailwind"
},
{
@@ -360,14 +389,14 @@
"name": "meta.at-rule.utility.body.tailwind",
"patterns": [
{
- "include": "source.css#rule-list"
+ "include": "source.css#rule-list-innards"
}
]
}
]
},
{
- "begin": "(?i)((@)variant)(?=[\\s{(]|$)",
+ "begin": "(?i)((@)(?:custom-)?variant)(?=[\\s{(]|$)",
"beginCaptures": {
"1": {
"name": "keyword.control.at-rule.variant.tailwind"
@@ -379,7 +408,7 @@
"end": "(?<=[};])(?!\\G)",
"patterns": [
{
- "match": "[^\\s({;,]+?",
+ "match": "[^\\s({;,]+",
"name": "variable.parameter.variant.tailwind"
},
{
@@ -465,6 +494,16 @@
"repository": {
"property-list": {
"patterns": [
+ {
+ "include": "source.css#comment-block"
+ },
+ {
+ "include": "source.css#escapes"
+ },
+ {
+ "match": "(?x) (?=6.9.0'}
- '@babel/runtime@7.25.0':
- resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==}
- engines: {node: '>=6.9.0'}
+ '@csstools/css-calc@2.1.2':
+ resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.4
+ '@csstools/css-tokenizer': ^3.0.3
- '@csstools/css-parser-algorithms@2.1.1':
- resolution: {integrity: sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==}
- engines: {node: ^14 || ^16 || >=18}
+ '@csstools/css-parser-algorithms@3.0.4':
+ resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
+ engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-tokenizer': ^2.1.1
+ '@csstools/css-tokenizer': ^3.0.3
- '@csstools/css-tokenizer@2.1.1':
- resolution: {integrity: sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==}
- engines: {node: ^14 || ^16 || >=18}
+ '@csstools/css-tokenizer@3.0.3':
+ resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
+ engines: {node: '>=18'}
'@csstools/media-query-list-parser@2.0.4':
resolution: {integrity: sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==}
@@ -421,290 +487,152 @@ packages:
'@csstools/css-parser-algorithms': ^2.1.1
'@csstools/css-tokenizer': ^2.1.1
- '@esbuild/aix-ppc64@0.21.5':
- resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [aix]
-
- '@esbuild/aix-ppc64@0.25.0':
- resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==}
+ '@esbuild/aix-ppc64@0.25.5':
+ resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/android-arm64@0.21.5':
- resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
-
- '@esbuild/android-arm64@0.25.0':
- resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==}
+ '@esbuild/android-arm64@0.25.5':
+ resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm@0.21.5':
- resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-arm@0.25.0':
- resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==}
+ '@esbuild/android-arm@0.25.5':
+ resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-x64@0.21.5':
- resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/android-x64@0.25.0':
- resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==}
+ '@esbuild/android-x64@0.25.5':
+ resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/darwin-arm64@0.21.5':
- resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-arm64@0.25.0':
- resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==}
+ '@esbuild/darwin-arm64@0.25.5':
+ resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-x64@0.21.5':
- resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.25.0':
- resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==}
+ '@esbuild/darwin-x64@0.25.5':
+ resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/freebsd-arm64@0.21.5':
- resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-arm64@0.25.0':
- resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==}
+ '@esbuild/freebsd-arm64@0.25.5':
+ resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.21.5':
- resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
-
- '@esbuild/freebsd-x64@0.25.0':
- resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==}
+ '@esbuild/freebsd-x64@0.25.5':
+ resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/linux-arm64@0.21.5':
- resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm64@0.25.0':
- resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==}
+ '@esbuild/linux-arm64@0.25.5':
+ resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm@0.21.5':
- resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-arm@0.25.0':
- resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==}
+ '@esbuild/linux-arm@0.25.5':
+ resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-ia32@0.21.5':
- resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-ia32@0.25.0':
- resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==}
+ '@esbuild/linux-ia32@0.25.5':
+ resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-loong64@0.21.5':
- resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-loong64@0.25.0':
- resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==}
+ '@esbuild/linux-loong64@0.25.5':
+ resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-mips64el@0.21.5':
- resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.25.0':
- resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==}
+ '@esbuild/linux-mips64el@0.25.5':
+ resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-ppc64@0.21.5':
- resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
-
- '@esbuild/linux-ppc64@0.25.0':
- resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==}
+ '@esbuild/linux-ppc64@0.25.5':
+ resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-riscv64@0.21.5':
- resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.25.0':
- resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==}
+ '@esbuild/linux-riscv64@0.25.5':
+ resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-s390x@0.21.5':
- resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-s390x@0.25.0':
- resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==}
+ '@esbuild/linux-s390x@0.25.5':
+ resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-x64@0.21.5':
- resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
-
- '@esbuild/linux-x64@0.25.0':
- resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==}
+ '@esbuild/linux-x64@0.25.5':
+ resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/netbsd-arm64@0.25.0':
- resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==}
+ '@esbuild/netbsd-arm64@0.25.5':
+ resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.21.5':
- resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
-
- '@esbuild/netbsd-x64@0.25.0':
- resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==}
+ '@esbuild/netbsd-x64@0.25.5':
+ resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/openbsd-arm64@0.25.0':
- resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==}
+ '@esbuild/openbsd-arm64@0.25.5':
+ resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.21.5':
- resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
-
- '@esbuild/openbsd-x64@0.25.0':
- resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==}
+ '@esbuild/openbsd-x64@0.25.5':
+ resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/sunos-x64@0.21.5':
- resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/sunos-x64@0.25.0':
- resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==}
+ '@esbuild/sunos-x64@0.25.5':
+ resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.21.5':
- resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-arm64@0.25.0':
- resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==}
+ '@esbuild/win32-arm64@0.25.5':
+ resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-ia32@0.21.5':
- resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-ia32@0.25.0':
- resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==}
+ '@esbuild/win32-ia32@0.25.5':
+ resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-x64@0.21.5':
- resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
-
- '@esbuild/win32-x64@0.25.0':
- resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==}
+ '@esbuild/win32-x64@0.25.5':
+ resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -713,10 +641,6 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
- '@jest/schemas@29.6.3':
- resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -759,112 +683,184 @@ packages:
resolution: {integrity: sha512-wBqcGsMELZna0jDblGd7UXgOby45TQaMWmbFwWX+SEotk4HV6zG2t6rT9siyLhPk4P6YYqgfL1UO8nMWDBVJXQ==}
engines: {node: ^16.14.0 || >=18.0.0}
- '@parcel/watcher@2.0.3':
- resolution: {integrity: sha512-PHh5PArr3nYGYVj9z/NSfDmmKEBNrg2bzoFgxzjTRBBxPUKx039x3HF6VGLFIfrghjJxcYn/IeSpdVwfob7KFA==}
+ '@parcel/watcher-android-arm64@2.5.1':
+ resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
+ engines: {node: '>= 10.0.0'}
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
+ engines: {node: '>= 10.0.0'}
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
+ engines: {node: '>= 10.0.0'}
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
+ engines: {node: '>= 10.0.0'}
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
+ engines: {node: '>= 10.0.0'}
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
+ engines: {node: '>= 10.0.0'}
+ os: [linux]
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
+ engines: {node: '>= 10.0.0'}
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
+ engines: {node: '>= 10.0.0'}
+ os: [win32]
+
+ '@parcel/watcher@2.5.1':
+ resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
- '@rollup/rollup-android-arm-eabi@4.34.3':
- resolution: {integrity: sha512-8kq/NjMKkMTGKMPldWihncOl62kgnLYk7cW+/4NCUWfS70/wz4+gQ7rMxMMpZ3dIOP/xw7wKNzIuUnN/H2GfUg==}
+ '@rollup/rollup-android-arm-eabi@4.41.1':
+ resolution: {integrity: sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.34.3':
- resolution: {integrity: sha512-1PqMHiuRochQ6++SDI7SaRDWJKr/NgAlezBi5nOne6Da6IWJo3hK0TdECBDwd92IUDPG4j/bZmWuwOnomNT8wA==}
+ '@rollup/rollup-android-arm64@4.41.1':
+ resolution: {integrity: sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.34.3':
- resolution: {integrity: sha512-fqbrykX4mGV3DlCDXhF4OaMGcchd2tmLYxVt3On5oOZWVDFfdEoYAV2alzNChl8OzNaeMAGqm1f7gk7eIw/uDg==}
+ '@rollup/rollup-darwin-arm64@4.41.1':
+ resolution: {integrity: sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.34.3':
- resolution: {integrity: sha512-8Wxrx/KRvMsTyLTbdrMXcVKfpW51cCNW8x7iQD72xSEbjvhCY3b+w83Bea3nQfysTMR7K28esc+ZFITThXm+1w==}
+ '@rollup/rollup-darwin-x64@4.41.1':
+ resolution: {integrity: sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.34.3':
- resolution: {integrity: sha512-lpBmV2qSiELh+ATQPTjQczt5hvbTLsE0c43Rx4bGxN2VpnAZWy77we7OO62LyOSZNY7CzjMoceRPc+Lt4e9J6A==}
+ '@rollup/rollup-freebsd-arm64@4.41.1':
+ resolution: {integrity: sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.34.3':
- resolution: {integrity: sha512-sNPvBIXpgaYcI6mAeH13GZMXFrrw5mdZVI1M9YQPRG2LpjwL8DSxSIflZoh/B5NEuOi53kxsR/S2GKozK1vDXA==}
+ '@rollup/rollup-freebsd-x64@4.41.1':
+ resolution: {integrity: sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.34.3':
- resolution: {integrity: sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.41.1':
+ resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.34.3':
- resolution: {integrity: sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==}
+ '@rollup/rollup-linux-arm-musleabihf@4.41.1':
+ resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.34.3':
- resolution: {integrity: sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==}
+ '@rollup/rollup-linux-arm64-gnu@4.41.1':
+ resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.34.3':
- resolution: {integrity: sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==}
+ '@rollup/rollup-linux-arm64-musl@4.41.1':
+ resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-loongarch64-gnu@4.34.3':
- resolution: {integrity: sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==}
+ '@rollup/rollup-linux-loongarch64-gnu@4.41.1':
+ resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==}
cpu: [loong64]
os: [linux]
- '@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
- resolution: {integrity: sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==}
+ '@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
+ resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.34.3':
- resolution: {integrity: sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==}
+ '@rollup/rollup-linux-riscv64-gnu@4.41.1':
+ resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.41.1':
+ resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.34.3':
- resolution: {integrity: sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==}
+ '@rollup/rollup-linux-s390x-gnu@4.41.1':
+ resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.34.3':
- resolution: {integrity: sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==}
+ '@rollup/rollup-linux-x64-gnu@4.41.1':
+ resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.34.3':
- resolution: {integrity: sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==}
+ '@rollup/rollup-linux-x64-musl@4.41.1':
+ resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-win32-arm64-msvc@4.34.3':
- resolution: {integrity: sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==}
+ '@rollup/rollup-win32-arm64-msvc@4.41.1':
+ resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.34.3':
- resolution: {integrity: sha512-K/V97GMbNa+Da9mGcZqmSl+DlJmWfHXTuI9V8oB2evGsQUtszCl67+OxWjBKpeOnYwox9Jpmt/J6VhpeRCYqow==}
+ '@rollup/rollup-win32-ia32-msvc@4.41.1':
+ resolution: {integrity: sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.34.3':
- resolution: {integrity: sha512-CUypcYP31Q8O04myV6NKGzk9GVXslO5EJNfmARNSzLF2A+5rmZUlDJ4et6eoJaZgBT9wrC2p4JZH04Vkic8HdQ==}
+ '@rollup/rollup-win32-x64-msvc@4.41.1':
+ resolution: {integrity: sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==}
cpu: [x64]
os: [win32]
- '@sinclair/typebox@0.27.8':
- resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
-
'@tailwindcss/aspect-ratio@0.4.2':
resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==}
peerDependencies:
@@ -885,68 +881,74 @@ packages:
peerDependencies:
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
- '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19':
- resolution: {integrity: sha512-NhpXem1j7g0uSGyLucmMj0VVQMeUrWc6kR/Ymnri3tpw2eaykgFYwLfdnI7jdJRxUxa/nNJip9yBJ3diZXl60w==}
+ '@tailwindcss/oxide-android-arm64@4.1.0':
+ resolution: {integrity: sha512-UredFljuHey2Kh5qyYfQVBr0Xfq70ZE5Df6i5IubNYQGs2JXXT4VL0SIUjwzHx5W9T6t7dT7banunlV6lthGPQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
- '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.19':
- resolution: {integrity: sha512-KCdalT+huX2cW9snNmPr+B66V91cSzIobBCXVgYCPCh0NZF4ueKu+X+kQN2gFxurDUm/D+aKW/0rQUIsUmFpdQ==}
+ '@tailwindcss/oxide-darwin-arm64@4.1.0':
+ resolution: {integrity: sha512-QHQ/46lRVwH9zEBNiRk8AJ3Af4pMq6DuZAI//q323qrPOXjsRdrhLsH9LUO3mqBfHr5EZNUxN3Am5vpO89sntw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.19':
- resolution: {integrity: sha512-ETOWA08loUmOVTEa3zhRhY8HyqdGtR9DNhXdrRZBi67ZwCAmA+jg5B+mZaYeQJ6CjETx07BnhcGmmxGz3/6c8w==}
+ '@tailwindcss/oxide-darwin-x64@4.1.0':
+ resolution: {integrity: sha512-lEMgYHCvQQ6x2KOZ4FwnPprwfnc+UnjzwXRqEYIhB/NlYvXQD1QMf7oKEDRqy94DiZaYox9ZRfG2YJOBgM0UkA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.19':
- resolution: {integrity: sha512-uB0rYLpqPnmyqtYSKHu1AtnHeerNcVH+de0sIufGCBDFGYNxmW8feCKNZwo6r7U/Fzg+AF9BOjwvdvd4yLfQ8g==}
+ '@tailwindcss/oxide-freebsd-x64@4.1.0':
+ resolution: {integrity: sha512-9fdImTc+2lA5yHqJ61oeTXfCtzylNOzJVFhyWwVQAJESJJbVCPnj6f+b+Zf/AYAdKQfS6FCThbPEahkQrDCgLQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.19':
- resolution: {integrity: sha512-hIfm6DNh18rkz2PFRsQANINH0tpso6/vaU8p0Qw7rgYqqrxJTRpyLVsnvx3ahMOplJrDT6Z+Nfak8udnZN2C/Q==}
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.0':
+ resolution: {integrity: sha512-HB0bTkUOuTLLSdadyRhKE9yps4/ZBjrojbHTPMSvvf/8yBLZRPpWb+A6IgW5R+2A2AL4KhVPgLwWfoXsErxJFg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
- '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.19':
- resolution: {integrity: sha512-n1Hr+8Hup2GLmeonQy9ydZxMBCs0FR1rcv4K7AHip+6PbD0se8k9LBIZac3OguFNj2hTehiadaiRb18rsVUw0g==}
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.0':
+ resolution: {integrity: sha512-+QtYCwvKLjC46h6RikKkpELJWrpiMMtgyK0aaqhwPLEx1icGgIhwz8dqrkAiqbFRE0KiRrE2aenhYoEkplyRmA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.19':
- resolution: {integrity: sha512-KhwthLZh9Js3t5URkuRURw45iU3rSh9vhuHRaV4KQT10ZFiXQMUlFfMKJyxRMcgC2fcL5vsiqwjOaMwp7Y8vsQ==}
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.0':
+ resolution: {integrity: sha512-nApadFKM9GauzuPZPlt6TKfELavMHqJ0gVd+GYkYBTwr2t9KhgCAb2sKiFDDIhs1a7gOjsU7P1lEauv3iKFp+Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.19':
- resolution: {integrity: sha512-qqEuULSiczyZkdWVzwkiiFyOYqx5RR2De75iwYREzXUuHRHval1ep2qO7tvZdgt37t2vgjoQwaPA6zO+JGUa+Q==}
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.0':
+ resolution: {integrity: sha512-cp0Rf9Wit2kZHhrV8HIoDFD8dxU2+ZTCFCFbDj3a07pGyyPwLCJm5H5VipKXgYrBaLmlYu73ERidW0S5sdEXEg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.19':
- resolution: {integrity: sha512-JKTYCiNz83sYl2FgKJk3dL11FS4dAj7Rgmuz3TVANmeMYTBtwmFPthoH0qAmF+hjPJgT5Ne7lSwplfuHJAD3MQ==}
+ '@tailwindcss/oxide-linux-x64-musl@4.1.0':
+ resolution: {integrity: sha512-4/wf42XWBJGXsOS6BhgPhdQbg/qyfdZ1nZvTL9sJoxYN+Ah+cfY5Dd7R0smzI8hmgCRt3TD1lYb72ChTyIA59w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.19':
- resolution: {integrity: sha512-D43tji14+i/GhJn5YZX8c2FXFmAqlI6DDGX8caUM35dga/uT+sJUfLJ84YigJyvAdSF1gBVdm7MvhYRcVYHOwg==}
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.0':
+ resolution: {integrity: sha512-caXJJ0G6NwGbcoxEYdH3MZYN84C3PldaMdAEPMU6bjJXURQlKdSlQ/Ecis7/nSgBkMkicZyhqWmb36Tw/BFSIw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.0':
+ resolution: {integrity: sha512-ZHXRXRxB7HBmkUE8U13nmkGGYfR1I2vsuhiYjeDDUFIYpk1BL6caU8hvzkSlL/X5CAQNdIUUJRGom5I0ZyfJOA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
- '@tailwindcss/oxide@4.0.0-alpha.19':
- resolution: {integrity: sha512-DdkrVz/MKPoe9v7W3c0+SEFKRDIPMSsxgN7gPC+xeTnTL4BGoT5b1EiVGFuXWEyLbDmWztuN6z75Yuze2BwvMQ==}
+ '@tailwindcss/oxide@4.1.0':
+ resolution: {integrity: sha512-A33oyZKpPFH08d7xkl13Dc8OTsbPhsuls0z9gUCxIHvn8c1BsUACddQxL6HwaeJR1fSYyXZUw8bdWcD8bVawpQ==}
engines: {node: '>= 10'}
'@tailwindcss/typography@0.5.7':
@@ -957,6 +959,9 @@ packages:
'@types/braces@3.0.1':
resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==}
+ '@types/chai@5.2.2':
+ resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
+
'@types/color-name@1.1.4':
resolution: {integrity: sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==}
@@ -969,11 +974,17 @@ packages:
'@types/debounce@1.2.0':
resolution: {integrity: sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==}
+ '@types/dedent@0.7.2':
+ resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
'@types/dlv@1.1.4':
resolution: {integrity: sha512-m8KmImw4Jt+4rIgupwfivrWEOnj1LzkmKkqbh075uG13eTQ1ZxHWT6T0vIdSQhLIjQCiR0n0lZdtyDOPO1x2Mw==}
- '@types/estree@1.0.6':
- resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+ '@types/estree@1.0.7':
+ resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
'@types/find-up@4.0.0':
resolution: {integrity: sha512-QlRNKeOPFWKisbNtKVOOGXw3AeLbkw8UmT/EyEGM6brfqpYffKBcch7f1y40NYN9O90aK2+K0xBMDJfOAsg2qg==}
@@ -1027,23 +1038,37 @@ packages:
'@types/ws@8.5.12':
resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
- '@vitest/expect@1.6.1':
- resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==}
+ '@vitest/expect@3.2.1':
+ resolution: {integrity: sha512-FqS/BnDOzV6+IpxrTg5GQRyLOCtcJqkwMwcS8qGCI2IyRVDwPAtutztaf1CjtPHlZlWtl1yUPCd7HM0cNiDOYw==}
+
+ '@vitest/mocker@3.2.1':
+ resolution: {integrity: sha512-OXxMJnx1lkB+Vl65Re5BrsZEHc90s5NMjD23ZQ9NlU7f7nZiETGoX4NeKZSmsKjseuMq2uOYXdLOeoM0pJU+qw==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
- '@vitest/runner@1.6.1':
- resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
+ '@vitest/pretty-format@3.2.1':
+ resolution: {integrity: sha512-xBh1X2GPlOGBupp6E1RcUQWIxw0w/hRLd3XyBS6H+dMdKTAqHDNsIR2AnJwPA3yYe9DFy3VUKTe3VRTrAiQ01g==}
- '@vitest/snapshot@1.6.1':
- resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
+ '@vitest/runner@3.2.1':
+ resolution: {integrity: sha512-kygXhNTu/wkMYbwYpS3z/9tBe0O8qpdBuC3dD/AW9sWa0LE/DAZEjnHtWA9sIad7lpD4nFW1yQ+zN7mEKNH3yA==}
- '@vitest/spy@1.6.1':
- resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
+ '@vitest/snapshot@3.2.1':
+ resolution: {integrity: sha512-5xko/ZpW2Yc65NVK9Gpfg2y4BFvcF+At7yRT5AHUpTg9JvZ4xZoyuRY4ASlmNcBZjMslV08VRLDrBOmUe2YX3g==}
- '@vitest/utils@1.6.1':
- resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
+ '@vitest/spy@3.2.1':
+ resolution: {integrity: sha512-Nbfib34Z2rfcJGSetMxjDCznn4pCYPZOtQYox2kzebIJcgH75yheIKd5QYSFmR8DIZf2M8fwOm66qSDIfRFFfQ==}
- '@vscode/l10n@0.0.16':
- resolution: {integrity: sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==}
+ '@vitest/utils@3.2.1':
+ resolution: {integrity: sha512-KkHlGhePEKZSub5ViknBcN5KEF+u7dSUr9NW8QsVICusUojrgrOnnY3DEWWO877ax2Pyopuk2qHmt+gkNKnBVw==}
+
+ '@vscode/l10n@0.0.18':
+ resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==}
'@vscode/vsce@2.21.1':
resolution: {integrity: sha512-f45/aT+HTubfCU2oC7IaWnH9NjOWp668ML002QiFObFRVUCoLtcwepp9mmql/ArFUy+HCHp54Xrq4koTcOD6TA==}
@@ -1053,15 +1078,6 @@ packages:
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
- acorn-walk@8.3.4:
- resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
- engines: {node: '>=0.4.0'}
-
- acorn@8.14.0:
- resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -1078,10 +1094,6 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
- ansi-styles@5.2.0:
- resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
- engines: {node: '>=10'}
-
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
@@ -1110,8 +1122,9 @@ packages:
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
- assertion-error@1.1.0:
- resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
azure-devops-node-api@11.2.0:
resolution: {integrity: sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==}
@@ -1186,9 +1199,9 @@ packages:
caniuse-lite@1.0.30001649:
resolution: {integrity: sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==}
- chai@4.5.0:
- resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
- engines: {node: '>=4'}
+ chai@5.2.0:
+ resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
+ engines: {node: '>=12'}
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@@ -1198,8 +1211,9 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
- check-error@1.0.3:
- resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+ check-error@2.1.1:
+ resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+ engines: {node: '>= 16'}
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
@@ -1215,8 +1229,9 @@ packages:
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
- cliui@7.0.4:
- resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@@ -1242,14 +1257,11 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
- concurrently@7.0.0:
- resolution: {integrity: sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==}
- engines: {node: ^12.20.0 || ^14.13.0 || >=16.0.0}
+ concurrently@9.1.2:
+ resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==}
+ engines: {node: '>=18'}
hasBin: true
- confbox@0.1.8:
- resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
-
cosmiconfig@7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'}
@@ -1277,10 +1289,6 @@ packages:
resolution: {integrity: sha512-LSnjA6HuIUOlkfKVbzi2OlToZE8OjFi667JWN9qNymXVXzGDmvuP60SSgC+e92sd7B7158f7Fy3Mb6rXS5EDPw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- date-fns@2.30.0:
- resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
- engines: {node: '>=0.11'}
-
debounce@1.2.0:
resolution: {integrity: sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==}
@@ -1292,8 +1300,8 @@ packages:
supports-color:
optional: true
- debug@4.3.6:
- resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
+ debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
@@ -1301,8 +1309,8 @@ packages:
supports-color:
optional: true
- debug@4.4.0:
- resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ debug@4.4.1:
+ resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
@@ -1338,8 +1346,8 @@ packages:
babel-plugin-macros:
optional: true
- deep-eql@4.1.4:
- resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deep-extend@0.6.0:
@@ -1358,6 +1366,11 @@ packages:
resolution: {integrity: sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==}
engines: {node: '>=8'}
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
@@ -1368,10 +1381,6 @@ packages:
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
- diff-sequences@29.6.3:
- resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
@@ -1432,19 +1441,17 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
esbuild-node-externals@1.14.0:
resolution: {integrity: sha512-jMWnTlCII3cLEjR5+u0JRSTJuP+MgbjEHKfwSIAI41NgLQ0ZjfzjchlbEn0r7v2u5gCBMSEYvYlkO7GDG8gG3A==}
engines: {node: '>=12'}
peerDependencies:
esbuild: 0.12 - 0.23
- esbuild@0.21.5:
- resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
- engines: {node: '>=12'}
- hasBin: true
-
- esbuild@0.25.0:
- resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==}
+ esbuild@0.25.5:
+ resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==}
engines: {node: '>=18'}
hasBin: true
@@ -1459,17 +1466,13 @@ packages:
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
- execa@8.0.1:
- resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
- engines: {node: '>=16.17'}
-
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
- fast-glob@3.2.4:
- resolution: {integrity: sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==}
- engines: {node: '>=8'}
+ expect-type@1.2.1:
+ resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
+ engines: {node: '>=12.0.0'}
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
@@ -1481,6 +1484,22 @@ packages:
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
+ fdir@6.4.3:
+ resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fdir@6.4.4:
+ resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -1518,9 +1537,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-func-name@2.0.2:
- resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
-
get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
@@ -1528,10 +1544,6 @@ packages:
get-own-enumerable-property-symbols@3.0.2:
resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
- get-stream@8.0.1:
- resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
- engines: {node: '>=16'}
-
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
@@ -1606,10 +1618,6 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
- human-signals@5.0.0:
- resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
- engines: {node: '>=16.17.0'}
-
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -1680,10 +1688,6 @@ packages:
resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
engines: {node: '>=0.10.0'}
- is-stream@3.0.0:
- resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -1713,9 +1717,6 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
- js-tokens@9.0.1:
- resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
-
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@@ -1763,10 +1764,6 @@ packages:
linkify-it@3.0.3:
resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==}
- local-pkg@0.5.1:
- resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
- engines: {node: '>=14'}
-
locate-path@3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
@@ -1787,8 +1784,8 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
- loupe@2.3.7:
- resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+ loupe@3.1.3:
+ resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
lru-cache@10.0.1:
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
@@ -1820,17 +1817,10 @@ packages:
resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- merge-stream@2.0.0:
- resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
- micromatch@4.0.7:
- resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
- engines: {node: '>=8.6'}
-
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -1840,10 +1830,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
- mimic-fn@4.0.0:
- resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
- engines: {node: '>=12'}
-
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -1885,9 +1871,6 @@ packages:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
- mlly@1.7.4:
- resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
-
moo@0.5.1:
resolution: {integrity: sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==}
@@ -1900,9 +1883,6 @@ packages:
resolution: {integrity: sha512-4aE3U7CCBWgrQlQDMq8da4woBWDGHioJFiOZ8Ie6Yq2uwYQ9V2kGhTz4x3u6Wc+OU17nw0yc3rJ/lQ4jIiPe3A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- ms@2.1.2:
- resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
-
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1912,13 +1892,8 @@ packages:
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
- nanoid@3.3.7:
- resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
-
- nanoid@3.3.8:
- resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -1929,15 +1904,11 @@ packages:
resolution: {integrity: sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==}
engines: {node: '>=10'}
- node-addon-api@3.2.1:
- resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
-
node-addon-api@4.3.0:
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
- node-gyp-build@4.8.1:
- resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
- hasBin: true
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
@@ -1980,10 +1951,6 @@ packages:
resolution: {integrity: sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==}
engines: {node: ^16.14.0 || >=18.0.0}
- npm-run-path@5.3.0:
- resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@@ -2002,10 +1969,6 @@ packages:
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
- onetime@6.0.0:
- resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
- engines: {node: '>=12'}
-
os-homedir@1.0.2:
resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==}
engines: {node: '>=0.10.0'}
@@ -2026,10 +1989,6 @@ packages:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
- p-limit@5.0.0:
- resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
- engines: {node: '>=18'}
-
p-locate@3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
@@ -2079,10 +2038,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- path-key@4.0.0:
- resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
- engines: {node: '>=12'}
-
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -2094,21 +2049,16 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
- pathe@1.1.2:
- resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
-
- pathe@2.0.2:
- resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
- pathval@1.1.1:
- resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+ pathval@2.0.0:
+ resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
+ engines: {node: '>= 14.16'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
- picocolors@1.0.1:
- resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
-
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2128,9 +2078,6 @@ packages:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
- pkg-types@1.3.1:
- resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
-
pkg-up@3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
@@ -2190,12 +2137,8 @@ packages:
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
- postcss@8.4.31:
- resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
- engines: {node: ^10 || ^12 || >=14}
-
- postcss@8.5.1:
- resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
+ postcss@8.5.4:
+ resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==}
engines: {node: ^10 || ^12 || >=14}
prebuild-install@7.1.2:
@@ -2208,10 +2151,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
- pretty-format@29.7.0:
- resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
proc-log@3.0.0:
resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -2246,9 +2185,6 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
- react-is@18.3.1:
- resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
-
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -2288,9 +2224,6 @@ packages:
resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==}
engines: {node: '>=12'}
- regenerator-runtime@0.14.1:
- resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
-
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -2323,17 +2256,16 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
- rollup@4.34.3:
- resolution: {integrity: sha512-ORCtU0UBJyiAIn9m0llUXJXAswG/68pZptCrqxHG7//Z2DDzAUeyyY5hqf4XrsGlUxscMr9GkQ2QI7KTLqeyPw==}
+ rollup@4.41.1:
+ resolution: {integrity: sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- rxjs@6.6.7:
- resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}
- engines: {npm: '>=2.0.0'}
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@@ -2362,6 +2294,10 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shell-quote@1.8.2:
+ resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
+ engines: {node: '>= 0.4'}
+
side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
engines: {node: '>= 0.4'}
@@ -2385,17 +2321,10 @@ packages:
slide@1.1.6:
resolution: {integrity: sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==}
- source-map-js@1.2.0:
- resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
- engines: {node: '>=0.10.0'}
-
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
- spawn-command@0.0.2:
- resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
-
spdx-compare@1.0.0:
resolution: {integrity: sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==}
@@ -2423,8 +2352,8 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
- std-env@3.8.0:
- resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==}
+ std-env@3.9.0:
+ resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -2453,10 +2382,6 @@ packages:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
- strip-final-newline@3.0.0:
- resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
- engines: {node: '>=12'}
-
strip-indent@4.0.0:
resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==}
engines: {node: '>=12'}
@@ -2465,9 +2390,6 @@ packages:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
- strip-literal@2.1.1:
- resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
-
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -2494,15 +2416,15 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
- tailwindcss@4.0.6:
- resolution: {integrity: sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==}
+ tailwindcss@4.1.1:
+ resolution: {integrity: sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==}
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
- tar-fs@2.1.1:
- resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
+ tar-fs@2.1.3:
+ resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==}
tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
@@ -2518,12 +2440,31 @@ packages:
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
- tinypool@0.8.4:
- resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyglobby@0.2.12:
+ resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
+ engines: {node: '>=12.0.0'}
+
+ tinyglobby@0.2.13:
+ resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
+ engines: {node: '>=12.0.0'}
+
+ tinyglobby@0.2.14:
+ resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinypool@1.1.0:
+ resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
- tinyspy@2.2.1:
- resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+ tinyspy@4.0.3:
+ resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
engines: {node: '>=14.0.0'}
tmp-cache@1.1.0:
@@ -2567,9 +2508,6 @@ packages:
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
engines: {node: '>=6'}
- tslib@1.14.1:
- resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
-
tslib@2.2.0:
resolution: {integrity: sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==}
@@ -2583,10 +2521,6 @@ packages:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
- type-detect@4.1.0:
- resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
- engines: {node: '>=4'}
-
type-fest@1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
@@ -2594,17 +2528,14 @@ packages:
typed-rest-client@1.8.11:
resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==}
- typescript@5.3.3:
- resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+ typescript@5.8.3:
+ resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
hasBin: true
uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
- ufo@1.5.4:
- resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
-
underscore@1.13.7:
resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==}
@@ -2636,35 +2567,40 @@ packages:
resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- vite-node@1.6.1:
- resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vite-node@3.2.1:
+ resolution: {integrity: sha512-V4EyKQPxquurNJPtQJRZo8hKOoKNBRIhxcDbQFPFig0JdoWcUhwRgK8yoCXXrfYVPKS6XwirGHPszLnR8FbjCA==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
- vite-tsconfig-paths@4.3.2:
- resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
+ vite-tsconfig-paths@5.1.4:
+ resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
peerDependencies:
vite: '*'
peerDependenciesMeta:
vite:
optional: true
- vite@5.4.14:
- resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vite@6.3.5:
+ resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
- '@types/node': ^18.0.0 || >=20.0.0
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
- terser: ^5.4.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
+ jiti:
+ optional: true
less:
optional: true
lightningcss:
@@ -2679,21 +2615,28 @@ packages:
optional: true
terser:
optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
- vitest@1.6.1:
- resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vitest@3.2.1:
+ resolution: {integrity: sha512-VZ40MBnlE1/V5uTgdqY3DmjUgZtIzsYq758JGlyQrv5syIsaYcabkfPkEuWML49Ph0D/SoqpVFd0dyVTr551oA==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
- '@types/node': ^18.0.0 || >=20.0.0
- '@vitest/browser': 1.6.1
- '@vitest/ui': 1.6.1
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.1
+ '@vitest/ui': 3.2.1
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
+ '@types/debug':
+ optional: true
'@types/node':
optional: true
'@vitest/browser':
@@ -2705,8 +2648,8 @@ packages:
jsdom:
optional: true
- vscode-css-languageservice@6.2.9:
- resolution: {integrity: sha512-9MsOvAi+VycKomQ7KEq4o/hLtjHHrtRLLl8lM9nMcH8cxfNI7/6jVXmsV/7pdbDWu9L3DZhsspN1eMXZwiOymw==}
+ vscode-css-languageservice@6.3.6:
+ resolution: {integrity: sha512-fU4h8mT3KlvfRcbF74v/M+Gzbligav6QMx4AD/7CbclWPYOpGb9kgIswfpZVJbIcOEJJACI9iYizkNwdiAqlHw==}
vscode-emmet-helper-bundled@0.0.1:
resolution: {integrity: sha512-EhZ0Wt8MbdrKF3NUMfaUDhFPTdRnl1tyqYS7KOcNtsSNTV285IV+XPDtNQyw5rwYsULEfb6n+fK1DRufJQlPYw==}
@@ -2740,8 +2683,8 @@ packages:
vscode-languageserver-protocol@3.17.5:
resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
- vscode-languageserver-textdocument@1.0.11:
- resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
vscode-languageserver-types@3.17.2:
resolution: {integrity: sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==}
@@ -2756,11 +2699,17 @@ packages:
resolution: {integrity: sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==}
hasBin: true
+ vscode-oniguruma@2.0.1:
+ resolution: {integrity: sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==}
+
+ vscode-textmate@9.2.0:
+ resolution: {integrity: sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==}
+
vscode-uri@3.0.2:
resolution: {integrity: sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA==}
- vscode-uri@3.0.8:
- resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@@ -2816,9 +2765,13 @@ packages:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
- yargs@16.2.0:
- resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
- engines: {node: '>=10'}
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
@@ -2830,10 +2783,6 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- yocto-queue@1.1.1:
- resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
- engines: {node: '>=12.20'}
-
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -2852,163 +2801,95 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/runtime@7.25.0':
+ '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
- regenerator-runtime: 0.14.1
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
- '@csstools/css-parser-algorithms@2.1.1(@csstools/css-tokenizer@2.1.1)':
+ '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@csstools/css-tokenizer': 2.1.1
+ '@csstools/css-tokenizer': 3.0.3
- '@csstools/css-tokenizer@2.1.1': {}
+ '@csstools/css-tokenizer@3.0.3': {}
- '@csstools/media-query-list-parser@2.0.4(@csstools/css-parser-algorithms@2.1.1(@csstools/css-tokenizer@2.1.1))(@csstools/css-tokenizer@2.1.1)':
+ '@csstools/media-query-list-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@csstools/css-parser-algorithms': 2.1.1(@csstools/css-tokenizer@2.1.1)
- '@csstools/css-tokenizer': 2.1.1
-
- '@esbuild/aix-ppc64@0.21.5':
- optional: true
-
- '@esbuild/aix-ppc64@0.25.0':
- optional: true
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
- '@esbuild/android-arm64@0.21.5':
+ '@esbuild/aix-ppc64@0.25.5':
optional: true
- '@esbuild/android-arm64@0.25.0':
+ '@esbuild/android-arm64@0.25.5':
optional: true
- '@esbuild/android-arm@0.21.5':
+ '@esbuild/android-arm@0.25.5':
optional: true
- '@esbuild/android-arm@0.25.0':
+ '@esbuild/android-x64@0.25.5':
optional: true
- '@esbuild/android-x64@0.21.5':
+ '@esbuild/darwin-arm64@0.25.5':
optional: true
- '@esbuild/android-x64@0.25.0':
+ '@esbuild/darwin-x64@0.25.5':
optional: true
- '@esbuild/darwin-arm64@0.21.5':
+ '@esbuild/freebsd-arm64@0.25.5':
optional: true
- '@esbuild/darwin-arm64@0.25.0':
+ '@esbuild/freebsd-x64@0.25.5':
optional: true
- '@esbuild/darwin-x64@0.21.5':
+ '@esbuild/linux-arm64@0.25.5':
optional: true
- '@esbuild/darwin-x64@0.25.0':
+ '@esbuild/linux-arm@0.25.5':
optional: true
- '@esbuild/freebsd-arm64@0.21.5':
+ '@esbuild/linux-ia32@0.25.5':
optional: true
- '@esbuild/freebsd-arm64@0.25.0':
+ '@esbuild/linux-loong64@0.25.5':
optional: true
- '@esbuild/freebsd-x64@0.21.5':
+ '@esbuild/linux-mips64el@0.25.5':
optional: true
- '@esbuild/freebsd-x64@0.25.0':
+ '@esbuild/linux-ppc64@0.25.5':
optional: true
- '@esbuild/linux-arm64@0.21.5':
+ '@esbuild/linux-riscv64@0.25.5':
optional: true
- '@esbuild/linux-arm64@0.25.0':
+ '@esbuild/linux-s390x@0.25.5':
optional: true
- '@esbuild/linux-arm@0.21.5':
+ '@esbuild/linux-x64@0.25.5':
optional: true
- '@esbuild/linux-arm@0.25.0':
+ '@esbuild/netbsd-arm64@0.25.5':
optional: true
- '@esbuild/linux-ia32@0.21.5':
+ '@esbuild/netbsd-x64@0.25.5':
optional: true
- '@esbuild/linux-ia32@0.25.0':
+ '@esbuild/openbsd-arm64@0.25.5':
optional: true
- '@esbuild/linux-loong64@0.21.5':
+ '@esbuild/openbsd-x64@0.25.5':
optional: true
- '@esbuild/linux-loong64@0.25.0':
+ '@esbuild/sunos-x64@0.25.5':
optional: true
- '@esbuild/linux-mips64el@0.21.5':
+ '@esbuild/win32-arm64@0.25.5':
optional: true
- '@esbuild/linux-mips64el@0.25.0':
+ '@esbuild/win32-ia32@0.25.5':
optional: true
- '@esbuild/linux-ppc64@0.21.5':
- optional: true
-
- '@esbuild/linux-ppc64@0.25.0':
- optional: true
-
- '@esbuild/linux-riscv64@0.21.5':
- optional: true
-
- '@esbuild/linux-riscv64@0.25.0':
- optional: true
-
- '@esbuild/linux-s390x@0.21.5':
- optional: true
-
- '@esbuild/linux-s390x@0.25.0':
- optional: true
-
- '@esbuild/linux-x64@0.21.5':
- optional: true
-
- '@esbuild/linux-x64@0.25.0':
- optional: true
-
- '@esbuild/netbsd-arm64@0.25.0':
- optional: true
-
- '@esbuild/netbsd-x64@0.21.5':
- optional: true
-
- '@esbuild/netbsd-x64@0.25.0':
- optional: true
-
- '@esbuild/openbsd-arm64@0.25.0':
- optional: true
-
- '@esbuild/openbsd-x64@0.21.5':
- optional: true
-
- '@esbuild/openbsd-x64@0.25.0':
- optional: true
-
- '@esbuild/sunos-x64@0.21.5':
- optional: true
-
- '@esbuild/sunos-x64@0.25.0':
- optional: true
-
- '@esbuild/win32-arm64@0.21.5':
- optional: true
-
- '@esbuild/win32-arm64@0.25.0':
- optional: true
-
- '@esbuild/win32-ia32@0.21.5':
- optional: true
-
- '@esbuild/win32-ia32@0.25.0':
- optional: true
-
- '@esbuild/win32-x64@0.21.5':
- optional: true
-
- '@esbuild/win32-x64@0.25.0':
+ '@esbuild/win32-x64@0.25.5':
optional: true
'@isaacs/cliui@8.0.2':
@@ -3020,10 +2901,6 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
- '@jest/schemas@29.6.3':
- dependencies:
- '@sinclair/typebox': 0.27.8
-
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -3082,72 +2959,120 @@ snapshots:
dependencies:
which: 4.0.0
- '@parcel/watcher@2.0.3':
+ '@parcel/watcher-android-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.1': {}
+
+ '@parcel/watcher-darwin-x64@2.5.1': {}
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1': {}
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1': {}
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1': {}
+
+ '@parcel/watcher-linux-x64-musl@2.5.1': {}
+
+ '@parcel/watcher-win32-arm64@2.5.1': {}
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.1': {}
+
+ '@parcel/watcher@2.5.1':
dependencies:
- node-addon-api: 3.2.1
- node-gyp-build: 4.8.1
+ detect-libc: 1.0.3
+ is-glob: 4.0.3
+ micromatch: 4.0.8
+ node-addon-api: 7.1.1
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.1
+ '@parcel/watcher-darwin-arm64': 2.5.1
+ '@parcel/watcher-darwin-x64': 2.5.1
+ '@parcel/watcher-freebsd-x64': 2.5.1
+ '@parcel/watcher-linux-arm-glibc': 2.5.1
+ '@parcel/watcher-linux-arm-musl': 2.5.1
+ '@parcel/watcher-linux-arm64-glibc': 2.5.1
+ '@parcel/watcher-linux-arm64-musl': 2.5.1
+ '@parcel/watcher-linux-x64-glibc': 2.5.1
+ '@parcel/watcher-linux-x64-musl': 2.5.1
+ '@parcel/watcher-win32-arm64': 2.5.1
+ '@parcel/watcher-win32-ia32': 2.5.1
+ '@parcel/watcher-win32-x64': 2.5.1
'@pkgjs/parseargs@0.11.0':
optional: true
- '@rollup/rollup-android-arm-eabi@4.34.3':
+ '@rollup/rollup-android-arm-eabi@4.41.1':
optional: true
- '@rollup/rollup-android-arm64@4.34.3':
+ '@rollup/rollup-android-arm64@4.41.1':
optional: true
- '@rollup/rollup-darwin-arm64@4.34.3':
+ '@rollup/rollup-darwin-arm64@4.41.1':
optional: true
- '@rollup/rollup-darwin-x64@4.34.3':
+ '@rollup/rollup-darwin-x64@4.41.1':
optional: true
- '@rollup/rollup-freebsd-arm64@4.34.3':
+ '@rollup/rollup-freebsd-arm64@4.41.1':
optional: true
- '@rollup/rollup-freebsd-x64@4.34.3':
+ '@rollup/rollup-freebsd-x64@4.41.1':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.34.3':
+ '@rollup/rollup-linux-arm-gnueabihf@4.41.1':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.34.3':
+ '@rollup/rollup-linux-arm-musleabihf@4.41.1':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.34.3':
+ '@rollup/rollup-linux-arm64-gnu@4.41.1':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.34.3':
+ '@rollup/rollup-linux-arm64-musl@4.41.1':
optional: true
- '@rollup/rollup-linux-loongarch64-gnu@4.34.3':
+ '@rollup/rollup-linux-loongarch64-gnu@4.41.1':
optional: true
- '@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
+ '@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.34.3':
+ '@rollup/rollup-linux-riscv64-gnu@4.41.1':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.34.3':
+ '@rollup/rollup-linux-riscv64-musl@4.41.1':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.34.3':
+ '@rollup/rollup-linux-s390x-gnu@4.41.1':
optional: true
- '@rollup/rollup-linux-x64-musl@4.34.3':
+ '@rollup/rollup-linux-x64-gnu@4.41.1':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.34.3':
+ '@rollup/rollup-linux-x64-musl@4.41.1':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.34.3':
+ '@rollup/rollup-win32-arm64-msvc@4.41.1':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.34.3':
+ '@rollup/rollup-win32-ia32-msvc@4.41.1':
optional: true
- '@sinclair/typebox@0.27.8': {}
+ '@rollup/rollup-win32-x64-msvc@4.41.1':
+ optional: true
'@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.17)':
dependencies:
@@ -3166,48 +3091,52 @@ snapshots:
dependencies:
tailwindcss: 3.4.17
- '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-android-arm64@4.1.0':
optional: true
- '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-darwin-arm64@4.1.0':
optional: true
- '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-darwin-x64@4.1.0':
optional: true
- '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-freebsd-x64@4.1.0':
optional: true
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.0':
optional: true
- '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.0':
optional: true
- '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.0':
optional: true
- '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.0':
optional: true
- '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-x64-musl@4.1.0':
optional: true
- '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.19':
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.0':
optional: true
- '@tailwindcss/oxide@4.0.0-alpha.19':
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.0':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.0':
optionalDependencies:
- '@tailwindcss/oxide-android-arm64': 4.0.0-alpha.19
- '@tailwindcss/oxide-darwin-arm64': 4.0.0-alpha.19
- '@tailwindcss/oxide-darwin-x64': 4.0.0-alpha.19
- '@tailwindcss/oxide-freebsd-x64': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-arm64-gnu': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-arm64-musl': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-x64-gnu': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-x64-musl': 4.0.0-alpha.19
- '@tailwindcss/oxide-win32-x64-msvc': 4.0.0-alpha.19
+ '@tailwindcss/oxide-android-arm64': 4.1.0
+ '@tailwindcss/oxide-darwin-arm64': 4.1.0
+ '@tailwindcss/oxide-darwin-x64': 4.1.0
+ '@tailwindcss/oxide-freebsd-x64': 4.1.0
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.0
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.0
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.0
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.0
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.0
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.0
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.0
'@tailwindcss/typography@0.5.7(tailwindcss@3.4.17)':
dependencies:
@@ -3219,6 +3148,10 @@ snapshots:
'@types/braces@3.0.1': {}
+ '@types/chai@5.2.2':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+
'@types/color-name@1.1.4': {}
'@types/css.escape@1.5.2': {}
@@ -3227,9 +3160,13 @@ snapshots:
'@types/debounce@1.2.0': {}
+ '@types/dedent@0.7.2': {}
+
+ '@types/deep-eql@4.0.2': {}
+
'@types/dlv@1.1.4': {}
- '@types/estree@1.0.6': {}
+ '@types/estree@1.0.7': {}
'@types/find-up@4.0.0':
dependencies:
@@ -3261,7 +3198,7 @@ snapshots:
'@types/postcss-import@14.0.3':
dependencies:
- postcss: 8.4.31
+ postcss: 8.5.4
'@types/semver@7.3.10': {}
@@ -3275,36 +3212,48 @@ snapshots:
dependencies:
'@types/node': 18.19.43
- '@vitest/expect@1.6.1':
+ '@vitest/expect@3.2.1':
+ dependencies:
+ '@types/chai': 5.2.2
+ '@vitest/spy': 3.2.1
+ '@vitest/utils': 3.2.1
+ chai: 5.2.0
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.2.1(vite@6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0))':
dependencies:
- '@vitest/spy': 1.6.1
- '@vitest/utils': 1.6.1
- chai: 4.5.0
+ '@vitest/spy': 3.2.1
+ estree-walker: 3.0.3
+ magic-string: 0.30.17
+ optionalDependencies:
+ vite: 6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0)
+
+ '@vitest/pretty-format@3.2.1':
+ dependencies:
+ tinyrainbow: 2.0.0
- '@vitest/runner@1.6.1':
+ '@vitest/runner@3.2.1':
dependencies:
- '@vitest/utils': 1.6.1
- p-limit: 5.0.0
- pathe: 1.1.2
+ '@vitest/utils': 3.2.1
+ pathe: 2.0.3
- '@vitest/snapshot@1.6.1':
+ '@vitest/snapshot@3.2.1':
dependencies:
+ '@vitest/pretty-format': 3.2.1
magic-string: 0.30.17
- pathe: 1.1.2
- pretty-format: 29.7.0
+ pathe: 2.0.3
- '@vitest/spy@1.6.1':
+ '@vitest/spy@3.2.1':
dependencies:
- tinyspy: 2.2.1
+ tinyspy: 4.0.3
- '@vitest/utils@1.6.1':
+ '@vitest/utils@3.2.1':
dependencies:
- diff-sequences: 29.6.3
- estree-walker: 3.0.3
- loupe: 2.3.7
- pretty-format: 29.7.0
+ '@vitest/pretty-format': 3.2.1
+ loupe: 3.1.3
+ tinyrainbow: 2.0.0
- '@vscode/l10n@0.0.16': {}
+ '@vscode/l10n@0.0.18': {}
'@vscode/vsce@2.21.1':
dependencies:
@@ -3333,12 +3282,6 @@ snapshots:
abbrev@1.1.1: {}
- acorn-walk@8.3.4:
- dependencies:
- acorn: 8.14.0
-
- acorn@8.14.0: {}
-
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@@ -3351,8 +3294,6 @@ snapshots:
dependencies:
color-convert: 2.0.1
- ansi-styles@5.2.0: {}
-
ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
@@ -3372,7 +3313,7 @@ snapshots:
asap@2.0.6: {}
- assertion-error@1.1.0: {}
+ assertion-error@2.0.1: {}
azure-devops-node-api@11.2.0:
dependencies:
@@ -3457,15 +3398,13 @@ snapshots:
caniuse-lite@1.0.30001649: {}
- chai@4.5.0:
+ chai@5.2.0:
dependencies:
- assertion-error: 1.1.0
- check-error: 1.0.3
- deep-eql: 4.1.4
- get-func-name: 2.0.2
- loupe: 2.3.7
- pathval: 1.1.1
- type-detect: 4.1.0
+ assertion-error: 2.0.1
+ check-error: 2.1.1
+ deep-eql: 5.0.2
+ loupe: 3.1.3
+ pathval: 2.0.0
chalk@2.4.2:
dependencies:
@@ -3478,9 +3417,7 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
- check-error@1.0.3:
- dependencies:
- get-func-name: 2.0.2
+ check-error@2.1.1: {}
cheerio-select@2.1.0:
dependencies:
@@ -3516,7 +3453,7 @@ snapshots:
chownr@1.1.4:
optional: true
- cliui@7.0.4:
+ cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
@@ -3540,18 +3477,15 @@ snapshots:
concat-map@0.0.1: {}
- concurrently@7.0.0:
+ concurrently@9.1.2:
dependencies:
chalk: 4.1.2
- date-fns: 2.30.0
lodash: 4.17.21
- rxjs: 6.6.7
- spawn-command: 0.0.2
+ rxjs: 7.8.2
+ shell-quote: 1.8.2
supports-color: 8.1.1
tree-kill: 1.2.2
- yargs: 16.2.0
-
- confbox@0.1.8: {}
+ yargs: 17.7.2
cosmiconfig@7.1.0:
dependencies:
@@ -3583,21 +3517,17 @@ snapshots:
culori@4.0.1: {}
- date-fns@2.30.0:
- dependencies:
- '@babel/runtime': 7.25.0
-
debounce@1.2.0: {}
debug@3.2.7:
dependencies:
ms: 2.1.3
- debug@4.3.6:
+ debug@4.4.0:
dependencies:
- ms: 2.1.2
+ ms: 2.1.3
- debug@4.4.0:
+ debug@4.4.1:
dependencies:
ms: 2.1.3
@@ -3619,9 +3549,7 @@ snapshots:
dedent@1.5.3: {}
- deep-eql@4.1.4:
- dependencies:
- type-detect: 4.1.0
+ deep-eql@5.0.2: {}
deep-extend@0.6.0:
optional: true
@@ -3636,6 +3564,8 @@ snapshots:
detect-indent@6.0.0: {}
+ detect-libc@1.0.3: {}
+
detect-libc@2.0.3:
optional: true
@@ -3646,8 +3576,6 @@ snapshots:
didyoumean@1.2.2: {}
- diff-sequences@29.6.3: {}
-
dlv@1.1.3: {}
dom-serializer@2.0.0:
@@ -3704,65 +3632,41 @@ snapshots:
es-errors@1.3.0: {}
- esbuild-node-externals@1.14.0(esbuild@0.25.0):
+ es-module-lexer@1.7.0: {}
+
+ esbuild-node-externals@1.14.0(esbuild@0.25.5):
dependencies:
- esbuild: 0.25.0
+ esbuild: 0.25.5
find-up: 5.0.0
tslib: 2.6.3
- esbuild@0.21.5:
+ esbuild@0.25.5:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.21.5
- '@esbuild/android-arm': 0.21.5
- '@esbuild/android-arm64': 0.21.5
- '@esbuild/android-x64': 0.21.5
- '@esbuild/darwin-arm64': 0.21.5
- '@esbuild/darwin-x64': 0.21.5
- '@esbuild/freebsd-arm64': 0.21.5
- '@esbuild/freebsd-x64': 0.21.5
- '@esbuild/linux-arm': 0.21.5
- '@esbuild/linux-arm64': 0.21.5
- '@esbuild/linux-ia32': 0.21.5
- '@esbuild/linux-loong64': 0.21.5
- '@esbuild/linux-mips64el': 0.21.5
- '@esbuild/linux-ppc64': 0.21.5
- '@esbuild/linux-riscv64': 0.21.5
- '@esbuild/linux-s390x': 0.21.5
- '@esbuild/linux-x64': 0.21.5
- '@esbuild/netbsd-x64': 0.21.5
- '@esbuild/openbsd-x64': 0.21.5
- '@esbuild/sunos-x64': 0.21.5
- '@esbuild/win32-arm64': 0.21.5
- '@esbuild/win32-ia32': 0.21.5
- '@esbuild/win32-x64': 0.21.5
-
- esbuild@0.25.0:
- optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.0
- '@esbuild/android-arm': 0.25.0
- '@esbuild/android-arm64': 0.25.0
- '@esbuild/android-x64': 0.25.0
- '@esbuild/darwin-arm64': 0.25.0
- '@esbuild/darwin-x64': 0.25.0
- '@esbuild/freebsd-arm64': 0.25.0
- '@esbuild/freebsd-x64': 0.25.0
- '@esbuild/linux-arm': 0.25.0
- '@esbuild/linux-arm64': 0.25.0
- '@esbuild/linux-ia32': 0.25.0
- '@esbuild/linux-loong64': 0.25.0
- '@esbuild/linux-mips64el': 0.25.0
- '@esbuild/linux-ppc64': 0.25.0
- '@esbuild/linux-riscv64': 0.25.0
- '@esbuild/linux-s390x': 0.25.0
- '@esbuild/linux-x64': 0.25.0
- '@esbuild/netbsd-arm64': 0.25.0
- '@esbuild/netbsd-x64': 0.25.0
- '@esbuild/openbsd-arm64': 0.25.0
- '@esbuild/openbsd-x64': 0.25.0
- '@esbuild/sunos-x64': 0.25.0
- '@esbuild/win32-arm64': 0.25.0
- '@esbuild/win32-ia32': 0.25.0
- '@esbuild/win32-x64': 0.25.0
+ '@esbuild/aix-ppc64': 0.25.5
+ '@esbuild/android-arm': 0.25.5
+ '@esbuild/android-arm64': 0.25.5
+ '@esbuild/android-x64': 0.25.5
+ '@esbuild/darwin-arm64': 0.25.5
+ '@esbuild/darwin-x64': 0.25.5
+ '@esbuild/freebsd-arm64': 0.25.5
+ '@esbuild/freebsd-x64': 0.25.5
+ '@esbuild/linux-arm': 0.25.5
+ '@esbuild/linux-arm64': 0.25.5
+ '@esbuild/linux-ia32': 0.25.5
+ '@esbuild/linux-loong64': 0.25.5
+ '@esbuild/linux-mips64el': 0.25.5
+ '@esbuild/linux-ppc64': 0.25.5
+ '@esbuild/linux-riscv64': 0.25.5
+ '@esbuild/linux-s390x': 0.25.5
+ '@esbuild/linux-x64': 0.25.5
+ '@esbuild/netbsd-arm64': 0.25.5
+ '@esbuild/netbsd-x64': 0.25.5
+ '@esbuild/openbsd-arm64': 0.25.5
+ '@esbuild/openbsd-x64': 0.25.5
+ '@esbuild/sunos-x64': 0.25.5
+ '@esbuild/win32-arm64': 0.25.5
+ '@esbuild/win32-ia32': 0.25.5
+ '@esbuild/win32-x64': 0.25.5
escalade@3.1.2: {}
@@ -3770,31 +3674,12 @@ snapshots:
estree-walker@3.0.3:
dependencies:
- '@types/estree': 1.0.6
-
- execa@8.0.1:
- dependencies:
- cross-spawn: 7.0.6
- get-stream: 8.0.1
- human-signals: 5.0.0
- is-stream: 3.0.0
- merge-stream: 2.0.0
- npm-run-path: 5.3.0
- onetime: 6.0.0
- signal-exit: 4.1.0
- strip-final-newline: 3.0.0
+ '@types/estree': 1.0.7
expand-template@2.0.3:
optional: true
- fast-glob@3.2.4:
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- '@nodelib/fs.walk': 1.2.8
- glob-parent: 5.1.2
- merge2: 1.4.1
- micromatch: 4.0.7
- picomatch: 2.3.1
+ expect-type@1.2.1: {}
fast-glob@3.3.2:
dependencies:
@@ -3812,6 +3697,14 @@ snapshots:
dependencies:
pend: 1.2.0
+ fdir@6.4.3(picomatch@4.0.2):
+ optionalDependencies:
+ picomatch: 4.0.2
+
+ fdir@6.4.4(picomatch@4.0.2):
+ optionalDependencies:
+ picomatch: 4.0.2
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -3844,8 +3737,6 @@ snapshots:
get-caller-file@2.0.5: {}
- get-func-name@2.0.2: {}
-
get-intrinsic@1.2.4:
dependencies:
es-errors: 1.3.0
@@ -3856,8 +3747,6 @@ snapshots:
get-own-enumerable-property-symbols@3.0.2: {}
- get-stream@8.0.1: {}
-
github-from-package@0.0.0:
optional: true
@@ -3933,8 +3822,6 @@ snapshots:
domutils: 3.1.0
entities: 4.5.0
- human-signals@5.0.0: {}
-
ieee754@1.2.1:
optional: true
@@ -3991,8 +3878,6 @@ snapshots:
is-regexp@1.0.0: {}
- is-stream@3.0.0: {}
-
isarray@1.0.0: {}
isexe@2.0.0: {}
@@ -4015,8 +3900,6 @@ snapshots:
js-tokens@4.0.0: {}
- js-tokens@9.0.1: {}
-
json-parse-even-better-errors@2.3.1: {}
json-parse-even-better-errors@3.0.0: {}
@@ -4065,11 +3948,6 @@ snapshots:
dependencies:
uc.micro: 1.0.6
- local-pkg@0.5.1:
- dependencies:
- mlly: 1.7.4
- pkg-types: 1.3.1
-
locate-path@3.0.0:
dependencies:
p-locate: 3.0.0
@@ -4087,9 +3965,7 @@ snapshots:
lodash@4.17.21: {}
- loupe@2.3.7:
- dependencies:
- get-func-name: 2.0.2
+ loupe@3.1.3: {}
lru-cache@10.0.1: {}
@@ -4130,15 +4006,8 @@ snapshots:
type-fest: 1.4.0
yargs-parser: 20.2.9
- merge-stream@2.0.0: {}
-
merge2@1.4.1: {}
- micromatch@4.0.7:
- dependencies:
- braces: 3.0.3
- picomatch: 2.3.1
-
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -4146,8 +4015,6 @@ snapshots:
mime@1.6.0: {}
- mimic-fn@4.0.0: {}
-
mimic-response@3.1.0:
optional: true
@@ -4184,13 +4051,6 @@ snapshots:
dependencies:
minimist: 1.2.8
- mlly@1.7.4:
- dependencies:
- acorn: 8.14.0
- pathe: 2.0.2
- pkg-types: 1.3.1
- ufo: 1.5.4
-
moo@0.5.1: {}
move-file-cli@3.0.0:
@@ -4202,8 +4062,6 @@ snapshots:
dependencies:
path-exists: 5.0.0
- ms@2.1.2: {}
-
ms@2.1.3: {}
mute-stream@0.0.8: {}
@@ -4214,9 +4072,7 @@ snapshots:
object-assign: 4.1.1
thenify-all: 1.6.0
- nanoid@3.3.7: {}
-
- nanoid@3.3.8: {}
+ nanoid@3.3.11: {}
napi-build-utils@1.0.2:
optional: true
@@ -4226,12 +4082,10 @@ snapshots:
semver: 7.7.1
optional: true
- node-addon-api@3.2.1: {}
-
node-addon-api@4.3.0:
optional: true
- node-gyp-build@4.8.1: {}
+ node-addon-api@7.1.1: {}
node-releases@2.0.18: {}
@@ -4285,10 +4139,6 @@ snapshots:
npm-package-arg: 11.0.1
semver: 7.7.1
- npm-run-path@5.3.0:
- dependencies:
- path-key: 4.0.0
-
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
@@ -4303,10 +4153,6 @@ snapshots:
dependencies:
wrappy: 1.0.2
- onetime@6.0.0:
- dependencies:
- mimic-fn: 4.0.0
-
os-homedir@1.0.2: {}
os-tmpdir@1.0.2: {}
@@ -4324,10 +4170,6 @@ snapshots:
dependencies:
yocto-queue: 0.1.0
- p-limit@5.0.0:
- dependencies:
- yocto-queue: 1.1.1
-
p-locate@3.0.0:
dependencies:
p-limit: 2.3.0
@@ -4372,8 +4214,6 @@ snapshots:
path-key@3.1.1: {}
- path-key@4.0.0: {}
-
path-parse@1.0.7: {}
path-scurry@1.10.1:
@@ -4383,16 +4223,12 @@ snapshots:
path-type@4.0.0: {}
- pathe@1.1.2: {}
-
- pathe@2.0.2: {}
+ pathe@2.0.3: {}
- pathval@1.1.1: {}
+ pathval@2.0.0: {}
pend@1.2.0: {}
- picocolors@1.0.1: {}
-
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -4403,50 +4239,44 @@ snapshots:
pirates@4.0.6: {}
- pkg-types@1.3.1:
- dependencies:
- confbox: 0.1.8
- mlly: 1.7.4
- pathe: 2.0.2
-
pkg-up@3.1.0:
dependencies:
find-up: 3.0.0
- postcss-import@15.1.0(postcss@8.5.1):
+ postcss-import@15.1.0(postcss@8.5.4):
dependencies:
- postcss: 8.5.1
+ postcss: 8.5.4
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.20.0
- postcss-import@16.1.0(postcss@8.4.31):
+ postcss-import@16.1.0(postcss@8.5.4):
dependencies:
- postcss: 8.4.31
+ postcss: 8.5.4
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.20.0
- postcss-js@4.0.1(postcss@8.5.1):
+ postcss-js@4.0.1(postcss@8.5.4):
dependencies:
camelcase-css: 2.0.1
- postcss: 8.5.1
+ postcss: 8.5.4
postcss-load-config@3.0.1:
dependencies:
cosmiconfig: 7.1.0
import-cwd: 3.0.0
- postcss-load-config@4.0.2(postcss@8.5.1):
+ postcss-load-config@4.0.2(postcss@8.5.4):
dependencies:
lilconfig: 3.1.3
yaml: 2.5.0
optionalDependencies:
- postcss: 8.5.1
+ postcss: 8.5.4
- postcss-nested@6.2.0(postcss@8.5.1):
+ postcss-nested@6.2.0(postcss@8.5.4):
dependencies:
- postcss: 8.5.1
+ postcss: 8.5.4
postcss-selector-parser: 6.1.2
postcss-selector-parser@6.0.10:
@@ -4467,15 +4297,9 @@ snapshots:
postcss-value-parser@4.2.0: {}
- postcss@8.4.31:
+ postcss@8.5.4:
dependencies:
- nanoid: 3.3.7
- picocolors: 1.0.1
- source-map-js: 1.2.0
-
- postcss@8.5.1:
- dependencies:
- nanoid: 3.3.8
+ nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -4491,18 +4315,12 @@ snapshots:
pump: 3.0.0
rc: 1.2.8
simple-get: 4.0.1
- tar-fs: 2.1.1
+ tar-fs: 2.1.3
tunnel-agent: 0.6.0
optional: true
prettier@3.2.5: {}
- pretty-format@29.7.0:
- dependencies:
- '@jest/schemas': 29.6.3
- ansi-styles: 5.2.0
- react-is: 18.3.1
-
proc-log@3.0.0: {}
promise-inflight@1.0.1: {}
@@ -4534,8 +4352,6 @@ snapshots:
strip-json-comments: 2.0.1
optional: true
- react-is@18.3.1: {}
-
read-cache@1.0.0:
dependencies:
pify: 2.3.0
@@ -4598,8 +4414,6 @@ snapshots:
indent-string: 5.0.0
strip-indent: 4.0.0
- regenerator-runtime@0.14.1: {}
-
require-directory@2.1.1: {}
resolve-from@4.0.0: {}
@@ -4625,38 +4439,39 @@ snapshots:
dependencies:
glob: 7.2.3
- rollup@4.34.3:
+ rollup@4.41.1:
dependencies:
- '@types/estree': 1.0.6
+ '@types/estree': 1.0.7
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.34.3
- '@rollup/rollup-android-arm64': 4.34.3
- '@rollup/rollup-darwin-arm64': 4.34.3
- '@rollup/rollup-darwin-x64': 4.34.3
- '@rollup/rollup-freebsd-arm64': 4.34.3
- '@rollup/rollup-freebsd-x64': 4.34.3
- '@rollup/rollup-linux-arm-gnueabihf': 4.34.3
- '@rollup/rollup-linux-arm-musleabihf': 4.34.3
- '@rollup/rollup-linux-arm64-gnu': 4.34.3
- '@rollup/rollup-linux-arm64-musl': 4.34.3
- '@rollup/rollup-linux-loongarch64-gnu': 4.34.3
- '@rollup/rollup-linux-powerpc64le-gnu': 4.34.3
- '@rollup/rollup-linux-riscv64-gnu': 4.34.3
- '@rollup/rollup-linux-s390x-gnu': 4.34.3
- '@rollup/rollup-linux-x64-gnu': 4.34.3
- '@rollup/rollup-linux-x64-musl': 4.34.3
- '@rollup/rollup-win32-arm64-msvc': 4.34.3
- '@rollup/rollup-win32-ia32-msvc': 4.34.3
- '@rollup/rollup-win32-x64-msvc': 4.34.3
+ '@rollup/rollup-android-arm-eabi': 4.41.1
+ '@rollup/rollup-android-arm64': 4.41.1
+ '@rollup/rollup-darwin-arm64': 4.41.1
+ '@rollup/rollup-darwin-x64': 4.41.1
+ '@rollup/rollup-freebsd-arm64': 4.41.1
+ '@rollup/rollup-freebsd-x64': 4.41.1
+ '@rollup/rollup-linux-arm-gnueabihf': 4.41.1
+ '@rollup/rollup-linux-arm-musleabihf': 4.41.1
+ '@rollup/rollup-linux-arm64-gnu': 4.41.1
+ '@rollup/rollup-linux-arm64-musl': 4.41.1
+ '@rollup/rollup-linux-loongarch64-gnu': 4.41.1
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.41.1
+ '@rollup/rollup-linux-riscv64-gnu': 4.41.1
+ '@rollup/rollup-linux-riscv64-musl': 4.41.1
+ '@rollup/rollup-linux-s390x-gnu': 4.41.1
+ '@rollup/rollup-linux-x64-gnu': 4.41.1
+ '@rollup/rollup-linux-x64-musl': 4.41.1
+ '@rollup/rollup-win32-arm64-msvc': 4.41.1
+ '@rollup/rollup-win32-ia32-msvc': 4.41.1
+ '@rollup/rollup-win32-x64-msvc': 4.41.1
fsevents: 2.3.3
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
- rxjs@6.6.7:
+ rxjs@7.8.2:
dependencies:
- tslib: 1.14.1
+ tslib: 2.2.0
safe-buffer@5.2.1:
optional: true
@@ -4682,6 +4497,8 @@ snapshots:
shebang-regex@3.0.0: {}
+ shell-quote@1.8.2: {}
+
side-channel@1.0.6:
dependencies:
call-bind: 1.0.7
@@ -4707,12 +4524,8 @@ snapshots:
slide@1.1.6: {}
- source-map-js@1.2.0: {}
-
source-map-js@1.2.1: {}
- spawn-command@0.0.2: {}
-
spdx-compare@1.0.0:
dependencies:
array-find-index: 1.0.2
@@ -4745,7 +4558,7 @@ snapshots:
stackback@0.0.2: {}
- std-env@3.8.0: {}
+ std-env@3.9.0: {}
string-width@4.2.3:
dependencies:
@@ -4780,8 +4593,6 @@ snapshots:
strip-bom@3.0.0: {}
- strip-final-newline@3.0.0: {}
-
strip-indent@4.0.0:
dependencies:
min-indent: 1.0.1
@@ -4789,10 +4600,6 @@ snapshots:
strip-json-comments@2.0.1:
optional: true
- strip-literal@2.1.1:
- dependencies:
- js-tokens: 9.0.1
-
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.5
@@ -4833,22 +4640,22 @@ snapshots:
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
- postcss: 8.5.1
- postcss-import: 15.1.0(postcss@8.5.1)
- postcss-js: 4.0.1(postcss@8.5.1)
- postcss-load-config: 4.0.2(postcss@8.5.1)
- postcss-nested: 6.2.0(postcss@8.5.1)
+ postcss: 8.5.4
+ postcss-import: 15.1.0(postcss@8.5.4)
+ postcss-js: 4.0.1(postcss@8.5.4)
+ postcss-load-config: 4.0.2(postcss@8.5.4)
+ postcss-nested: 6.2.0(postcss@8.5.4)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
- tailwindcss@4.0.6: {}
+ tailwindcss@4.1.1: {}
tapable@2.2.1: {}
- tar-fs@2.1.1:
+ tar-fs@2.1.3:
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
@@ -4875,9 +4682,28 @@ snapshots:
tinybench@2.9.0: {}
- tinypool@0.8.4: {}
+ tinyexec@0.3.2: {}
- tinyspy@2.2.1: {}
+ tinyglobby@0.2.12:
+ dependencies:
+ fdir: 6.4.3(picomatch@4.0.2)
+ picomatch: 4.0.2
+
+ tinyglobby@0.2.13:
+ dependencies:
+ fdir: 6.4.4(picomatch@4.0.2)
+ picomatch: 4.0.2
+
+ tinyglobby@0.2.14:
+ dependencies:
+ fdir: 6.4.4(picomatch@4.0.2)
+ picomatch: 4.0.2
+
+ tinypool@1.1.0: {}
+
+ tinyrainbow@2.0.0: {}
+
+ tinyspy@4.0.3: {}
tmp-cache@1.1.0: {}
@@ -4895,9 +4721,9 @@ snapshots:
ts-interface-checker@0.1.13: {}
- tsconfck@3.1.4(typescript@5.3.3):
+ tsconfck@3.1.4(typescript@5.8.3):
optionalDependencies:
- typescript: 5.3.3
+ typescript: 5.8.3
tsconfig-paths@4.2.0:
dependencies:
@@ -4905,8 +4731,6 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
- tslib@1.14.1: {}
-
tslib@2.2.0: {}
tslib@2.6.3: {}
@@ -4918,8 +4742,6 @@ snapshots:
tunnel@0.0.6: {}
- type-detect@4.1.0: {}
-
type-fest@1.4.0: {}
typed-rest-client@1.8.11:
@@ -4928,12 +4750,10 @@ snapshots:
tunnel: 0.0.6
underscore: 1.13.7
- typescript@5.3.3: {}
+ typescript@5.8.3: {}
uc.micro@1.0.6: {}
- ufo@1.5.4: {}
-
underscore@1.13.7: {}
undici-types@5.26.5: {}
@@ -4961,15 +4781,16 @@ snapshots:
dependencies:
builtins: 5.0.1
- vite-node@1.6.1(@types/node@18.19.43):
+ vite-node@3.2.1(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0):
dependencies:
cac: 6.7.14
- debug: 4.4.0
- pathe: 1.1.2
- picocolors: 1.1.1
- vite: 5.4.14(@types/node@18.19.43)
+ debug: 4.4.1
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0)
transitivePeerDependencies:
- '@types/node'
+ - jiti
- less
- lightningcss
- sass
@@ -4978,67 +4799,81 @@ snapshots:
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
- vite-tsconfig-paths@4.3.2(typescript@5.3.3)(vite@5.4.14(@types/node@18.19.43)):
+ vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0)):
dependencies:
- debug: 4.3.6
+ debug: 4.4.0
globrex: 0.1.2
- tsconfck: 3.1.4(typescript@5.3.3)
+ tsconfck: 3.1.4(typescript@5.8.3)
optionalDependencies:
- vite: 5.4.14(@types/node@18.19.43)
+ vite: 6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0)
transitivePeerDependencies:
- supports-color
- typescript
- vite@5.4.14(@types/node@18.19.43):
+ vite@6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0):
dependencies:
- esbuild: 0.21.5
- postcss: 8.5.1
- rollup: 4.34.3
+ esbuild: 0.25.5
+ fdir: 6.4.4(picomatch@4.0.2)
+ picomatch: 4.0.2
+ postcss: 8.5.4
+ rollup: 4.41.1
+ tinyglobby: 0.2.13
optionalDependencies:
'@types/node': 18.19.43
fsevents: 2.3.3
+ jiti: 2.3.3
+ yaml: 2.5.0
- vitest@1.6.1(@types/node@18.19.43):
- dependencies:
- '@vitest/expect': 1.6.1
- '@vitest/runner': 1.6.1
- '@vitest/snapshot': 1.6.1
- '@vitest/spy': 1.6.1
- '@vitest/utils': 1.6.1
- acorn-walk: 8.3.4
- chai: 4.5.0
- debug: 4.4.0
- execa: 8.0.1
- local-pkg: 0.5.1
+ vitest@3.2.1(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0):
+ dependencies:
+ '@types/chai': 5.2.2
+ '@vitest/expect': 3.2.1
+ '@vitest/mocker': 3.2.1(vite@6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0))
+ '@vitest/pretty-format': 3.2.1
+ '@vitest/runner': 3.2.1
+ '@vitest/snapshot': 3.2.1
+ '@vitest/spy': 3.2.1
+ '@vitest/utils': 3.2.1
+ chai: 5.2.0
+ debug: 4.4.1
+ expect-type: 1.2.1
magic-string: 0.30.17
- pathe: 1.1.2
- picocolors: 1.1.1
- std-env: 3.8.0
- strip-literal: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.2
+ std-env: 3.9.0
tinybench: 2.9.0
- tinypool: 0.8.4
- vite: 5.4.14(@types/node@18.19.43)
- vite-node: 1.6.1(@types/node@18.19.43)
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.14
+ tinypool: 1.1.0
+ tinyrainbow: 2.0.0
+ vite: 6.3.5(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0)
+ vite-node: 3.2.1(@types/node@18.19.43)(jiti@2.3.3)(yaml@2.5.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 18.19.43
transitivePeerDependencies:
+ - jiti
- less
- lightningcss
+ - msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
- vscode-css-languageservice@6.2.9:
+ vscode-css-languageservice@6.3.6:
dependencies:
- '@vscode/l10n': 0.0.16
- vscode-languageserver-textdocument: 1.0.11
- vscode-languageserver-types: 3.17.3
- vscode-uri: 3.0.8
+ '@vscode/l10n': 0.0.18
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-languageserver-types: 3.17.5
+ vscode-uri: 3.1.0
vscode-emmet-helper-bundled@0.0.1: {}
@@ -5075,7 +4910,7 @@ snapshots:
vscode-jsonrpc: 8.2.0
vscode-languageserver-types: 3.17.5
- vscode-languageserver-textdocument@1.0.11: {}
+ vscode-languageserver-textdocument@1.0.12: {}
vscode-languageserver-types@3.17.2: {}
@@ -5087,9 +4922,13 @@ snapshots:
dependencies:
vscode-languageserver-protocol: 3.17.3
+ vscode-oniguruma@2.0.1: {}
+
+ vscode-textmate@9.2.0: {}
+
vscode-uri@3.0.2: {}
- vscode-uri@3.0.8: {}
+ vscode-uri@3.1.0: {}
which@2.0.2:
dependencies:
@@ -5135,15 +4974,17 @@ snapshots:
yargs-parser@20.2.9: {}
- yargs@16.2.0:
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
dependencies:
- cliui: 7.0.4
+ cliui: 8.0.1
escalade: 3.1.2
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
- yargs-parser: 20.2.9
+ yargs-parser: 21.1.1
yauzl@2.10.0:
dependencies:
@@ -5155,5 +4996,3 @@ snapshots:
buffer-crc32: 0.2.13
yocto-queue@0.1.0: {}
-
- yocto-queue@1.1.1: {}