diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c19e3a644d77..c8a8ee2c0e69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v3 with: - version: ^8.15.0 + version: ^9.5.0 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67f698b3be4f..2cefce68e635 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ permissions: env: APP_NAME: tailwindcss-oxide NODE_VERSION: 20 - PNPM_VERSION: ^8.15.0 + PNPM_VERSION: ^9.5.0 OXIDE_LOCATION: ./crates/node jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 54fe934900b6..c98ee385c44f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Discard invalid `variants` and `utilities` with modifiers ([#13977](https://github.com/tailwindlabs/tailwindcss/pull/13977)) - Add missing utilities that exist in v3, such as `resize`, `fill-none`, `accent-none`, `drop-shadow-none`, and negative `hue-rotate` and `backdrop-hue-rotate` utilities ([#13971](https://github.com/tailwindlabs/tailwindcss/pull/13971)) +### Added + +- Add support for basic `addVariant` plugins with new `@plugin` directive ([#13982](https://github.com/tailwindlabs/tailwindcss/pull/13982)) + ## [4.0.0-alpha.17] - 2024-07-04 ### Added diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index f739dd7fa836..62084ec459ae 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -6,7 +6,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import postcss from 'postcss' import atImport from 'postcss-import' -import { compile } from 'tailwindcss' +import * as tailwindcss from 'tailwindcss' import type { Arg, Result } from '../../utils/args' import { eprintln, @@ -124,6 +124,22 @@ export async function handle(args: Result>) { } } + let inputFile = args['--input'] && args['--input'] !== '-' ? args['--input'] : process.cwd() + + let basePath = path.dirname(path.resolve(inputFile)) + + function compile(css: string) { + return tailwindcss.compile(css, { + loadPlugin: (pluginPath) => { + if (pluginPath[0] === '.') { + return require(path.resolve(basePath, pluginPath)) + } + + return require(pluginPath) + }, + }) + } + // Compile the input let { build } = compile(input) diff --git a/packages/@tailwindcss-postcss/package.json b/packages/@tailwindcss-postcss/package.json index 431fbc6c8a69..1695b991e90d 100644 --- a/packages/@tailwindcss-postcss/package.json +++ b/packages/@tailwindcss-postcss/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@types/node": "^20.12.12", "@types/postcss-import": "^14.0.3", - "postcss": "8.4.24" + "postcss": "8.4.24", + "tailwindcss-test-utils": "workspace:*" } } diff --git a/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html b/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html index 30d3c6684dcd..9d8328479188 100644 --- a/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html +++ b/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html @@ -1 +1 @@ -
+
diff --git a/packages/@tailwindcss-postcss/src/fixtures/example-project/plugin.js b/packages/@tailwindcss-postcss/src/fixtures/example-project/plugin.js new file mode 100644 index 000000000000..0852126714ec --- /dev/null +++ b/packages/@tailwindcss-postcss/src/fixtures/example-project/plugin.js @@ -0,0 +1,4 @@ +module.exports = function ({ addVariant }) { + addVariant('inverted', '@media (inverted-colors: inverted)') + addVariant('hocus', ['&:focus', '&:hover']) +} diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts index 8896d255c8bd..ffc2818e47cc 100644 --- a/packages/@tailwindcss-postcss/src/index.test.ts +++ b/packages/@tailwindcss-postcss/src/index.test.ts @@ -134,3 +134,65 @@ describe('processing without specifying a base path', () => { }) }) }) + +describe('plugins', () => { + test('local CJS plugin', async () => { + let processor = postcss([ + tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }), + ]) + + let result = await processor.process( + css` + @import 'tailwindcss/utilities'; + @plugin 'tailwindcss-test-utils'; + `, + { from: INPUT_CSS_PATH }, + ) + + expect(result.css.trim()).toMatchInlineSnapshot(` + ".underline { + text-decoration-line: underline; + } + + @media (inverted-colors: inverted) { + .inverted\\:flex { + display: flex; + } + } + + .hocus\\:underline:focus, .hocus\\:underline:hover { + text-decoration-line: underline; + }" + `) + }) + + test('published CJS plugin', async () => { + let processor = postcss([ + tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }), + ]) + + let result = await processor.process( + css` + @import 'tailwindcss/utilities'; + @plugin 'tailwindcss-test-utils'; + `, + { from: INPUT_CSS_PATH }, + ) + + expect(result.css.trim()).toMatchInlineSnapshot(` + ".underline { + text-decoration-line: underline; + } + + @media (inverted-colors: inverted) { + .inverted\\:flex { + display: flex; + } + } + + .hocus\\:underline:focus, .hocus\\:underline:hover { + text-decoration-line: underline; + }" + `) + }) +}) diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts index 6ccaff3f0bd8..30a0828a2f51 100644 --- a/packages/@tailwindcss-postcss/src/index.ts +++ b/packages/@tailwindcss-postcss/src/index.ts @@ -1,6 +1,7 @@ import { scanDir } from '@tailwindcss/oxide' import fs from 'fs' import { Features, transform } from 'lightningcss' +import path from 'path' import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss' import postcssImport from 'postcss-import' import { compile } from 'tailwindcss' @@ -130,7 +131,16 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { } if (rebuildStrategy === 'full') { - let { build } = compile(root.toString()) + let basePath = path.dirname(path.resolve(inputFile)) + let { build } = compile(root.toString(), { + loadPlugin: (pluginPath) => { + if (pluginPath[0] === '.') { + return require(path.resolve(basePath, pluginPath)) + } + + return require(pluginPath) + }, + }) context.build = build css = build(hasTailwind ? candidates : []) } else if (rebuildStrategy === 'incremental') { diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 79b94d9faeec..d8a0924c5562 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -72,12 +72,22 @@ export default function tailwindcss(): Plugin[] { return updated } - function generateCss(css: string) { - return compile(css).build(Array.from(candidates)) + function generateCss(css: string, inputPath: string) { + let basePath = path.dirname(path.resolve(inputPath)) + + return compile(css, { + loadPlugin: (pluginPath) => { + if (pluginPath[0] === '.') { + return require(path.resolve(basePath, pluginPath)) + } + + return require(pluginPath) + }, + }).build(Array.from(candidates)) } - function generateOptimizedCss(css: string) { - return optimizeCss(generateCss(css), { minify }) + function generateOptimizedCss(css: string, inputPath: string) { + return optimizeCss(generateCss(css, inputPath), { minify }) } // Manually run the transform functions of non-Tailwind plugins on the given CSS @@ -189,7 +199,7 @@ export default function tailwindcss(): Plugin[] { await server?.waitForRequestsIdle?.(id) } - let code = await transformWithPlugins(this, id, generateCss(src)) + let code = await transformWithPlugins(this, id, generateCss(src, id)) return { code } }, }, @@ -213,7 +223,7 @@ export default function tailwindcss(): Plugin[] { continue } - let css = generateOptimizedCss(file.content) + let css = generateOptimizedCss(file.content, id) // These plugins have side effects which, during build, results in CSS // being written to the output dir. We need to run them here to ensure diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index bebd82a33c23..494b7bd53c15 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -1,4 +1,4 @@ -import { toCss } from './ast' +import { rule, toCss } from './ast' import { parseCandidate, parseVariant } from './candidate' import { compileAstNodes, compileCandidates } from './compile' import { getClassList, getVariants, type ClassEntry, type VariantEntry } from './intellisense' @@ -8,6 +8,10 @@ import { Utilities, createUtilities } from './utilities' import { DefaultMap } from './utils/default-map' import { Variants, createVariants } from './variants' +export type Plugin = (api: { + addVariant: (name: string, selector: string | string[]) => void +}) => void + export type DesignSystem = { theme: Theme utilities: Utilities @@ -25,7 +29,7 @@ export type DesignSystem = { getUsedVariants(): ReturnType[] } -export function buildDesignSystem(theme: Theme): DesignSystem { +export function buildDesignSystem(theme: Theme, plugins: Plugin[] = []): DesignSystem { let utilities = createUtilities(theme) let variants = createVariants(theme) @@ -77,5 +81,15 @@ export function buildDesignSystem(theme: Theme): DesignSystem { }, } + for (let plugin of plugins) { + plugin({ + addVariant: (name: string, selectors: string | string[]) => { + variants.static(name, (r) => { + r.nodes = ([] as string[]).concat(selectors).map((selector) => rule(selector, r.nodes)) + }) + }, + }) + } + return designSystem } diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 89f1918fff1a..0b8d184832df 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1,7 +1,8 @@ import fs from 'node:fs' import path from 'node:path' import { describe, expect, it, test } from 'vitest' -import { compileCss, run } from './test-utils/run' +import { compile } from '.' +import { compileCss, optimizeCss, run } from './test-utils/run' const css = String.raw @@ -1123,3 +1124,61 @@ describe('Parsing themes values from CSS', () => { ) }) }) + +describe('plugins', () => { + test('addVariant with string selector', () => { + let compiled = compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadPlugin: () => { + return ({ addVariant }) => { + addVariant('hocus', '&:hover, &:focus') + } + }, + }, + ).build(['hocus:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .hocus\\:underline:hover, .hocus\\:underline:focus { + text-decoration-line: underline; + } + }" + `) + }) + + test('addVariant with array of selectors', () => { + let compiled = compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadPlugin: () => { + return ({ addVariant }) => { + addVariant('hocus', ['&:hover', '&:focus']) + } + }, + }, + ).build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) { + display: flex; + } + + .hocus\\:underline:hover, .hocus\\:underline:focus { + text-decoration-line: underline; + } + }" + `) + }) +}) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 34a095584944..e15d9926bb4f 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -2,10 +2,21 @@ import { version } from '../package.json' import { WalkAction, comment, decl, rule, toCss, walk, type AstNode, type Rule } from './ast' import { compileCandidates } from './compile' import * as CSS from './css-parser' -import { buildDesignSystem } from './design-system' +import { buildDesignSystem, type Plugin } from './design-system' import { Theme } from './theme' -export function compile(css: string): { +type CompileOptions = { + loadPlugin?: (path: string) => Plugin +} + +function throwOnPlugin(): never { + throw new Error('No `loadPlugin` function provided to `compile`') +} + +export function compile( + css: string, + { loadPlugin = throwOnPlugin }: CompileOptions = {}, +): { build(candidates: string[]): string } { let ast = CSS.parse(css) @@ -22,12 +33,20 @@ export function compile(css: string): { // Find all `@theme` declarations let theme = new Theme() + let plugins: Plugin[] = [] let firstThemeRule: Rule | null = null let keyframesRules: Rule[] = [] walk(ast, (node, { replaceWith }) => { if (node.kind !== 'rule') return + // Collect paths from `@plugin` at-rules + if (node.selector.startsWith('@plugin ')) { + plugins.push(loadPlugin(node.selector.slice(9, -1))) + replaceWith([]) + return + } + // Drop instances of `@media reference` // // We support `@import "tailwindcss/theme" reference` as a way to import an external theme file @@ -125,7 +144,7 @@ export function compile(css: string): { firstThemeRule.nodes = nodes } - let designSystem = buildDesignSystem(theme) + let designSystem = buildDesignSystem(theme, plugins) let tailwindUtilitiesNode: Rule | null = null diff --git a/packages/test-utils/index.js b/packages/test-utils/index.js new file mode 100644 index 000000000000..0852126714ec --- /dev/null +++ b/packages/test-utils/index.js @@ -0,0 +1,4 @@ +module.exports = function ({ addVariant }) { + addVariant('inverted', '@media (inverted-colors: inverted)') + addVariant('hocus', ['&:focus', '&:hover']) +} diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json new file mode 100644 index 000000000000..152589ba94c1 --- /dev/null +++ b/packages/test-utils/package.json @@ -0,0 +1,5 @@ +{ + "name": "tailwindcss-test-utils", + "private": true, + "main": "index.js" +} diff --git a/playgrounds/vite/src/app.css b/playgrounds/vite/src/app.css index d4b5078586e2..adbd8e19f9b0 100644 --- a/playgrounds/vite/src/app.css +++ b/playgrounds/vite/src/app.css @@ -1 +1,2 @@ @import 'tailwindcss'; +@plugin "./plugin.js"; diff --git a/playgrounds/vite/src/app.tsx b/playgrounds/vite/src/app.tsx index ab1808bf7125..285bd41439de 100644 --- a/playgrounds/vite/src/app.tsx +++ b/playgrounds/vite/src/app.tsx @@ -4,6 +4,7 @@ export function App() { return (

Hello World

+
) diff --git a/playgrounds/vite/src/plugin.js b/playgrounds/vite/src/plugin.js new file mode 100644 index 000000000000..0852126714ec --- /dev/null +++ b/playgrounds/vite/src/plugin.js @@ -0,0 +1,4 @@ +module.exports = function ({ addVariant }) { + addVariant('inverted', '@media (inverted-colors: inverted)') + addVariant('hocus', ['&:focus', '&:hover']) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc809381bd8b..b9cc1a2b14b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,9 @@ importers: postcss: specifier: 8.4.24 version: 8.4.24 + tailwindcss-test-utils: + specifier: workspace:* + version: link:../test-utils packages/@tailwindcss-vite: dependencies: @@ -188,6 +191,8 @@ importers: specifier: ^1.25.1 version: 1.25.1 + packages/test-utils: {} + playgrounds/nextjs: dependencies: '@tailwindcss/postcss': @@ -675,6 +680,7 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -682,6 +688,7 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -1678,6 +1685,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -2444,6 +2452,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup@4.18.0: @@ -4083,7 +4092,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) @@ -4102,12 +4111,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 4.3.4 enhanced-resolve: 5.16.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -4119,14 +4128,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -4140,7 +4149,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3