From 468cb5e99e48729ce41206da06e842c6d7a5ebae Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 14 Oct 2024 17:45:36 +0200 Subject: [PATCH] Detect and migrate static plugin usages (#14648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR builds on top of the new [JS config to CSS migration](https://github.com/tailwindlabs/tailwindcss/pull/14651) and extends it to support migrating _static_ plugins. What are _static_ plugins you might ask? Static plugins are plugins where we can statically determine that these are coming from a different file (so there is nothing inside the JS config that creates them). An example for this is this config file: ```js import typographyPlugin from '@tailwindcss/typography' import { type Config } from 'tailwindcss' export default { content: ['./src/**/*.{js,jsx,ts,tsx}'], darkMode: 'selector', plugins: [typographyPlugin], } satisfies Config ``` Here, the `plugins` array only has one element and it is a static import from the `@tailwindcss/typography` module. In this PR we attempt to parse the config file via Tree-sitter to extract the following information from this file: - What are the contents of the `plugins` array - What are statically imported resources from the file We then check if _all_ entries in the `plugins` array are either static resources or _strings_ (something I saw working in some tests but I’m not sure it still does). We migrate the JS config file to CSS if all plugins are static and we can migrate them to CSS `@plugin` calls. ## Todo This will need to be rebased after the updated tests in #14648 --- CHANGELOG.md | 2 +- integrations/upgrade/js-config.test.ts | 115 +++++----- packages/@tailwindcss-upgrade/package.json | 4 +- .../src/codemods/migrate-config.ts | 12 +- .../src/migrate-js-config.ts | 17 +- .../src/utils/extract-static-plugins.test.ts | 201 ++++++++++++++++++ .../src/utils/extract-static-plugins.ts | 199 +++++++++++++++++ packages/@tailwindcss-upgrade/tsconfig.json | 3 + pnpm-lock.yaml | 41 ++++ 9 files changed, 529 insertions(+), 65 deletions(-) create mode 100644 packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.test.ts create mode 100644 packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c126b069..f9c5023d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612)) - _Upgrade (experimental)_: Automatically discover JavaScript config files ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) - _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643)) -- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650)) +- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650), [#14648](https://github.com/tailwindlabs/tailwindcss/pull/14648)) - _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603)) - _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635)) - _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644)) diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index 347e08e93..e19681b4c 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -102,6 +102,61 @@ test( }, ) +test( + 'upgrades JS config files with plugins', + { + fs: { + 'package.json': json` + { + "dependencies": { + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.ts': ts` + import { type Config } from 'tailwindcss' + import typography from '@tailwindcss/typography' + import customPlugin from './custom-plugin' + + export default { + plugins: [typography, customPlugin], + } satisfies Config + `, + 'custom-plugin.js': ts` + export default function ({ addVariant }) { + addVariant('inverted', '@media (inverted-colors: inverted)') + addVariant('hocus', ['&:focus', '&:hover']) + } + `, + 'src/input.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ exec, fs }) => { + await exec('npx @tailwindcss/upgrade') + + expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` + " + --- src/input.css --- + @import 'tailwindcss'; + + @plugin '@tailwindcss/typography'; + @plugin '../custom-plugin'; + " + `) + + expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(` + " + + " + `) + }, +) + test( 'does not upgrade JS config files with functions in the theme config', { @@ -231,66 +286,6 @@ test( }, ) -test( - 'does not upgrade JS config files with plugins', - { - fs: { - 'package.json': json` - { - "dependencies": { - "@tailwindcss/typography": "^0.5.15", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import typography from '@tailwindcss/typography' - import customPlugin from './custom-plugin' - - export default { - plugins: [typography, customPlugin], - } satisfies Config - `, - 'custom-plugin.js': ts` - export default function ({ addVariant }) { - addVariant('inverted', '@media (inverted-colors: inverted)') - addVariant('hocus', ['&:focus', '&:hover']) - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs }) => { - await exec('npx @tailwindcss/upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - @config '../tailwind.config.ts'; - " - `) - - expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(` - " - --- tailwind.config.ts --- - import { type Config } from 'tailwindcss' - import typography from '@tailwindcss/typography' - import customPlugin from './custom-plugin' - - export default { - plugins: [typography, customPlugin], - } satisfies Config - " - `) - }, -) - test( `does not upgrade JS config files with inline plugins`, { diff --git a/packages/@tailwindcss-upgrade/package.json b/packages/@tailwindcss-upgrade/package.json index 7e0425be4..048a470a7 100644 --- a/packages/@tailwindcss-upgrade/package.json +++ b/packages/@tailwindcss-upgrade/package.json @@ -39,7 +39,9 @@ "postcss-selector-parser": "^6.1.2", "prettier": "^3.3.3", "string-byte-slice": "^3.0.0", - "tailwindcss": "workspace:^" + "tailwindcss": "workspace:^", + "tree-sitter": "^0.21.1", + "tree-sitter-typescript": "^0.23.0" }, "devDependencies": { "@types/node": "catalog:", diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts index 1aadec966..178122f1d 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts @@ -55,11 +55,21 @@ export function migrateConfig( let absolute = path.resolve(source.base, source.pattern) css += `@source '${relativeToStylesheet(sheet, absolute)}';\n` } - if (jsConfigMigration.sources.length > 0) { css = css + '\n' } + for (let plugin of jsConfigMigration.plugins) { + let relative = + plugin.path[0] === '.' + ? relativeToStylesheet(sheet, path.resolve(plugin.base, plugin.path)) + : plugin.path + css += `@plugin '${relative}';\n` + } + if (jsConfigMigration.plugins.length > 0) { + css = css + '\n' + } + cssConfig.append(postcss.parse(css + jsConfigMigration.css)) } diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts index 18e6712c8..bc80bbdf8 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -12,6 +12,7 @@ import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge' import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config' import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types' import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode' +import { findStaticPlugins } from './utils/extract-static-plugins' import { info } from './utils/renderer' const __filename = fileURLToPath(import.meta.url) @@ -21,6 +22,7 @@ export type JSConfigMigration = // Could not convert the config file, need to inject it as-is in a @config directive null | { sources: { base: string; pattern: string }[] + plugins: { base: string; path: string }[] css: string } @@ -41,6 +43,7 @@ export async function migrateJsConfig( } let sources: { base: string; pattern: string }[] = [] + let plugins: { base: string; path: string }[] = [] let cssConfigs: string[] = [] if ('darkMode' in unresolvedConfig) { @@ -56,8 +59,16 @@ export async function migrateJsConfig( if (themeConfig) cssConfigs.push(themeConfig) } + let simplePlugins = findStaticPlugins(source) + if (simplePlugins !== null) { + for (let plugin of simplePlugins) { + plugins.push({ base, path: plugin }) + } + } + return { sources, + plugins, css: cssConfigs.join('\n'), } } @@ -168,7 +179,9 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean { return ['string', 'number', 'boolean', 'undefined'].includes(typeof value) } - if (!isSimpleValue(unresolvedConfig)) { + // Plugins are more complex, so we have a special heuristics for them. + let { plugins, ...remainder } = unresolvedConfig + if (!isSimpleValue(remainder)) { return false } @@ -186,7 +199,7 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean { return false } - if (unresolvedConfig.plugins && unresolvedConfig.plugins.length > 0) { + if (findStaticPlugins(source) === null) { return false } diff --git a/packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.test.ts b/packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.test.ts new file mode 100644 index 000000000..8f8892cb3 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.test.ts @@ -0,0 +1,201 @@ +import dedent from 'dedent' +import { describe, expect, test } from 'vitest' +import { extractStaticImportMap, findStaticPlugins } from './extract-static-plugins' + +const js = dedent + +describe('findStaticPlugins', () => { + test('parses all export styles', () => { + expect( + findStaticPlugins(js` + import plugin1 from './plugin1' + import * as plugin2 from './plugin2' + + export default { + plugins: [plugin1, plugin2, 'plugin3', require('./plugin4')] + } + `), + ).toEqual(['./plugin1', './plugin2', 'plugin3', './plugin4']) + + expect( + findStaticPlugins(js` + import plugin1 from './plugin1' + import * as plugin2 from './plugin2' + + export default { + plugins: [plugin1, plugin2, 'plugin3', require('./plugin4')] + } as any + `), + ).toEqual(['./plugin1', './plugin2', 'plugin3', './plugin4']) + + expect( + findStaticPlugins(js` + import plugin1 from './plugin1' + import * as plugin2 from './plugin2' + + export default { + plugins: [plugin1, plugin2, 'plugin3', require('./plugin4')] + } satisfies any + `), + ).toEqual(['./plugin1', './plugin2', 'plugin3', './plugin4']) + + expect( + findStaticPlugins(js` + const plugin1 = require('./plugin1') + + module.exports = { + plugins: [plugin1, 'plugin2', require('./plugin3')] + } as any + `), + ).toEqual(['./plugin1', 'plugin2', './plugin3']) + + expect( + findStaticPlugins(js` + const plugin1 = require('./plugin1') + + module.exports = { + plugins: [plugin1, 'plugin2', require('./plugin3')] + } satisfies any + `), + ).toEqual(['./plugin1', 'plugin2', './plugin3']) + + expect( + findStaticPlugins(js` + const plugin1 = require('./plugin1') + + module.exports = { + plugins: [plugin1, 'plugin2', require('./plugin3')] + } + `), + ).toEqual(['./plugin1', 'plugin2', './plugin3']) + }) + + test('bails out on inline plugins', () => { + expect( + findStaticPlugins(js` + import plugin1 from './plugin1' + + export default { + plugins: [plugin1, () => {} ] + } + `), + ).toEqual(null) + + expect( + findStaticPlugins(js` + let plugin1 = () => {} + + export default { + plugins: [plugin1] + } + `), + ).toEqual(null) + }) + + test('bails out on non `require` calls', () => { + expect( + findStaticPlugins(js` + export default { + plugins: [frequire('./plugin1')] + } + `), + ).toEqual(null) + }) + + test('bails out on named imports for plugins', () => { + expect( + findStaticPlugins(js` + import {plugin1} from './plugin1' + + export default { + plugins: [plugin1] + } + `), + ).toEqual(null) + }) + + test('bails for plugins with options', () => { + expect( + findStaticPlugins(js` + import plugin1 from './plugin1' + + export default { + plugins: [plugin1({foo:'bar'})] + } + `), + ).toEqual(null) + + expect( + findStaticPlugins(js` + export default { + plugins: [require('@tailwindcss/typography')({foo:'bar'})] + } + `), + ).toEqual(null) + }) + + test('returns no plugins if none are exported', () => { + expect( + findStaticPlugins(js` + export default { + plugins: [] + } + `), + ).toEqual([]) + + expect( + findStaticPlugins(js` + export default {} + `), + ).toEqual([]) + }) +}) + +describe('extractStaticImportMap', () => { + test('extracts different kind of imports from an ESM file', () => { + let extracted = extractStaticImportMap(js` + import plugin1 from './plugin1' + import * as plugin2 from './plugin2' + import plugin6, { plugin3, plugin4, default as plugin5 } from './plugin3' + import plugin8, * as plugin7 from './plugin7' + `) + + expect(extracted).toEqual({ + plugin1: { module: './plugin1', export: null }, + plugin2: { module: './plugin2', export: '*' }, + plugin3: { module: './plugin3', export: 'plugin3' }, + plugin4: { module: './plugin3', export: 'plugin4' }, + plugin5: { module: './plugin3', export: 'default' }, + plugin6: { module: './plugin3', export: null }, + plugin7: { module: './plugin7', export: '*' }, + plugin8: { module: './plugin7', export: null }, + }) + }) + + test('extracts different kind of imports from an CJS file', () => { + let extracted = extractStaticImportMap(js` + const plugin1 = require('./plugin1') + let plugin2 = require('./plugin2') + var plugin3 = require('./plugin3') + + const {plugin4, foo: plugin5, ...plugin6} = require('./plugin4') + let {plugin7, foo: plugin8, ...plugin9} = require('./plugin5') + var {plugin10, foo: plugin11, ...plugin12} = require('./plugin6') + `) + + expect(extracted).toEqual({ + plugin1: { module: './plugin1', export: null }, + plugin2: { module: './plugin2', export: null }, + plugin3: { module: './plugin3', export: null }, + plugin4: { module: './plugin4', export: 'plugin4' }, + plugin5: { module: './plugin4', export: 'foo' }, + plugin6: { module: './plugin4', export: '*' }, + plugin7: { module: './plugin5', export: 'plugin7' }, + plugin8: { module: './plugin5', export: 'foo' }, + plugin9: { module: './plugin5', export: '*' }, + plugin10: { module: './plugin6', export: 'plugin10' }, + plugin11: { module: './plugin6', export: 'foo' }, + plugin12: { module: './plugin6', export: '*' }, + }) + }) +}) diff --git a/packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.ts b/packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.ts new file mode 100644 index 000000000..4034ac42e --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.ts @@ -0,0 +1,199 @@ +import Parser from 'tree-sitter' +import TS from 'tree-sitter-typescript' + +let parser = new Parser() +parser.setLanguage(TS.typescript) +const treesitter = String.raw + +const PLUGINS_QUERY = new Parser.Query( + TS.typescript, + treesitter` + ; export default {} + (export_statement + value: (satisfies_expression (object + (pair + key: (property_identifier) @_name (#eq? @_name "plugins") + value: (array) @imports + ) + ))? + value: (as_expression (object + (pair + key: (property_identifier) @_name (#eq? @_name "plugins") + value: (array) @imports + ) + ))? + value: (object + (pair + key: (property_identifier) @_name (#eq? @_name "plugins") + value: (array) @imports + ) + )? + ) + + ; module.exports = {} + (expression_statement + (assignment_expression + left: (member_expression) @left (#eq? @left "module.exports") + right: (satisfies_expression (object + (pair + key: (property_identifier) @_name (#eq? @_name "plugins") + value: (array) @imports + ) + ))? + right: (as_expression (object + (pair + key: (property_identifier) @_name (#eq? @_name "plugins") + value: (array) @imports + ) + ))? + right: (object + (pair + key: (property_identifier) @_name (#eq? @_name "plugins") + value: (array) @imports + ) + )? + ) + ) + `, +) +export function findStaticPlugins(source: string): string[] | null { + try { + let tree = parser.parse(source) + let root = tree.rootNode + + let imports = extractStaticImportMap(source) + let captures = PLUGINS_QUERY.matches(root) + + let plugins = [] + for (let match of captures) { + for (let capture of match.captures) { + if (capture.name !== 'imports') continue + + for (let pluginDefinition of capture.node.children) { + if ( + pluginDefinition.type === '[' || + pluginDefinition.type === ']' || + pluginDefinition.type === ',' + ) + continue + + switch (pluginDefinition.type) { + case 'identifier': + let source = imports[pluginDefinition.text] + if (!source || (source.export !== null && source.export !== '*')) { + return null + } + plugins.push(source.module) + break + case 'string': + plugins.push(pluginDefinition.children[1].text) + break + case 'call_expression': + // allow require('..') calls + if (pluginDefinition.children?.[0]?.text !== 'require') return null + let firstArgument = pluginDefinition.children?.[1]?.children?.[1]?.children?.[1]?.text + if (typeof firstArgument !== 'string') return null + plugins.push(firstArgument) + break + default: + return null + } + } + } + } + return plugins + } catch (error) { + console.error(error) + return null + } +} + +const IMPORT_QUERY = new Parser.Query( + TS.typescript, + treesitter` + ; ESM import + (import_statement + (import_clause + (identifier)? @default + (named_imports + (import_specifier + name: (identifier) @imported-name + alias: (identifier)? @imported-alias + ) + )? + (namespace_import (identifier) @imported-namespace)? + ) + (string + (string_fragment) @imported-from) + ) + + ; CJS require + (variable_declarator + name: (identifier)? @default + name: (object_pattern + (shorthand_property_identifier_pattern)? @imported-name + (pair_pattern + key: (property_identifier) @imported-name + value: (identifier) @imported-alias + )? + (rest_pattern + (identifier) @imported-namespace + )? + )? + value: (call_expression + function: (identifier) @_fn (#eq? @_fn "require") + arguments: (arguments + (string + (string_fragment) @imported-from + ) + ) + ) + ) + `, +) + +export function extractStaticImportMap(source: string) { + let tree = parser.parse(source) + let root = tree.rootNode + + let captures = IMPORT_QUERY.matches(root) + + let imports: Record = {} + for (let match of captures) { + let toImport: { name: string; export: null | string }[] = [] + let from = '' + for (let i = 0; i < match.captures.length; i++) { + let capture = match.captures[i] + + switch (capture.name) { + case 'default': + toImport.push({ name: capture.node.text, export: null }) + break + case 'imported-name': + toImport.push({ name: capture.node.text, export: capture.node.text }) + break + case 'imported-from': + from = capture.node.text + break + case 'imported-namespace': + toImport.push({ name: capture.node.text, export: '*' }) + break + case 'imported-alias': + if (toImport.length < 1) { + throw new Error('Unexpected alias: ' + JSON.stringify(captures, null, 2)) + } + let prevImport = toImport[toImport.length - 1] + let name = prevImport.name + prevImport.export = name + prevImport.name = capture.node.text + break + } + } + + for (let { name, export: exportSource } of toImport) { + imports[name] = { module: from, export: exportSource } + } + } + + return imports +} diff --git a/packages/@tailwindcss-upgrade/tsconfig.json b/packages/@tailwindcss-upgrade/tsconfig.json index 6ae022f65..b7ac105af 100644 --- a/packages/@tailwindcss-upgrade/tsconfig.json +++ b/packages/@tailwindcss-upgrade/tsconfig.json @@ -1,3 +1,6 @@ { "extends": "../tsconfig.base.json", + "compilerOptions": { + "allowSyntheticDefaultImports":true + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1651527e5..da24cf908 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -312,6 +312,12 @@ importers: tailwindcss: specifier: workspace:^ version: link:../tailwindcss + tree-sitter: + specifier: ^0.21.1 + version: 0.21.1 + tree-sitter-typescript: + specifier: ^0.23.0 + version: 0.23.0(tree-sitter@0.21.1) devDependencies: '@types/node': specifier: 'catalog:' @@ -2384,6 +2390,14 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-addon-api@8.1.0: + resolution: {integrity: sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==} + engines: {node: ^18 || ^20 || >= 21} + + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -2917,6 +2931,18 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + tree-sitter-typescript@0.23.0: + resolution: {integrity: sha512-hRy5O9d+9ON4HxIWWxkI4zonrw2v/WNN1JoiGW5HkXfC9K2R3p53ugMvs6Vs4T7ASCwggsoQ75LNdgpExC/zgQ==} + peerDependencies: + tree-sitter: ^0.21.0 + tree_sitter: '*' + peerDependenciesMeta: + tree_sitter: + optional: true + + tree-sitter@0.21.1: + resolution: {integrity: sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -5145,6 +5171,10 @@ snapshots: node-addon-api@7.1.1: {} + node-addon-api@8.1.0: {} + + node-gyp-build@4.8.2: {} + node-releases@2.0.18: {} normalize-path@3.0.0: {} @@ -5677,6 +5707,17 @@ snapshots: tree-kill@1.2.2: {} + tree-sitter-typescript@0.23.0(tree-sitter@0.21.1): + dependencies: + node-addon-api: 8.1.0 + node-gyp-build: 4.8.2 + tree-sitter: 0.21.1 + + tree-sitter@0.21.1: + dependencies: + node-addon-api: 8.1.0 + node-gyp-build: 4.8.2 + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: typescript: 5.5.4