diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index 3809535cb..770012e87 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -26,10 +26,10 @@ For example: npm, yarn
For example: macOS, Windows
-**Tailwind config**
+**Tailwind CSS Stylesheet (v4) or config file (v3)**
```js
-// Paste the contents of your Tailwind config file here
+// Paste the contents of your CSS file or config file here
```
**VS Code settings**
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/package.json b/packages/tailwindcss-language-server/package.json
index 5ed431fb5..768232e54 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.9",
+ "version": "0.14.22",
"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 a43910b22..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,
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 21296fc71..f4737e196 100644
--- a/packages/tailwindcss-language-server/src/testing/index.ts
+++ b/packages/tailwindcss-language-server/src/testing/index.ts
@@ -1,42 +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 | { [IS_A_SYMLINK]: true; filepath: string }
+ [filePath: string]: string | Uint8Array | StorageSymlink
}
-export interface TestConfig {
+export interface TestConfig> {
name: string
+ inputs?: TestInput[]
+
+ skipNPM?: boolean
fs?: Storage
debug?: boolean
- prepare?(utils: TestUtils): Promise
- handle(utils: TestUtils & Extras): void | Promise
+ 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}`)
@@ -46,14 +71,23 @@ 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
@@ -66,14 +100,16 @@ async function setup(config: TestConfig): Promise {
return {
root: baseDir,
+ input,
}
}
const IS_A_SYMLINK = Symbol('is-a-symlink')
-export const symlinkTo = function (filepath: string) {
+export function symlinkTo(filepath: string, type?: 'file' | 'dir'): StorageSymlink {
return {
[IS_A_SYMLINK]: true as const,
filepath,
+ type,
}
}
@@ -88,7 +124,14 @@ async function prepareFileSystem(base: string, storage: Storage) {
if (typeof content === 'object' && IS_A_SYMLINK in content) {
let target = path.resolve(base, content.filepath)
- await fs.symlink(target, fullPath)
+
+ let type: string = content.type
+
+ if (os.platform() === 'win32' && content.type === 'dir') {
+ type = 'junction'
+ }
+
+ await fs.symlink(target, fullPath, type)
continue
}
@@ -121,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 35da9386e..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/v4/design-system.ts b/packages/tailwindcss-language-server/src/util/v4/design-system.ts
index 05d2ecd3e..f312b95c0 100644
--- a/packages/tailwindcss-language-server/src/util/v4/design-system.ts
+++ b/packages/tailwindcss-language-server/src/util/v4/design-system.ts
@@ -219,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/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 e8970e08a..388e94af7 100644
--- a/packages/tailwindcss-language-server/tests/css/css-server.test.ts
+++ b/packages/tailwindcss-language-server/tests/css/css-server.test.ts
@@ -689,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 76de7f15d..6104ef7ae 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -21,7 +21,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -49,7 +49,7 @@ defineTest({
},
})
- expect(completion?.items.length).toBe(12288)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -137,7 +137,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -188,7 +188,7 @@ defineTest({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.1"
+ "tailwindcss": "4.1.1"
}
}
`,
@@ -205,7 +205,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.1',
+ version: '4.1.1',
isDefaultVersion: false,
},
})
@@ -233,7 +233,7 @@ defineTest({
},
})
- expect(completion?.items.length).toBe(12288)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -243,7 +243,7 @@ defineTest({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.1"
+ "tailwindcss": "4.1.1"
}
}
`,
@@ -270,7 +270,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.1',
+ version: '4.1.1',
isDefaultVersion: false,
},
})
@@ -322,7 +322,7 @@ defineTest({
expect(await client.project()).toMatchObject({
tailwind: {
- version: '4.0.6',
+ version: '4.1.1',
isDefaultVersion: true,
},
})
@@ -354,7 +354,7 @@ defineTest({
'package.json': json`
{
"dependencies": {
- "tailwindcss": "4.0.1"
+ "tailwindcss": "4.1.1"
}
}
`,
@@ -605,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`
@@ -650,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`
@@ -707,6 +719,12 @@ 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 `#`
@@ -737,7 +755,7 @@ defineTest({
presets: [require('some-pkg/config/tailwind.config.js').default]
}
`,
- 'packages/some-pkg': symlinkTo('packages/some-pkg#c3f1e'),
+ 'packages/some-pkg': symlinkTo('packages/some-pkg#c3f1e', 'dir'),
'packages/some-pkg#c3f1e/package.json': json`
{
"name": "some-pkg",
@@ -791,3 +809,89 @@ defineTest({
})
},
})
+
+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 },
+ },
+ },
+ ])
+})
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..9a01cf9ad
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/test-utils.ts
@@ -0,0 +1,66 @@
+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 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 ca4255d71..986fdc2b7 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,101 @@
## Prerelease
-- Nothing yet!
+- 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))
+
+## 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
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 3b5fd76a0..17f481a53 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.9",
+ "version": "0.14.22",
"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 a37486160..467d4e085 100755
--- a/packages/vscode-tailwindcss/src/extension.ts
+++ b/packages/vscode-tailwindcss/src/extension.ts
@@ -4,9 +4,7 @@ import type {
TextDocument,
WorkspaceFolder,
ConfigurationScope,
- WorkspaceConfiguration,
Selection,
- CancellationToken,
} from 'vscode'
import {
workspace as Workspace,
@@ -16,8 +14,6 @@ import {
SymbolInformation,
Position,
Range,
- RelativePattern,
- CancellationTokenSource,
} from 'vscode'
import type {
DocumentFilter,
@@ -34,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)
@@ -52,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[],
@@ -177,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', () => {
@@ -266,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()
})
@@ -278,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)
@@ -578,111 +525,34 @@ export async function activate(context: ExtensionContext) {
return client
}
- async function bootClientIfNeeded(): Promise {
- if (currentClient) {
- return
- }
-
- 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)
-
- if (!(await anyFolderNeedsLanguageServer(Workspace.workspaceFolders ?? [], source!.token))) {
- source?.dispose()
- return
- }
-
- source?.dispose()
-
- await bootWorkspaceClient()
- }
-
- async function anyFolderNeedsLanguageServer(
- folders: readonly WorkspaceFolder[],
- token: CancellationToken,
- ): Promise {
- for (let folder of folders) {
- if (await folderNeedsLanguageServer(folder, token)) {
- return true
- }
- }
-
- return false
- }
-
- async function folderNeedsLanguageServer(
- folder: WorkspaceFolder,
- token: CancellationToken,
- ): 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,
- token,
- )
-
- for (let file of configFiles) {
- return true
- }
-
- let cssFiles = await Workspace.findFiles(
- new RelativePattern(folder, `**/${CSS_GLOB}`),
- exclude,
- undefined,
- token,
- )
-
- 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: {}