diff --git a/package.json b/package.json index 218a4fb2..deb5fb5e 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.20.2", + "esbuild": "^0.24.0", "minimist": "^1.2.8", "prettier": "^3.2.5", "semver": "^7.5.4" diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json index 8faf0430..1c772dc1 100644 --- a/packages/tailwindcss-language-server/package.json +++ b/packages/tailwindcss-language-server/package.json @@ -63,7 +63,7 @@ "dlv": "1.1.3", "dset": "3.1.2", "enhanced-resolve": "^5.16.1", - "esbuild": "^0.20.2", + "esbuild": "^0.24.0", "fast-glob": "3.2.4", "find-up": "5.0.0", "jiti": "^2.3.3", @@ -81,6 +81,8 @@ "rimraf": "3.0.2", "stack-trace": "0.0.10", "tailwindcss": "3.4.4", + "tsconfck": "^3.1.4", + "tsconfig-paths": "^4.2.0", "typescript": "5.3.3", "vite-tsconfig-paths": "^4.3.1", "vitest": "^1.4.0", diff --git a/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts b/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts index 46d5d169..df9d38e1 100644 --- a/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts +++ b/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts @@ -1,4 +1,4 @@ -import path from 'node:path' +import * as path from 'node:path' import type { AtRule, Plugin } from 'postcss' import { normalizePath } from '../utils' diff --git a/packages/tailwindcss-language-server/src/css/resolve-css-imports.ts b/packages/tailwindcss-language-server/src/css/resolve-css-imports.ts index 788b5eea..7cfd7f3f 100644 --- a/packages/tailwindcss-language-server/src/css/resolve-css-imports.ts +++ b/packages/tailwindcss-language-server/src/css/resolve-css-imports.ts @@ -1,25 +1,62 @@ +import * as fs from 'node:fs/promises' import postcss from 'postcss' import postcssImport from 'postcss-import' -import { createResolver } from '../util/resolve' import { fixRelativePaths } from './fix-relative-paths' +import { Resolver } from '../resolver' -const resolver = createResolver({ - extensions: ['.css'], - mainFields: ['style'], - conditionNames: ['style'], -}) - -const resolveImports = postcss([ - postcssImport({ - resolve: (id, base) => resolveCssFrom(base, id), - }), - fixRelativePaths(), -]) - -export function resolveCssImports() { - return resolveImports -} +export function resolveCssImports({ + resolver, + loose = false, +}: { + resolver: Resolver + loose?: boolean +}) { + return postcss([ + // Hoist imports to the top of the file + { + postcssPlugin: 'hoist-at-import', + Once(root, { result }) { + if (!loose) return + + let hoist: postcss.AtRule[] = [] + let seenImportsAfterOtherNodes = false + + for (let node of root.nodes) { + if (node.type === 'atrule' && (node.name === 'import' || node.name === 'charset')) { + hoist.push(node) + } else if (hoist.length > 0 && (node.type === 'atrule' || node.type === 'rule')) { + seenImportsAfterOtherNodes = true + } + } + + root.prepend(hoist) + + if (!seenImportsAfterOtherNodes) return + + console.log( + `hoist-at-import: The file '${result.opts.from}' contains @import rules after other at rules. This is invalid CSS and may cause problems with your build.`, + ) + }, + }, + + postcssImport({ + async resolve(id, base) { + try { + return await resolver.resolveCssId(id, base) + } catch (e) { + // TODO: Need to test this on windows + return `/virtual:missing/${id}` + } + }, + + load(filepath) { + if (filepath.startsWith('/virtual:missing/')) { + return Promise.resolve('') + } -export function resolveCssFrom(base: string, id: string) { - return resolver.resolveSync({}, base, id) || id + return fs.readFile(filepath, 'utf-8') + }, + }), + fixRelativePaths(), + ]) } diff --git a/packages/tailwindcss-language-server/src/lib/constants.ts b/packages/tailwindcss-language-server/src/lib/constants.ts index 29d0e641..3ad6d2fa 100644 --- a/packages/tailwindcss-language-server/src/lib/constants.ts +++ b/packages/tailwindcss-language-server/src/lib/constants.ts @@ -2,3 +2,4 @@ export const CONFIG_GLOB = '{tailwind,tailwind.config,tailwind.*.config,tailwind.config.*}.{js,cjs,ts,mjs,mts,cts}' export const PACKAGE_LOCK_GLOB = '{package-lock.json,yarn.lock,pnpm-lock.yaml}' export const CSS_GLOB = '*.{css,scss,sass,less,pcss}' +export const TSCONFIG_GLOB = '{tsconfig,tsconfig.*,jsconfig,jsconfig.*}.json' diff --git a/packages/tailwindcss-language-server/src/lib/hook.ts b/packages/tailwindcss-language-server/src/lib/hook.ts index 9f7a3f96..8d994648 100644 --- a/packages/tailwindcss-language-server/src/lib/hook.ts +++ b/packages/tailwindcss-language-server/src/lib/hook.ts @@ -1,7 +1,7 @@ /** * Adapted from: https://github.com/elastic/require-in-the-middle */ -import Module from 'module' +import Module from 'node:module' import plugins from './plugins' let bundledModules = { diff --git a/packages/tailwindcss-language-server/src/oxide.ts b/packages/tailwindcss-language-server/src/oxide.ts index 7e10da7a..bb8700ff 100644 --- a/packages/tailwindcss-language-server/src/oxide.ts +++ b/packages/tailwindcss-language-server/src/oxide.ts @@ -1,4 +1,4 @@ -import { lte } from 'tailwindcss-language-service/src/util/semver' +import { lte } from '@tailwindcss/language-service/src/util/semver' // This covers the Oxide API from v4.0.0-alpha.1 to v4.0.0-alpha.18 declare namespace OxideV1 { diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts index e47dd534..fd805806 100644 --- a/packages/tailwindcss-language-server/src/project-locator.test.ts +++ b/packages/tailwindcss-language-server/src/project-locator.test.ts @@ -3,6 +3,7 @@ import * as path from 'node:path' import { ProjectLocator } from './project-locator' import { URL, fileURLToPath } from 'url' import { Settings } from '@tailwindcss/language-service/src/util/state' +import { createResolver } from './resolver' let settings: Settings = { tailwindCSS: { @@ -17,7 +18,8 @@ function testFixture(fixture: string, details: any[]) { let fixturePath = `${fixtures}/${fixture}` test.concurrent(fixture, async ({ expect }) => { - let locator = new ProjectLocator(fixturePath, settings) + let resolver = await createResolver({ root: fixturePath, tsconfig: true }) + let locator = new ProjectLocator(fixturePath, settings, resolver) let projects = await locator.search() for (let i = 0; i < Math.max(projects.length, details.length); i++) { @@ -195,3 +197,33 @@ testFixture('v4/custom-source', [ ], }, ]) + +testFixture('v4/missing-files', [ + // + { + config: 'app.css', + content: ['{URL}/package.json'], + }, +]) + +testFixture('v4/path-mappings', [ + // + { + config: 'app.css', + content: [ + '{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/a/my-config.ts', + '{URL}/src/a/my-plugin.ts', + '{URL}/tsconfig.json', + ], + }, +]) + +testFixture('v4/invalid-import-order', [ + // + { + config: 'tailwind.css', + content: ['{URL}/package.json'], + }, +]) diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts index 0c025914..08ba66fa 100644 --- a/packages/tailwindcss-language-server/src/project-locator.ts +++ b/packages/tailwindcss-language-server/src/project-locator.ts @@ -7,11 +7,10 @@ import type { Settings } from '@tailwindcss/language-service/src/util/state' import { CONFIG_GLOB, CSS_GLOB } from './lib/constants' import { readCssFile } from './util/css' import { Graph } from './graph' -import type { AtRule, Message } from 'postcss' import { type DocumentSelector, DocumentSelectorPriority } from './projects' import { CacheMap } from './cache-map' import { getPackageRoot } from './util/get-package-root' -import { resolveFrom } from './util/resolveFrom' +import type { Resolver } from './resolver' import { type Feature, supportedFeatures } from '@tailwindcss/language-service/src/features' import { extractSourceDirectives, resolveCssImports } from './css' import { normalizeDriveLetter, normalizePath, pathToFileURL } from './utils' @@ -46,6 +45,7 @@ export class ProjectLocator { constructor( private base: string, private settings: Settings, + private resolver: Resolver, ) {} async search(): Promise { @@ -130,7 +130,12 @@ export class ProjectLocator { private async createProject(config: ConfigEntry): Promise { let tailwind = await this.detectTailwindVersion(config) - console.log(JSON.stringify({ tailwind })) + console.log( + JSON.stringify({ + tailwind, + path: config.path, + }), + ) // A JS/TS config file was loaded from an `@config` directive in a CSS file // This is only relevant for v3 projects so we'll do some feature detection @@ -191,7 +196,11 @@ export class ProjectLocator { }) // - Content patterns from config - for await (let selector of contentSelectorsFromConfig(config, tailwind.features)) { + for await (let selector of contentSelectorsFromConfig( + config, + tailwind.features, + this.resolver, + )) { selectors.push(selector) } @@ -321,8 +330,6 @@ export class ProjectLocator { // we'll verify after config resolution. let configPath = file.configPathInCss() if (configPath) { - // We don't need the content for this file anymore - file.content = null file.configs.push( configs.remember(configPath, () => ({ // A CSS file produced a JS config file @@ -340,7 +347,7 @@ export class ProjectLocator { } // Resolve imports in all the CSS files - await Promise.all(imports.map((file) => file.resolveImports())) + await Promise.all(imports.map((file) => file.resolveImports(this.resolver))) // Resolve real paths for all the files in the CSS import graph await Promise.all(imports.map((file) => file.resolveRealpaths())) @@ -418,7 +425,10 @@ export class ProjectLocator { private async detectTailwindVersion(config: ConfigEntry) { try { - let metadataPath = resolveFrom(path.dirname(config.path), 'tailwindcss/package.json') + let metadataPath = await this.resolver.resolveJsId( + 'tailwindcss/package.json', + path.dirname(config.path), + ) let { version } = require(metadataPath) let features = supportedFeatures(version) @@ -445,14 +455,14 @@ export class ProjectLocator { function contentSelectorsFromConfig( entry: ConfigEntry, features: Feature[], - actualConfig?: any, + resolver: Resolver, ): AsyncIterable { if (entry.type === 'css') { - return contentSelectorsFromCssConfig(entry) + return contentSelectorsFromCssConfig(entry, resolver) } if (entry.type === 'js') { - return contentSelectorsFromJsConfig(entry, features, actualConfig) + return contentSelectorsFromJsConfig(entry, features) } } @@ -497,7 +507,10 @@ async function* contentSelectorsFromJsConfig( } } -async function* contentSelectorsFromCssConfig(entry: ConfigEntry): AsyncIterable { +async function* contentSelectorsFromCssConfig( + entry: ConfigEntry, + resolver: Resolver, +): AsyncIterable { let auto = false for (let item of entry.content) { if (item.kind === 'file') { @@ -513,7 +526,12 @@ async function* contentSelectorsFromCssConfig(entry: ConfigEntry): AsyncIterable // other entries should have sources. let sources = entry.entries.flatMap((entry) => entry.sources) - for await (let pattern of detectContentFiles(entry.packageRoot, entry.path, sources)) { + for await (let pattern of detectContentFiles( + entry.packageRoot, + entry.path, + sources, + resolver, + )) { yield { pattern, priority: DocumentSelectorPriority.CONTENT_FILE, @@ -527,11 +545,15 @@ async function* detectContentFiles( base: string, inputFile: string, sources: string[], + resolver: Resolver, ): AsyncIterable { try { - let oxidePath = resolveFrom(path.dirname(base), '@tailwindcss/oxide') + let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', path.dirname(base)) oxidePath = pathToFileURL(oxidePath).href - let oxidePackageJsonPath = resolveFrom(path.dirname(base), '@tailwindcss/oxide/package.json') + let oxidePackageJsonPath = await resolver.resolveJsId( + '@tailwindcss/oxide/package.json', + path.dirname(base), + ) let oxidePackageJson = JSON.parse(await fs.readFile(oxidePackageJsonPath, 'utf8')) let result = await oxide.scan({ @@ -594,19 +616,26 @@ class FileEntry { } } - async resolveImports() { + async resolveImports(resolver: Resolver) { try { - let result = await resolveCssImports().process(this.content, { from: this.path }) + let result = await resolveCssImports({ resolver, loose: true }).process(this.content, { + from: this.path, + }) let deps = result.messages.filter((msg) => msg.type === 'dependency') + deps = deps.filter((msg) => { + return !msg.file.startsWith('/virtual:missing/') + }) + // Record entries for each of the dependencies this.deps = deps.map((msg) => new FileEntry('css', normalizePath(msg.file))) // Replace the file content with the processed CSS this.content = result.css - } catch { - // TODO: Errors here should be surfaced in tests and possibly the user in - // `trace` logs or something like that + } catch (err) { + console.debug(`Unable to resolve imports for ${this.path}.`) + console.debug(`This may result in failure to locate Tailwind CSS projects.`) + console.error(err) } } diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index 2da5a4c7..ea440c22 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -20,8 +20,8 @@ import { FileChangeType } from 'vscode-languageserver/node' import type { TextDocument } from 'vscode-languageserver-textdocument' import { URI } from 'vscode-uri' import { showError, SilentError } from './util/error' -import * as path from 'path' -import * as fs from 'fs' +import * as path from 'node:path' +import * as fs from 'node:fs' import findUp from 'find-up' import picomatch from 'picomatch' import { resolveFrom, setPnpApi } from './util/resolveFrom' @@ -35,6 +35,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 { Resolver } from './resolver' import { doComplete, resolveCompletionItem, @@ -53,7 +54,7 @@ import { getDocumentColors } from '@tailwindcss/language-service/src/documentCol import { getDocumentLinks } from '@tailwindcss/language-service/src/documentLinksProvider' import { debounce } from 'debounce' import { getModuleDependencies } from './util/getModuleDependencies' -import assert from 'assert' +import assert from 'node:assert' // import postcssLoadConfig from 'postcss-load-config' import { bigSign } from '@tailwindcss/language-service/src/util/jit' import { getColor } from '@tailwindcss/language-service/src/util/color' @@ -80,6 +81,7 @@ import type { ProjectConfig } from './project-locator' import { supportedFeatures } from '@tailwindcss/language-service/src/features' import { loadDesignSystem } from './util/v4' import { readCssFile } from './util/css' +import type { DesignSystem } from '@tailwindcss/language-service/src/util/v4' const colorNames = Object.keys(namedColors) @@ -107,7 +109,7 @@ export interface ProjectService { onDocumentColor(params: DocumentColorParams): Promise onColorPresentation(params: ColorPresentationParams): Promise onCodeAction(params: CodeActionParams): Promise - onDocumentLinks(params: DocumentLinkParams): DocumentLink[] + onDocumentLinks(params: DocumentLinkParams): Promise sortClassLists(classLists: string[]): string[] dependencies(): Iterable @@ -186,6 +188,7 @@ export async function createProjectService( initialTailwindVersion: string, getConfiguration: (uri?: string) => Promise, userLanguages: Record, + parentResolver?: Resolver, ): Promise { /* Project dependencies require a design system reload */ let dependencies = new Set() @@ -228,7 +231,10 @@ export async function createProjectService( }, async readDirectory(document, directory) { try { - directory = path.resolve(path.dirname(getFileFsPath(document.uri)), directory) + let baseDir = path.dirname(getFileFsPath(document.uri)) + directory = await resolver.substituteId(`${directory}/`, baseDir) + directory = path.resolve(baseDir, directory) + let dirents = await fs.promises.readdir(directory, { withFileTypes: true }) let result: Array<[string, { isDirectory: boolean }] | null> = await Promise.all( @@ -265,6 +271,10 @@ export async function createProjectService( ]) } + let resolver = await parentResolver.child({ + root: projectConfig.folder, + }) + function log(...args: string[]): void { console.log( `[${path.relative(projectConfig.folder, projectConfig.configPath)}] ${args.join(' ')}`, @@ -432,9 +442,9 @@ export async function createProjectService( let applyComplexClasses: any try { - let tailwindcssPath = resolveFrom(configDir, 'tailwindcss') - const tailwindcssPkgPath = resolveFrom(configDir, 'tailwindcss/package.json') - const tailwindDir = path.dirname(tailwindcssPkgPath) + let tailwindcssPath = await resolver.resolveJsId('tailwindcss', configDir) + let tailwindcssPkgPath = await resolver.resolveJsId('tailwindcss/package.json', configDir) + let tailwindDir = path.dirname(tailwindcssPkgPath) tailwindcssVersion = require(tailwindcssPkgPath).version let features = supportedFeatures(tailwindcssVersion) @@ -442,13 +452,16 @@ export async function createProjectService( tailwindcssPath = pathToFileURL(tailwindcssPath).href tailwindcss = await import(tailwindcssPath) - tailwindcss = tailwindcss.default ?? tailwindcss + + if (!features.includes('css-at-theme')) { + tailwindcss = tailwindcss.default ?? tailwindcss + } + log(`Loaded tailwindcss v${tailwindcssVersion}: ${tailwindDir}`) if (features.includes('css-at-theme')) { state.configPath = configPath state.version = tailwindcssVersion - // TODO: Handle backwards compat stuff here too state.isCssConfig = true state.v4 = true state.jit = true @@ -756,6 +769,7 @@ export async function createProjectService( try { let css = await readCssFile(state.configPath) let designSystem = await loadDesignSystem( + resolver, state.modules.tailwindcss.module, state.configPath, css, @@ -1023,12 +1037,24 @@ export async function createProjectService( if (!state.v4) return console.log('---- RELOADING DESIGN SYSTEM ----') + console.log(`---- ${state.configPath} ----`) + let css = await readCssFile(state.configPath) - let designSystem = await loadDesignSystem( - state.modules.tailwindcss.module, - state.configPath, - css, - ) + let designSystem: DesignSystem + + let start = process.hrtime.bigint() + + try { + designSystem = await loadDesignSystem( + resolver, + state.modules.tailwindcss.module, + state.configPath, + css, + ) + } catch (err) { + console.error(err) + throw err + } // TODO: This is weird and should be changed // We use Object.create so no global state is mutated until necessary @@ -1064,7 +1090,9 @@ export async function createProjectService( // TODO: Need to verify how well this works across various editors // updateCapabilities() - console.log('---- RELOADED ----') + let elapsed = process.hrtime.bigint() - start + + console.log(`---- RELOADED IN ${(Number(elapsed) / 1e6).toFixed(2)}ms ----`) }, state, @@ -1127,13 +1155,21 @@ export async function createProjectService( return doCodeActions(state, params, document) }, null) }, - onDocumentLinks(params: DocumentLinkParams): DocumentLink[] { + onDocumentLinks(params: DocumentLinkParams): Promise { if (!state.enabled) return null let document = documentService.getDocument(params.textDocument.uri) if (!document) return null - return getDocumentLinks(state, document, (linkPath) => - URI.file(path.resolve(path.dirname(URI.parse(document.uri).fsPath), linkPath)).toString(), - ) + + let documentPath = URI.parse(document.uri).fsPath + let baseDir = path.dirname(documentPath) + + async function resolveTarget(linkPath: string) { + linkPath = (await resolver.substituteId(linkPath, baseDir)) ?? linkPath + + return URI.file(path.resolve(baseDir, linkPath)).toString() + } + + return getDocumentLinks(state, document, resolveTarget) }, provideDiagnostics: debounce( (document: TextDocument) => { diff --git a/packages/tailwindcss-language-server/src/resolver/index.ts b/packages/tailwindcss-language-server/src/resolver/index.ts new file mode 100644 index 00000000..b5c6db94 --- /dev/null +++ b/packages/tailwindcss-language-server/src/resolver/index.ts @@ -0,0 +1,250 @@ +import * as fs from 'node:fs' +import * as path from 'node:path' +import { + CachedInputFileSystem, + ResolverFactory, + Resolver as BaseResolver, + FileSystem, +} from 'enhanced-resolve' +import { loadPnPApi, type PnpApi } from './pnp' +import { loadTsConfig, type TSConfigApi } from './tsconfig' + +export interface ResolverOptions { + /** + * The root directory for the resolver + */ + root: string + + /** + * Whether or not the resolver should attempt to use PnP resolution. + * + * If `true`, the resolver will attempt to load the PnP API and use it for + * resolution. However, if an API is provided, the resolver will use that API + * instead. + */ + pnp?: boolean | PnpApi + + /** + * Whether or not the resolver should load tsconfig path mappings. + * + * If `true`, the resolver will look for all `tsconfig` files in the project + * and use them to resolve module paths where possible. However, if an API is + * provided, the resolver will use that API to resolve module paths. + */ + tsconfig?: boolean | TSConfigApi + + /** + * A filesystem to use for resolution. If not provided, the resolver will + * create one and use it internally for itself and any child resolvers that + * do not provide their own filesystem. + */ + fileSystem?: FileSystem +} + +export interface Resolver { + /** + * Sets up the PnP API if it is available such that globals like `require` + * have been monkey-patched to use PnP resolution. + * + * This function does nothing if PnP resolution is not enabled or if the PnP + * API is not available. + */ + setupPnP(): Promise + + /** + * Resolves a JavaScript module to a file path. + * + * Assumes dynamic imports or some other ESM-captable mechanism will be used + * to load the module. Tries to resolve the ESM module first, then falls back + * to the CommonJS module if the ESM module is not found. + * + * @param id The module or file to resolve + * @param base The base directory to resolve the module from + */ + resolveJsId(id: string, base: string): Promise + + /** + * Resolves a CSS module to a file path. + * + * @param id The module or file to resolve + * @param base The base directory to resolve the module from + */ + resolveCssId(id: string, base: string): Promise + + /** + * Resolves a module to a possible file or directory path. + * + * This provides reasonable results when TypeScript config files are in use. + * This file may not exist but is the likely path that would be used to load + * the module if it were to exist. + * + * @param id The module, file, or directory to resolve + * @param base The base directory to resolve the module from + */ + substituteId(id: string, base: string): Promise + + /** + * Return a list of path resolution aliases for the given base directory + */ + aliases(base: string): Promise> + + /** + * Create a child resolver with the given options. + * + * Use this to share state between resolvers. For example, if a resolver has + * already loaded the PnP API, you can create a child resolver that reuses + * the same PnP API without needing to load it again. + */ + child(opts: Partial): Promise + + /** + * Refresh information the resolver may have cached + * + * This may look for new TypeScript configs if necessary + */ + refresh(): Promise +} + +export async function createResolver(opts: ResolverOptions): Promise { + let fileSystem = opts.fileSystem ? opts.fileSystem : new CachedInputFileSystem(fs, 4000) + + let pnpApi: PnpApi | null = null + + // Load PnP API if requested + if (typeof opts.pnp === 'object') { + pnpApi = opts.pnp + } else if (opts.pnp) { + pnpApi = await loadPnPApi(opts.root) + } + + let tsconfig: TSConfigApi | null = null + + // Load TSConfig path mappings + if (typeof opts.tsconfig === 'object') { + tsconfig = opts.tsconfig + } else if (opts.tsconfig) { + try { + tsconfig = await loadTsConfig(opts.root) + } catch (err) { + // We don't want to hard crash in case of an error handling tsconfigs + // It does affect what projects we can resolve or how we load files + // but the LSP shouldn't become unusable because of it. + console.error('Failed to load tsconfig', err) + } + } + + let esmResolver = ResolverFactory.createResolver({ + fileSystem, + extensions: ['.mjs', '.js'], + mainFields: ['module'], + conditionNames: ['node', 'import'], + pnpApi, + }) + + let cjsResolver = ResolverFactory.createResolver({ + fileSystem, + extensions: ['.cjs', '.js'], + mainFields: ['main'], + conditionNames: ['node', 'require'], + pnpApi, + }) + + let cssResolver = ResolverFactory.createResolver({ + fileSystem, + extensions: ['.css'], + mainFields: ['style'], + conditionNames: ['style'], + pnpApi, + + // Given `foo/bar.css` try `./foo/bar.css` first before trying `foo/bar.css` + // as a module + preferRelative: true, + }) + + async function resolveId( + resolver: BaseResolver, + id: string, + base: string, + ): Promise { + // Windows-specific path tweaks + if (path.sep === '\\') { + // Absolute path on Network Share + if (id.startsWith('\\\\')) return id + + // Absolute path on Network Share (normalized) + if (id.startsWith('//')) return id + + // Relative to Network Share (normalized) + if (base.startsWith('//')) base = `\\\\${base.slice(2)}` + } + + if (tsconfig) { + let match = await tsconfig.resolveId(id, base) + if (match) id = match + } + + return new Promise((resolve, reject) => { + resolver.resolve({}, base, id, {}, (err, res) => { + if (err) { + reject(err) + } else { + resolve(res) + } + }) + }) + } + + async function resolveJsId(id: string, base: string): Promise { + try { + return (await resolveId(esmResolver, id, base)) || id + } catch { + return (await resolveId(cjsResolver, id, base)) || id + } + } + + async function resolveCssId(id: string, base: string): Promise { + return (await resolveId(cssResolver, id, base)) || id + } + + // Takes a path which may or may not be complete and returns the aliased path + // if possible + async function substituteId(id: string, base: string): Promise { + return (await tsconfig?.substituteId(id, base)) ?? id + } + + async function setupPnP() { + pnpApi?.setup() + } + + async function aliases(base: string) { + if (!tsconfig) return {} + + return await tsconfig.paths(base) + } + + async function refresh() { + await tsconfig?.refresh() + } + + return { + setupPnP, + resolveJsId, + resolveCssId, + substituteId, + refresh, + + aliases, + + child(childOpts: Partial) { + return createResolver({ + ...opts, + ...childOpts, + + // Inherit defaults from parent + pnp: childOpts.pnp ?? pnpApi, + tsconfig: childOpts.tsconfig ?? tsconfig, + fileSystem: childOpts.fileSystem ?? fileSystem, + }) + }, + } +} diff --git a/packages/tailwindcss-language-server/src/resolver/pnp.ts b/packages/tailwindcss-language-server/src/resolver/pnp.ts new file mode 100644 index 00000000..543db0fa --- /dev/null +++ b/packages/tailwindcss-language-server/src/resolver/pnp.ts @@ -0,0 +1,49 @@ +import findUp from 'find-up' +import * as path from 'node:path' + +export interface PnpApi { + setup(): void + resolveToUnqualified: (arg0: string, arg1: string, arg2: object) => null | string +} + +const cache = new Map() + +/** + * Loads the PnP API from the given directory if found. + * We intentionally do not call `setup` to monkey patch global APIs + * TODO: Verify that we can get by without doing this + */ +export async function loadPnPApi(root: string): Promise { + let existing = cache.get(root) + if (existing !== undefined) { + return existing + } + + let pnpPath = await findPnPApi(path.normalize(root)) + if (!pnpPath) { + cache.set(root, null) + return null + } + + let mod = await import(pnpPath) + let api = mod.default + cache.set(root, api) + return api +} + +/** + * Locates the PnP API file for a given directory + */ +async function findPnPApi(root: string): Promise { + let names = ['.pnp.js', '.pnp.cjs'] + + for (let name of names) { + let filepath = path.join(root, name) + + if (await findUp.exists(filepath)) { + return filepath + } + } + + return null +} diff --git a/packages/tailwindcss-language-server/src/resolver/tsconfig.ts b/packages/tailwindcss-language-server/src/resolver/tsconfig.ts new file mode 100644 index 00000000..e578613f --- /dev/null +++ b/packages/tailwindcss-language-server/src/resolver/tsconfig.ts @@ -0,0 +1,285 @@ +// This implementation is inspired by and very loosely based on a Vite plugin +// with many simplifications and changes for our use case. +// +// The Vite plugin `vite-tsconfig-paths` can be found here: +// MIT License | Copyright (c) Alec Larson +// https://github.com/aleclarson/vite-tsconfig-paths + +import * as path from 'node:path' +import * as tsconfig from 'tsconfig-paths' +import * as tsconfck from 'tsconfck' +import { normalizePath } from '../utils' +import { DefaultMap } from '../util/default-map' + +export interface TSConfigApi { + /** + * Get the tsconfig paths used in the given directory + * + * @param base The directory to get the paths for + */ + paths(base: string): Promise> + + /** + * Resolve a module to a file path based on the tsconfig paths + * + * @param id The module or file to resolve + * @param base The directory to resolve the module from + */ + resolveId(id: string, base: string): Promise + + /** + * Given an id and base path turn it into a path that's likely to be + * the one that will can be used to load the module. + * + * @param id The module, file, or directory to resolve + * @param base The directory to resolve the module from + */ + substituteId(id: string, base: string): Promise + + /** + * Refresh information on available tsconfig paths. + * + * This rescans the project for tsconfig files and updates the matchers. + */ + refresh(): Promise + + /** + * Errors we encountered while trying to load the tsconfig files. + * + * We don't crash on errors because we want to be able to provide partial info + * even if some of the tsconfig files are invalid. + */ + errors: unknown[] +} + +export async function loadTsConfig(root: string): Promise { + let { configs, errors } = await findConfigs(root) + + let matchers = await createMatchers(configs) + + // 5. Create matchers for each project + async function resolveId(id: string, base: string) { + for (let projectDir of walkPaths(base)) { + for (let { match } of matchers.get(projectDir)) { + try { + return await match(id) + } catch (err) { + // If we got here we found a valid resolver for this path but it + // failed to resolve the path then we should stop looking. If we + // didn't we might end up using a resolver that would give us a + // valid path but not the one we want. + return null + } + } + } + + return null + } + + async function substituteId(id: string, base: string) { + for (let projectDir of walkPaths(base)) { + for (let { match } of matchers.get(projectDir)) { + try { + return await match(id, { mustExist: false }) + } catch (err) { + // If we got here we found a valid resolver for this path but it + // failed to resolve the path then we should stop looking. If we + // didn't we might end up using a resolver that would give us a + // valid path but not the one we want. + return null + } + } + } + + return null + } + + async function paths(base: string) { + for (let projectDir of walkPaths(base)) { + for (let { paths } of matchers.get(projectDir)) { + if (Object.keys(paths).length) return paths + } + } + + return {} + } + + async function refresh() { + let { configs, errors } = await findConfigs(root) + + matchers = await createMatchers(configs) + + if (errors.length) { + throw new AggregateError(errors) + } + } + + return { + resolveId, + substituteId, + paths, + refresh, + errors, + } +} + +async function findConfigs(root: string): Promise<{ + configs: Set + errors: unknown[] +}> { + // 1. Find all tsconfig files in the project + let files = await tsconfck.findAll(root, { + configNames: ['tsconfig.json', 'jsconfig.json'], + skip(dir) { + if (dir === 'node_modules') return true + if (dir === '.git') return true + + // TODO: Incorporate thee `exclude` option from VSCode settings. + // + // Doing so here is complicated because we don't have access to the + // full path to the file here and we need that to match it against the + // exclude patterns. + // + // This probably means we need to filter them after we've found them all. + + return false + }, + }) + + // 2. Load them all + let options: tsconfck.TSConfckParseOptions = { + root, + cache: new tsconfck.TSConfckCache(), + } + + let parsed = new Set() + let errors: unknown[] = [] + + for (let file of files) { + try { + let result = await tsconfck.parse(file, options) + parsed.add(result) + } catch (err) { + errors.push(err) + } + } + + // 3. Extract referenced projects + for (let result of parsed) { + if (!result.referenced) continue + + // Mach against referenced projects rather than the project itself + for (let ref of result.referenced) { + parsed.add(ref) + } + + // And use the project itself as a fallback since project references can + // be used to override the parent project. + parsed.delete(result) + parsed.add(result) + + result.referenced = undefined + } + + for (let err of errors) { + console.error(err) + } + + return { configs: parsed, errors } +} + +interface MatchOptions { + mustExist?: boolean +} + +interface Matcher { + match(id: string, opts?: MatchOptions): Promise + paths: Record +} + +async function createMatchers( + configs: Iterable, +): Promise> { + let matchers = new DefaultMap(() => []) + + let assumeExists: tsconfig.FileExistsAsync = (_, callback) => callback(undefined, true) + + for (let result of configs) { + let parent = normalizePath(path.dirname(result.tsconfigFile)) + + let opts = result.tsconfig.compilerOptions ?? {} + + let baseUrl = findBaseDir(result) + let absoluteBaseUrl = path.resolve(parent, baseUrl || '') + + let matchPath!: tsconfig.MatchPathAsync + + function match(id: string, { mustExist = true }: MatchOptions = {}) { + matchPath ??= tsconfig.createMatchPathAsync( + absoluteBaseUrl, + opts.paths ?? {}, + undefined, + baseUrl !== undefined, + ) + + let isPrefixMatch = mustExist === false ? id.endsWith('/') : false + + if (isPrefixMatch) { + id += '__placeholder__' + } + + return new Promise((resolve, reject) => { + matchPath( + id, + undefined, + mustExist === false ? assumeExists : undefined, + undefined, + (err, path) => { + if (err) return reject(err) + + if (isPrefixMatch) { + path = path.replace(/__placeholder__$/, '') + } + + return resolve(path) + }, + ) + }) + } + + matchers.get(parent).push({ + match, + paths: opts.paths ?? {}, + }) + } + + return matchers +} + +function* walkPaths(base: string) { + let projectDir = normalizePath(base) + + let prevProjectDir: string | undefined + while (projectDir !== prevProjectDir) { + yield projectDir + + prevProjectDir = projectDir + projectDir = path.dirname(projectDir) + } + + return null +} + +function findBaseDir(project: tsconfck.TSConfckParseResult): string { + let baseUrl = project.tsconfig.compilerOptions?.baseUrl + if (baseUrl) return baseUrl + + for (let p of project.extended ?? []) { + let opts = p.tsconfig.compilerOptions ?? {} + if (opts?.paths) { + return path.dirname(p.tsconfigFile) + } + } + + return path.dirname(project.tsconfigFile) +} diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts index aabdc3cf..e5d6522f 100644 --- a/packages/tailwindcss-language-server/src/tw.ts +++ b/packages/tailwindcss-language-server/src/tw.ts @@ -33,13 +33,13 @@ import { } from 'vscode-languageserver/node' import { URI } from 'vscode-uri' import normalizePath from 'normalize-path' -import * as path from 'path' +import * as path from 'node:path' import type * as chokidar from 'chokidar' import picomatch from 'picomatch' import { resolveFrom } from './util/resolveFrom' import * as parcel from './watcher/index.js' import { equal } from '@tailwindcss/language-service/src/util/array' -import { CONFIG_GLOB, CSS_GLOB, PACKAGE_LOCK_GLOB } from './lib/constants' +import { CONFIG_GLOB, CSS_GLOB, PACKAGE_LOCK_GLOB, TSCONFIG_GLOB } from './lib/constants' import { clearRequireCache, isObject, changeAffectsFile, normalizeDriveLetter } from './utils' import { DocumentService } from './documents' import { createProjectService, type ProjectService } from './projects' @@ -47,6 +47,8 @@ import { type SettingsCache, createSettingsCache } from './config' 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' const TRIGGER_CHARACTERS = [ // class attributes @@ -243,7 +245,13 @@ export class TW { return } - let locator = new ProjectLocator(base, globalSettings) + let resolver = await createResolver({ + root: base, + pnp: true, + tsconfig: true, + }) + + let locator = new ProjectLocator(base, globalSettings, resolver) if (configs.length > 0) { console.log('Loading Tailwind CSS projects from the workspace settings.') @@ -289,6 +297,7 @@ export class TW { let isPackageMatcher = picomatch(`**/${PACKAGE_LOCK_GLOB}`, { dot: true }) let isCssMatcher = picomatch(`**/${CSS_GLOB}`, { dot: true }) let isConfigMatcher = picomatch(`**/${CONFIG_GLOB}`, { dot: true }) + let isTSConfigMatcher = picomatch(`**/${TSCONFIG_GLOB}`, { dot: true }) changeLoop: for (let change of changes) { let normalizedFilename = normalizePath(change.file) @@ -328,6 +337,25 @@ export class TW { } } + let isTsconfig = isTSConfigMatcher(normalizedFilename) + if (isTsconfig) { + // TODO: Use a refresh() instead of a full server restart + // let refreshPromise = retry({ + // tries: 4, + // delay: 250, + // callback: () => resolver.refresh(), + // }) + + // try { + // await refreshPromise + // } catch (err) { + // console.error('Unable to reload resolver', err) + // } + + needsRestart = true + break changeLoop + } + for (let [, project] of this.projects) { if (!project.state.v4) continue @@ -417,6 +445,7 @@ export class TW { { globPattern: `**/${CONFIG_GLOB}` }, { globPattern: `**/${PACKAGE_LOCK_GLOB}` }, { globPattern: `**/${CSS_GLOB}` }, + { globPattern: `**/${TSCONFIG_GLOB}` }, ], }, ) @@ -465,7 +494,7 @@ export class TW { } else { let watch: typeof chokidar.watch = require('chokidar').watch let chokidarWatcher = watch( - [`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`], + [`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`, `**/${TSCONFIG_GLOB}`], { cwd: base, ignorePermissionErrors: true, @@ -524,6 +553,7 @@ export class TW { this.watchPatterns, configTailwindVersionMap.get(projectConfig.configPath), userLanguages, + resolver, ), ), ) @@ -654,6 +684,7 @@ export class TW { watchPatterns: (patterns: string[]) => void, tailwindVersion: string, userLanguages: Record, + resolver: Resolver, ): Promise { let key = String(this.projectCounter++) const project = await createProjectService( @@ -678,6 +709,7 @@ export class TW { tailwindVersion, this.settingsCache.get, userLanguages, + resolver, ) this.projects.set(key, project) diff --git a/packages/tailwindcss-language-server/src/util/default-map.ts b/packages/tailwindcss-language-server/src/util/default-map.ts new file mode 100644 index 00000000..a045b828 --- /dev/null +++ b/packages/tailwindcss-language-server/src/util/default-map.ts @@ -0,0 +1,20 @@ +/** + * A Map that can generate default values for keys that don't exist. + * Generated default values are added to the map to avoid recomputation. + */ +export class DefaultMap 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-server/src/util/getModuleDependencies.ts b/packages/tailwindcss-language-server/src/util/getModuleDependencies.ts index aee0119a..84f8872b 100644 --- a/packages/tailwindcss-language-server/src/util/getModuleDependencies.ts +++ b/packages/tailwindcss-language-server/src/util/getModuleDependencies.ts @@ -1,6 +1,6 @@ // https://github.com/tailwindlabs/tailwindcss/blob/bac5ecf0040aa9a788d1b22d706506146ee831ff/src/lib/getModuleDependencies.js -import fs from 'fs' -import path from 'path' +import * as fs from 'node:fs' +import * as path from 'node:path' import { normalizeDriveLetter, normalizePath } from '../utils' let jsExtensions = ['.js', '.cjs', '.mjs'] diff --git a/packages/tailwindcss-language-server/src/util/isExcluded.ts b/packages/tailwindcss-language-server/src/util/isExcluded.ts index bbb6ca58..beb4115a 100644 --- a/packages/tailwindcss-language-server/src/util/isExcluded.ts +++ b/packages/tailwindcss-language-server/src/util/isExcluded.ts @@ -1,5 +1,5 @@ import picomatch from 'picomatch' -import * as path from 'path' +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' diff --git a/packages/tailwindcss-language-server/src/util/resolve.ts b/packages/tailwindcss-language-server/src/util/resolve.ts deleted file mode 100644 index 8d6e9504..00000000 --- a/packages/tailwindcss-language-server/src/util/resolve.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as fs from 'fs' -import { - CachedInputFileSystem, - ResolverFactory, - Resolver, - ResolveOptions, -} from 'enhanced-resolve' - -export function createResolver(options: Partial = {}): Resolver { - return ResolverFactory.createResolver({ - fileSystem: new CachedInputFileSystem(fs, 4000), - useSyncFileSystemCalls: true, - conditionNames: ['node', 'require'], - ...options, - }) -} diff --git a/packages/tailwindcss-language-server/src/util/resolveFrom.ts b/packages/tailwindcss-language-server/src/util/resolveFrom.ts index f81cccf7..2b62bbeb 100644 --- a/packages/tailwindcss-language-server/src/util/resolveFrom.ts +++ b/packages/tailwindcss-language-server/src/util/resolveFrom.ts @@ -1,21 +1,41 @@ -import { equal } from '@tailwindcss/language-service/src/util/array' +import * as fs from 'node:fs' import * as path from 'node:path' -import { createResolver } from './resolve' +import { equal } from '@tailwindcss/language-service/src/util/array' +import { CachedInputFileSystem, ResolverFactory } from 'enhanced-resolve' let pnpApi: any let extensions = Object.keys(require.extensions) function recreateResolver() { - return createResolver({ extensions, pnpApi }) + let fileSystem = new CachedInputFileSystem(fs, 4000) + + return ResolverFactory.createResolver({ + fileSystem, + useSyncFileSystemCalls: true, + conditionNames: ['node', 'require'], + extensions, + pnpApi, + }) } let resolver = recreateResolver() +/** + * @deprecated Use `createResolver()` instead. + */ export function setPnpApi(newPnpApi: any): void { pnpApi = newPnpApi resolver = recreateResolver() } +/** + * Resolve a module id from a given path synchronously. + * + * This is a legacy API and should be avoided in favor of the async version as + * it does not support TypeScript path mapping. + * + * @deprecated Use `createResolver().resolveJsId(…)` instead. + */ export function resolveFrom(from?: string, id?: string): string { // Network share path on Windows if (id.startsWith('\\\\')) return id diff --git a/packages/tailwindcss-language-server/src/util/retry.ts b/packages/tailwindcss-language-server/src/util/retry.ts new file mode 100644 index 00000000..b14b927a --- /dev/null +++ b/packages/tailwindcss-language-server/src/util/retry.ts @@ -0,0 +1,19 @@ +export interface RetryOptions { + tries: number + delay: number + callback: () => Promise +} + +export async function retry({ tries, delay, callback }) { + retry: try { + return await callback() + } catch (err) { + if (tries-- === 0) throw err + + // Wait a bit before trying again _ this exists for projects like + // Nuxt that create a several tsconfig files at once + await new Promise((resolve) => setTimeout(resolve, delay)) + + break retry + } +} 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 8e8ad5be..8211bac6 100644 --- a/packages/tailwindcss-language-server/src/util/v4/design-system.ts +++ b/packages/tailwindcss-language-server/src/util/v4/design-system.ts @@ -4,9 +4,9 @@ import postcss from 'postcss' import { createJiti } from 'jiti' import * as fs from 'node:fs/promises' import * as path from 'node:path' -import { resolveCssFrom, resolveCssImports } from '../../css' -import { resolveFrom } from '../resolveFrom' -import { pathToFileURL } from 'tailwindcss-language-server/src/utils' +import { resolveCssImports } from '../../css' +import { Resolver } from '../../resolver' +import { pathToFileURL } from '../../utils' import type { Jiti } from 'jiti/lib/types' const HAS_V4_IMPORT = /@import\s*(?:'tailwindcss'|"tailwindcss")/ @@ -24,20 +24,6 @@ export async function isMaybeV4(css: string): Promise { return HAS_V4_THEME.test(css) || HAS_V4_IMPORT.test(css) } -let jiti: Jiti | undefined - -async function importFile(id: string) { - try { - // Load ESM/CJS files through Node/Bun/whatever runtime is being used - return await import(id) - } catch { - jiti ??= createJiti(__filename, { moduleCache: false, fsCache: false }) - - // Transpile using Jiti if we can't load the file directly - return await jiti.import(id) - } -} - /** * Create a loader function that can load plugins and config files relative to * the CSS file that uses them. However, we don't want missing files to prevent @@ -46,26 +32,30 @@ async function importFile(id: string) { function createLoader({ dependencies, legacy, + jiti, filepath, + resolver, onError, }: { dependencies: Set legacy: boolean + jiti: Jiti filepath: string + resolver: Resolver onError: (id: string, error: unknown, resourceType: string) => T }) { let cacheKey = `${+Date.now()}` async function loadFile(id: string, base: string, resourceType: string) { try { - let resolved = resolveFrom(base, id) + let resolved = await resolver.resolveJsId(id, base) dependencies.add(resolved) let url = pathToFileURL(resolved) url.searchParams.append('t', cacheKey) - return await importFile(url.href).then((m) => m.default ?? m) + return await jiti.import(url.href, { default: true }) } catch (err) { return onError(id, err, resourceType) } @@ -85,6 +75,7 @@ function createLoader({ } export async function loadDesignSystem( + resolver: Resolver, tailwindcss: any, filepath: string, css: string, @@ -111,10 +102,16 @@ export async function loadDesignSystem( // Step 2: Use postcss to resolve `@import` rules in the CSS file if (!supportsImports) { - let resolved = await resolveCssImports().process(css, { from: filepath }) + let resolved = await resolveCssImports({ resolver }).process(css, { from: filepath }) css = resolved.css } + // Create a Jiti instance that can be used to load plugins and config files + let jiti = createJiti(__filename, { + moduleCache: false, + fsCache: false, + }) + // Step 3: Take the resolved CSS and pass it to v4's `loadDesignSystem` let design: DesignSystem = await tailwindcss.__unstable__loadDesignSystem(css, { base: path.dirname(filepath), @@ -123,7 +120,9 @@ export async function loadDesignSystem( loadModule: createLoader({ dependencies, legacy: false, + jiti, filepath, + resolver, onError: (id, err, resourceType) => { console.error(`Unable to load ${resourceType}: ${id}`, err) @@ -136,13 +135,24 @@ export async function loadDesignSystem( }), loadStylesheet: async (id: string, base: string) => { - let resolved = resolveCssFrom(base, id) + // Skip over missing stylesheets (and log an error) so we can do our best + // to compile the design system even when the build might be incomplete. + // TODO: Figure out if we can recover from parsing errors in stylesheets + // we'd want to surface diagnostics when we discover imports that cause + // parsing errors or other logic errors. - dependencies.add(resolved) + try { + let resolved = await resolver.resolveCssId(id, base) - return { - base: path.dirname(resolved), - content: await fs.readFile(resolved, 'utf-8'), + dependencies.add(resolved) + + return { + base: path.dirname(resolved), + content: await fs.readFile(resolved, 'utf-8'), + } + } catch (err) { + console.error(`Unable to load stylesheet: ${id}`, err) + return { base, content: '' } } }, @@ -150,7 +160,9 @@ export async function loadDesignSystem( loadPlugin: createLoader({ dependencies, legacy: true, + jiti, filepath, + resolver, onError(id, err) { console.error(`Unable to load plugin: ${id}`, err) @@ -161,7 +173,9 @@ export async function loadDesignSystem( loadConfig: createLoader({ dependencies, legacy: true, + jiti, filepath, + resolver, onError(id, err) { console.error(`Unable to load config: ${id}`, err) diff --git a/packages/tailwindcss-language-server/src/utils.ts b/packages/tailwindcss-language-server/src/utils.ts index 1660ce80..8e1f6303 100644 --- a/packages/tailwindcss-language-server/src/utils.ts +++ b/packages/tailwindcss-language-server/src/utils.ts @@ -1,5 +1,5 @@ import Module from 'node:module' -import path from 'node:path' +import * as path from 'node:path' import { URI } from 'vscode-uri' import normalizePathBase from 'normalize-path' import { pathToFileURL as pathToFileURLBase } from 'node:url' diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/a.css b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/a.css new file mode 100644 index 00000000..b60fe2c7 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/a.css @@ -0,0 +1,5 @@ +@layer base { + :root { + font-family: sans-serif; + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/b.css b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/b.css new file mode 100644 index 00000000..62948aff --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/b.css @@ -0,0 +1,5 @@ +@layer base { + :root { + --foo: red; + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json new file mode 100644 index 00000000..47224857 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "invalid-import-order", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "tailwindcss": "^4.0.0-beta.6" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.6.tgz", + "integrity": "sha512-eCCuMk3H65w4J/QWkjxfeWoBSKbCD3E6Uj2LA6Xkkl4eMa1MXuwVpIU1RXcLIp+BVsXGEZMP7i7uJig7KxfAXQ==" + } + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json new file mode 100644 index 00000000..960bfb88 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tailwindcss": "^4.0.0-beta.6" + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/tailwind.css b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/tailwind.css new file mode 100644 index 00000000..e3d2eb5e --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/tailwind.css @@ -0,0 +1,14 @@ +@import 'tailwindcss'; + +/* + * This is invalid in this position because some `@import`s are not at the top of the file. + * We don't want project discovery to fail so we hoist them up and then warn in the console. + */ +@variant dark (&:where(.dark, .dark *)); + +@import './a.css'; +@import './b.css'; + +@theme { + --color-primary: #c0ffee; +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/app.css b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/app.css new file mode 100644 index 00000000..cbbdcb4a --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/app.css @@ -0,0 +1,4 @@ +@import 'tailwindcss'; + +@import './i-do-not-exist.css'; +@import './i-exist.css'; diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/i-exist.css b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/i-exist.css new file mode 100644 index 00000000..a15c877a --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/i-exist.css @@ -0,0 +1,3 @@ +.foo { + color: red; +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json new file mode 100644 index 00000000..0ea4e1a1 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "missing-files", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "tailwindcss": "^4.0.0-beta.6" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.6.tgz", + "integrity": "sha512-eCCuMk3H65w4J/QWkjxfeWoBSKbCD3E6Uj2LA6Xkkl4eMa1MXuwVpIU1RXcLIp+BVsXGEZMP7i7uJig7KxfAXQ==" + } + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json new file mode 100644 index 00000000..960bfb88 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tailwindcss": "^4.0.0-beta.6" + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/app.css b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/app.css new file mode 100644 index 00000000..a6aa8a2b --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/app.css @@ -0,0 +1,5 @@ +@import 'tailwindcss'; + +@import '#a/file.css'; +@config '#a/my-config.ts'; +@plugin '#a/my-plugin.ts'; diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json new file mode 100644 index 00000000..5de5cb3e --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "path-mappings", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "tailwindcss": "^4.0.0-beta.6" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.6.tgz", + "integrity": "sha512-eCCuMk3H65w4J/QWkjxfeWoBSKbCD3E6Uj2LA6Xkkl4eMa1MXuwVpIU1RXcLIp+BVsXGEZMP7i7uJig7KxfAXQ==" + } + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json new file mode 100644 index 00000000..960bfb88 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tailwindcss": "^4.0.0-beta.6" + } +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/file.css b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/file.css new file mode 100644 index 00000000..49426571 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/file.css @@ -0,0 +1,3 @@ +@theme { + --color-map-a-css: black; +} diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/my-config.ts b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/my-config.ts new file mode 100644 index 00000000..1f37e378 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/my-config.ts @@ -0,0 +1,11 @@ +import type { Config } from 'tailwindcss' + +export default { + theme: { + extend: { + colors: { + 'map-a-config': 'black', + }, + }, + }, +} satisfies Config diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/my-plugin.ts b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/my-plugin.ts new file mode 100644 index 00000000..933c9d32 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/src/a/my-plugin.ts @@ -0,0 +1,17 @@ +import type { PluginAPI } from 'tailwindcss' +import plugin from 'tailwindcss/plugin' + +export default plugin( + (api: PluginAPI) => { + // + }, + { + theme: { + extend: { + colors: { + 'map-a-plugin': 'black', + }, + }, + }, + }, +) diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/tsconfig.json b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/tsconfig.json new file mode 100644 index 00000000..6e9c38fd --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "#a/*": ["./src/a/*"] + } + } +} diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js index 8967ab40..4ab6aeb8 100644 --- a/packages/tailwindcss-language-server/tests/hover/hover.test.js +++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js @@ -357,3 +357,58 @@ withFixture('v4/css-loading-js', (c) => { }, }) }) + +withFixture('v4/path-mappings', (c) => { + async function testHover(name, { text, lang, position, expected, expectedRange, settings }) { + test.concurrent(name, async ({ expect }) => { + let textDocument = await c.openDocument({ text, lang, settings }) + let res = await c.sendRequest('textDocument/hover', { + textDocument, + position, + }) + + expect(res).toEqual( + expected + ? { + contents: { + language: 'css', + value: expected, + }, + range: expectedRange, + } + : expected, + ) + }) + } + + testHover('Mapping: CSS Imports', { + text: '
', + position: { line: 0, character: 13 }, + expected: + '.bg-map-a-css {\n background-color: var(--color-map-a-css) /* black = #000000 */;\n}', + expectedRange: { + start: { line: 0, character: 12 }, + end: { line: 0, character: 24 }, + }, + }) + + testHover('Mapping: Configs', { + text: '
', + position: { line: 0, character: 13 }, + expected: '.bg-map-a-config {\n background-color: black;\n}', + expectedRange: { + start: { line: 0, character: 12 }, + end: { line: 0, character: 27 }, + }, + }) + + testHover('Mapping: Plugins', { + text: '
', + position: { line: 0, character: 13 }, + expected: '.bg-map-a-plugin {\n background-color: black;\n}', + expectedRange: { + start: { line: 0, character: 12 }, + end: { line: 0, character: 27 }, + }, + }) +}) diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json index d92a10c7..d9893dc0 100644 --- a/packages/tailwindcss-language-service/package.json +++ b/packages/tailwindcss-language-service/package.json @@ -44,7 +44,7 @@ "@types/line-column": "^1.0.2", "@types/node": "^18.19.33", "@types/stringify-object": "^4.0.5", - "esbuild": "^0.20.2", + "esbuild": "^0.24.0", "esbuild-node-externals": "^1.9.0", "minimist": "^1.2.8", "tslib": "2.2.0", diff --git a/packages/tailwindcss-language-service/src/documentLinksProvider.ts b/packages/tailwindcss-language-service/src/documentLinksProvider.ts index b18a4711..a0fcd1a2 100644 --- a/packages/tailwindcss-language-service/src/documentLinksProvider.ts +++ b/packages/tailwindcss-language-service/src/documentLinksProvider.ts @@ -11,8 +11,8 @@ const HAS_DRIVE_LETTER = /^[A-Z]:/ export function getDocumentLinks( state: State, document: TextDocument, - resolveTarget: (linkPath: string) => string, -): DocumentLink[] { + resolveTarget: (linkPath: string) => Promise, +): Promise { let patterns = [/@config\s*(?'[^']+'|"[^"]+")/g] if (state.v4) { @@ -27,12 +27,12 @@ export function getDocumentLinks( return getDirectiveLinks(state, document, patterns, resolveTarget) } -function getDirectiveLinks( +async function getDirectiveLinks( state: State, document: TextDocument, patterns: RegExp[], - resolveTarget: (linkPath: string) => string, -): DocumentLink[] { + resolveTarget: (linkPath: string) => Promise, +): Promise { if (!semver.gte(state.version, '3.2.0')) { return [] } @@ -67,7 +67,7 @@ function getDirectiveLinks( } links.push({ - target: resolveTarget(path), + target: await resolveTarget(path), range: absoluteRange(range, block.range), }) } diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 06dfcf83..d59e1e84 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -2,7 +2,9 @@ ## Prerelease -- Nothing yet! +- Don't break when importing missing CSS files ([#1106](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1106)) +- Resolve CSS imports as relative first ([#1106](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1106)) +- Add TypeScript config path support in v4 CSS files ([#1106](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1106)) ## 0.12.18 diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 53e24ee2..57bbf3da 100644 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -358,7 +358,7 @@ "braces": "3.0.3", "color-name": "1.1.4", "concurrently": "7.0.0", - "esbuild": "^0.20.2", + "esbuild": "^0.24.0", "minimist": "^1.2.8", "move-file-cli": "3.0.0", "normalize-path": "3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0ca5d41..596c70aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^4.0.1 version: 4.0.1 esbuild: - specifier: ^0.20.2 - version: 0.20.2 + specifier: ^0.24.0 + version: 0.24.0 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -123,8 +123,8 @@ importers: specifier: ^5.16.1 version: 5.17.1 esbuild: - specifier: ^0.20.2 - version: 0.20.2 + specifier: ^0.24.0 + version: 0.24.0 fast-glob: specifier: 3.2.4 version: 3.2.4 @@ -176,6 +176,12 @@ importers: tailwindcss: specifier: 3.4.4 version: 3.4.4 + tsconfck: + specifier: ^3.1.4 + version: 3.1.4(typescript@5.3.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 typescript: specifier: 5.3.3 version: 5.3.3 @@ -298,11 +304,11 @@ importers: specifier: ^4.0.5 version: 4.0.5 esbuild: - specifier: ^0.20.2 - version: 0.20.2 + specifier: ^0.24.0 + version: 0.24.0 esbuild-node-externals: specifier: ^1.9.0 - version: 1.14.0(esbuild@0.20.2) + version: 1.14.0(esbuild@0.24.0) minimist: specifier: ^1.2.8 version: 1.2.8 @@ -346,8 +352,8 @@ importers: specifier: 7.0.0 version: 7.0.0 esbuild: - specifier: ^0.20.2 - version: 0.20.2 + specifier: ^0.24.0 + version: 0.24.0 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -409,23 +415,17 @@ packages: '@csstools/css-parser-algorithms': ^2.1.1 '@csstools/css-tokenizer': ^2.1.1 - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} @@ -433,10 +433,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + engines: {node: '>=18'} + cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': @@ -445,10 +445,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + engines: {node: '>=18'} + cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': @@ -457,11 +457,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} @@ -469,10 +469,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + engines: {node: '>=18'} + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': @@ -481,11 +481,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} @@ -493,10 +493,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + engines: {node: '>=18'} + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': @@ -505,11 +505,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} @@ -517,10 +517,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + engines: {node: '>=18'} + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': @@ -529,10 +529,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + engines: {node: '>=18'} + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': @@ -541,10 +541,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} - engines: {node: '>=12'} - cpu: [loong64] + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + engines: {node: '>=18'} + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': @@ -553,10 +553,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} - engines: {node: '>=12'} - cpu: [mips64el] + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + engines: {node: '>=18'} + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': @@ -565,10 +565,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} - engines: {node: '>=12'} - cpu: [ppc64] + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + engines: {node: '>=18'} + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': @@ -577,10 +577,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} - engines: {node: '>=12'} - cpu: [riscv64] + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + engines: {node: '>=18'} + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': @@ -589,10 +589,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} - engines: {node: '>=12'} - cpu: [s390x] + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + engines: {node: '>=18'} + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': @@ -601,10 +601,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + engines: {node: '>=18'} + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': @@ -613,11 +613,11 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + engines: {node: '>=18'} cpu: [x64] - os: [netbsd] + os: [linux] '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} @@ -625,10 +625,16 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} - engines: {node: '>=12'} + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + engines: {node: '>=18'} cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + engines: {node: '>=18'} + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': @@ -637,11 +643,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} + engines: {node: '>=18'} cpu: [x64] - os: [sunos] + os: [openbsd] '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} @@ -649,11 +655,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} @@ -661,10 +667,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + engines: {node: '>=18'} + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': @@ -673,10 +679,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + engines: {node: '>=18'} + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': @@ -685,6 +691,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1382,16 +1394,16 @@ packages: peerDependencies: esbuild: 0.12 - 0.23 - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -1667,6 +1679,11 @@ packages: resolution: {integrity: sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} @@ -2378,6 +2395,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -2475,8 +2496,8 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsconfck@3.1.1: - resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} + tsconfck@3.1.4: + resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} hasBin: true peerDependencies: @@ -2485,6 +2506,10 @@ packages: typescript: optional: true + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -2782,144 +2807,147 @@ snapshots: '@csstools/css-parser-algorithms': 2.1.1(@csstools/css-tokenizer@2.1.1) '@csstools/css-tokenizer': 2.1.1 - '@esbuild/aix-ppc64@0.20.2': - optional: true - '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/android-arm64@0.20.2': + '@esbuild/aix-ppc64@0.24.0': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm@0.20.2': + '@esbuild/android-arm64@0.24.0': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-x64@0.20.2': + '@esbuild/android-arm@0.24.0': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.20.2': + '@esbuild/android-x64@0.24.0': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-x64@0.20.2': + '@esbuild/darwin-arm64@0.24.0': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.20.2': + '@esbuild/darwin-x64@0.24.0': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.20.2': + '@esbuild/freebsd-arm64@0.24.0': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/linux-arm64@0.20.2': + '@esbuild/freebsd-x64@0.24.0': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm@0.20.2': + '@esbuild/linux-arm64@0.24.0': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-ia32@0.20.2': + '@esbuild/linux-arm@0.24.0': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-loong64@0.20.2': + '@esbuild/linux-ia32@0.24.0': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-mips64el@0.20.2': + '@esbuild/linux-loong64@0.24.0': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-ppc64@0.20.2': + '@esbuild/linux-mips64el@0.24.0': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.20.2': + '@esbuild/linux-ppc64@0.24.0': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-s390x@0.20.2': + '@esbuild/linux-riscv64@0.24.0': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-x64@0.20.2': + '@esbuild/linux-s390x@0.24.0': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.20.2': + '@esbuild/linux-x64@0.24.0': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.20.2': + '@esbuild/netbsd-x64@0.24.0': + optional: true + + '@esbuild/openbsd-arm64@0.24.0': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.20.2': + '@esbuild/openbsd-x64@0.24.0': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/win32-arm64@0.20.2': + '@esbuild/sunos-x64@0.24.0': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-ia32@0.20.2': + '@esbuild/win32-arm64@0.24.0': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-x64@0.20.2': + '@esbuild/win32-ia32@0.24.0': optional: true '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.24.0': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3598,38 +3626,12 @@ snapshots: es-errors@1.3.0: {} - esbuild-node-externals@1.14.0(esbuild@0.20.2): + esbuild-node-externals@1.14.0(esbuild@0.24.0): dependencies: - esbuild: 0.20.2 + esbuild: 0.24.0 find-up: 5.0.0 tslib: 2.6.3 - esbuild@0.20.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -3656,6 +3658,33 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.24.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 + escalade@3.1.2: {} escape-string-regexp@1.0.5: {} @@ -3913,6 +3942,8 @@ snapshots: json-parse-even-better-errors@3.0.0: {} + json5@2.2.3: {} + jsonc-parser@3.3.1: {} keytar@7.9.0: @@ -4660,6 +4691,8 @@ snapshots: dependencies: ansi-regex: 6.0.1 + strip-bom@3.0.0: {} + strip-final-newline@3.0.0: {} strip-indent@4.0.0: @@ -4773,10 +4806,16 @@ snapshots: ts-interface-checker@0.1.13: {} - tsconfck@3.1.1(typescript@5.3.3): + tsconfck@3.1.4(typescript@5.3.3): optionalDependencies: typescript: 5.3.3 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@1.14.1: {} tslib@2.2.0: {} @@ -4854,7 +4893,7 @@ snapshots: dependencies: debug: 4.3.6 globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.3.3) + tsconfck: 3.1.4(typescript@5.3.3) optionalDependencies: vite: 5.3.5(@types/node@18.19.43) transitivePeerDependencies: