diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12cc32392814..d47f7cfd2b53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+
+- Ensure `@tailwindcss/upgrade` runs on Tailwind CSS v4 projects ([#17717](https://github.com/tailwindlabs/tailwindcss/pull/17717))
+
### Fixed
- Don't scan `.geojson` files for classes by default ([#17700](https://github.com/tailwindlabs/tailwindcss/pull/17700))
diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts
index fd379e20a937..b52031b3275c 100644
--- a/integrations/upgrade/index.test.ts
+++ b/integrations/upgrade/index.test.ts
@@ -1,3 +1,4 @@
+import { isRepoDirty } from '../../packages/@tailwindcss-upgrade/src/utils/git'
import { candidate, css, html, js, json, test, ts } from '../utils'
test(
@@ -2595,40 +2596,6 @@ test(
},
)
-test(
- 'requires Tailwind v3 before attempting an upgrade',
- {
- fs: {
- 'package.json': json`
- {
- "dependencies": {
- "tailwindcss": "workspace:^",
- "@tailwindcss/upgrade": "workspace:^"
- }
- }
- `,
- 'tailwind.config.ts': js` export default {} `,
- 'src/index.html': html`
-
- `,
- 'src/index.css': css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `,
- },
- },
- async ({ exec, expect }) => {
- let output = await exec('npx @tailwindcss/upgrade', {}, { ignoreStdErr: true }).catch((e) =>
- e.toString(),
- )
-
- expect(output).toMatch(
- /Tailwind CSS v.* found. The migration tool can only be run on v3 projects./,
- )
- },
-)
-
test(
`upgrades opacity namespace values to percentages`,
{
@@ -2810,6 +2777,191 @@ test(
},
)
+test(
+ 'upgrades are idempotent, and can run on v4 projects',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "^3",
+ "@tailwindcss/upgrade": "workspace:^"
+ },
+ "devDependencies": {
+ "@tailwindcss/cli": "workspace:^"
+ }
+ }
+ `,
+ 'tailwind.config.js': js`
+ /** @type {import('tailwindcss').Config} */
+ module.exports = {
+ content: ['./src/**/*.{html,js}'],
+ }
+ `,
+ 'src/index.html': html`
+
+ `,
+ 'src/input.css': css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ .foo {
+ @apply !bg-[var(--my-color)] rounded;
+ }
+ `,
+ },
+ },
+ async ({ exec, fs, expect }) => {
+ await exec('npx @tailwindcss/upgrade')
+
+ let before = await fs.dumpFiles('./src/**/*.{css,html}')
+ expect(before).toMatchInlineSnapshot(`
+ "
+ --- ./src/index.html ---
+
+
+ --- ./src/input.css ---
+ @import 'tailwindcss';
+
+ /*
+ The default border color has changed to \`currentcolor\` in Tailwind CSS v4,
+ so we've added these compatibility styles to make sure everything still
+ looks the same as it did with Tailwind CSS v3.
+
+ If we ever want to remove these styles, we need to add an explicit border
+ color utility to any element that depends on these defaults.
+ */
+ @layer base {
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ border-color: var(--color-gray-200, currentcolor);
+ }
+ }
+
+ .foo {
+ @apply bg-(--my-color)! rounded-sm;
+ }
+ "
+ `)
+
+ // Commit the changes
+ if (isRepoDirty()) {
+ await exec('git add .')
+ await exec('git commit -m "upgrade"')
+ }
+
+ // Run the upgrade again
+ let output = await exec('npx @tailwindcss/upgrade')
+ expect(output).toContain('No changes were made to your repository')
+
+ let after = await fs.dumpFiles('./src/**/*.{css,html}')
+ expect(after).toMatchInlineSnapshot(`
+ "
+ --- ./src/index.html ---
+
+
+ --- ./src/input.css ---
+ @import 'tailwindcss';
+
+ /*
+ The default border color has changed to \`currentcolor\` in Tailwind CSS v4,
+ so we've added these compatibility styles to make sure everything still
+ looks the same as it did with Tailwind CSS v3.
+
+ If we ever want to remove these styles, we need to add an explicit border
+ color utility to any element that depends on these defaults.
+ */
+ @layer base {
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ border-color: var(--color-gray-200, currentcolor);
+ }
+ }
+
+ .foo {
+ @apply bg-(--my-color)! rounded-sm;
+ }
+ "
+ `)
+
+ // Ensure the file system is in the same state
+ expect(before).toEqual(after)
+ },
+)
+
+test(
+ 'upgrades run on v4 projects',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "^4",
+ "@tailwindcss/upgrade": "workspace:^"
+ },
+ "devDependencies": {
+ "@tailwindcss/cli": "workspace:^"
+ }
+ }
+ `,
+ 'src/index.html': html`
+
+
+
+
+
+
+
+
+
+ `,
+ 'src/input.css': css`
+ @import 'tailwindcss';
+
+ .foo {
+ @apply !bg-[var(--my-color)];
+ }
+ `,
+ },
+ },
+ async ({ exec, fs, expect }) => {
+ await exec('npx @tailwindcss/upgrade')
+
+ expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
+ "
+ --- ./src/index.html ---
+
+
+
+
+
+
+
+
+
+
+ --- ./src/input.css ---
+ @import 'tailwindcss';
+
+ .foo {
+ @apply bg-(--my-color)!;
+ }
+ "
+ `)
+ },
+)
+
function withBOM(text: string): string {
return '\uFEFF' + text
}
diff --git a/packages/@tailwindcss-upgrade/package.json b/packages/@tailwindcss-upgrade/package.json
index 495499020786..075feff64b38 100644
--- a/packages/@tailwindcss-upgrade/package.json
+++ b/packages/@tailwindcss-upgrade/package.json
@@ -40,13 +40,15 @@
"postcss-import": "^16.1.0",
"postcss-selector-parser": "^7.1.0",
"prettier": "catalog:",
+ "semver": "^7.7.1",
+ "tailwindcss": "workspace:*",
"tree-sitter": "^0.22.4",
- "tree-sitter-typescript": "^0.23.2",
- "tailwindcss": "workspace:*"
+ "tree-sitter-typescript": "^0.23.2"
},
"devDependencies": {
"@types/braces": "^3.0.5",
"@types/node": "catalog:",
- "@types/postcss-import": "^14.0.3"
+ "@types/postcss-import": "^14.0.3",
+ "@types/semver": "^7.7.0"
}
}
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts
index d01368a14073..0c72cb7be4cb 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts
@@ -1,9 +1,11 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import postcss from 'postcss'
-import { expect, it } from 'vitest'
+import { expect, it, vi } from 'vitest'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
+import * as versions from '../../utils/version'
import { migrateAtApply } from './migrate-at-apply'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
const css = dedent
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts
index ffcb12619479..69f92688dff5 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts
@@ -8,8 +8,8 @@ export function migrateAtApply({
designSystem,
userConfig,
}: {
- designSystem: DesignSystem
- userConfig: Config
+ designSystem: DesignSystem | null
+ userConfig: Config | null
}): Plugin {
function migrate(atRule: AtRule) {
let utilities = atRule.params.split(/(\s+)/)
@@ -35,6 +35,8 @@ export function migrateAtApply({
})
return async () => {
+ if (!designSystem) return
+
// If we have a valid designSystem and config setup, we can run all
// candidate migrations on each utility
params = await Promise.all(
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts
index 553b1f5d079a..ba1d069471ea 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts
@@ -1,10 +1,12 @@
import dedent from 'dedent'
import postcss from 'postcss'
-import { describe, expect, it } from 'vitest'
+import { describe, expect, it, vi } from 'vitest'
import { Stylesheet } from '../../stylesheet'
+import * as versions from '../../utils/version'
import { formatNodes } from './format-nodes'
import { migrateAtLayerUtilities } from './migrate-at-layer-utilities'
import { sortBuckets } from './sort-buckets'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
const css = dedent
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts
index e775e3b6d26b..9b03e9d1658f 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts
@@ -2,10 +2,16 @@ import { type AtRule, type Comment, type Plugin, type Rule } from 'postcss'
import SelectorParser from 'postcss-selector-parser'
import { segment } from '../../../../tailwindcss/src/utils/segment'
import { Stylesheet } from '../../stylesheet'
+import * as version from '../../utils/version'
import { walk, WalkAction, walkDepth } from '../../utils/walk'
export function migrateAtLayerUtilities(stylesheet: Stylesheet): Plugin {
function migrate(atRule: AtRule) {
+ // Migrating `@layer utilities` to `@utility` is only supported in Tailwind
+ // CSS v3 projects. Tailwind CSS v4 projects could also have `@layer
+ // utilities` but those aren't actual utilities.
+ if (!version.isMajor(3)) return
+
// Only migrate `@layer utilities` and `@layer components`.
if (atRule.params !== 'utilities' && atRule.params !== 'components') return
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts
index 3149c2725e69..2004d096903d 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts
@@ -11,10 +11,11 @@ export function migrateConfig(
{
configFilePath,
jsConfigMigration,
- }: { configFilePath: string; jsConfigMigration: JSConfigMigration },
+ }: { configFilePath: string | null; jsConfigMigration: JSConfigMigration | null },
): Plugin {
function migrate() {
if (!sheet.isTailwindRoot) return
+ if (!configFilePath) return
let alreadyInjected = ALREADY_INJECTED.get(sheet)
if (alreadyInjected && alreadyInjected.includes(configFilePath)) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts
index 57cc20eeb45b..f4526209d89a 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts
@@ -9,8 +9,8 @@ export function migrateMediaScreen({
designSystem,
userConfig,
}: {
- designSystem?: DesignSystem
- userConfig?: Config
+ designSystem?: DesignSystem | null
+ userConfig?: Config | null
} = {}): Plugin {
function migrate(root: Root) {
if (!designSystem || !userConfig) return
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.test.ts
index 38471a6d89cb..2d8b700411b1 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.test.ts
@@ -1,10 +1,12 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import postcss from 'postcss'
-import { expect, it } from 'vitest'
+import { expect, it, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { formatNodes } from './format-nodes'
import { migratePreflight } from './migrate-preflight'
import { sortBuckets } from './sort-buckets'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
const css = dedent
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts
index 32172069100d..0085a2fd9f8e 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts
@@ -5,6 +5,7 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path'
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
+import * as version from '../../utils/version'
// Defaults in v4
const DEFAULT_BORDER_COLOR = 'currentcolor'
@@ -34,18 +35,23 @@ export function migratePreflight({
designSystem,
userConfig,
}: {
- designSystem: DesignSystem
- userConfig?: Config
+ designSystem: DesignSystem | null
+ userConfig?: Config | null
}): Plugin {
// @ts-expect-error
let defaultBorderColor = userConfig?.theme?.borderColor?.DEFAULT
function canResolveThemeValue(path: string) {
+ if (!designSystem) return false
let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const
return Boolean(designSystem.theme.get([variable]))
}
function migrate(root: Root) {
+ // CSS for backwards compatibility with v3 should only injected in v3
+ // projects and not v4 projects.
+ if (!version.isMajor(3)) return
+
let isTailwindRoot = false
root.walkAtRules('import', (node) => {
if (
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts
index d724620acd76..8285def1e432 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts
@@ -5,7 +5,7 @@ import { Convert, createConverter } from '../template/migrate-theme-to-var'
export function migrateThemeToVar({
designSystem,
}: {
- designSystem?: DesignSystem
+ designSystem?: DesignSystem | null
} = {}): Plugin {
return {
postcssPlugin: '@tailwindcss/upgrade/migrate-theme-to-var',
diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts
index ab77bab9f120..cb91508513fe 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts
@@ -16,10 +16,10 @@ import { migrateVariantsDirective } from './migrate-variants-directive'
export interface MigrateOptions {
newPrefix: string | null
- designSystem: DesignSystem
- userConfig: Config
- configFilePath: string
- jsConfigMigration: JSConfigMigration
+ designSystem: DesignSystem | null
+ userConfig: Config | null
+ configFilePath: string | null
+ jsConfigMigration: JSConfigMigration | null
}
export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts
index 6b01bb7437e6..fd26a9a041cb 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts
@@ -7,7 +7,7 @@ import { printCandidate } from './candidates'
export function migrateArbitraryValueToBareValue(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts
index dfb702ed8657..9fd1aaaf7594 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts
@@ -6,7 +6,7 @@ import { printCandidate } from './candidates'
export function migrateAutomaticVarInjection(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts
index a056adae6dbf..2a16524a2b1c 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts
@@ -6,7 +6,7 @@ const DIRECTIONS = ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl']
export function migrateBgGradient(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts
index 107c901c0a6c..bebd433432a2 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts
@@ -1,7 +1,9 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
-import { expect, test } from 'vitest'
+import { expect, test, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values'
import { migratePrefix } from './migrate-prefix'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
test.each([
['group-[]:flex', 'group-[&]:flex'],
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts
index 0c4fddb11bc9..0058814d9a3c 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts
@@ -3,7 +3,7 @@ import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
export function migrateEmptyArbitraryValues(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
// We can parse the candidate, nothing to do
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts
index 576557fb98d5..34b6d4ef2f01 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts
@@ -18,7 +18,7 @@ import { isSafeMigration } from './is-safe-migration'
// flex! md:block!
export function migrateImportant(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
location?: {
contents: string
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts
index b2bd44d07a06..784480521082 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts
@@ -6,7 +6,7 @@ import { printCandidate } from './candidates'
export function migrateLegacyArbitraryValues(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts
index ad589cbee80e..1dbe90426423 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts
@@ -1,6 +1,8 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
-import { expect, test } from 'vitest'
+import { expect, test, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { migrateLegacyClasses } from './migrate-legacy-classes'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
test.each([
['shadow', 'shadow-sm'],
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts
index 8f3835c8f955..2a231bb3daa7 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts
@@ -5,6 +5,7 @@ import type { Candidate } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
+import * as version from '../../utils/version'
import { printCandidate } from './candidates'
import { isSafeMigration } from './is-safe-migration'
@@ -67,7 +68,7 @@ const DESIGN_SYSTEMS = new DefaultMap((base) => {
export async function migrateLegacyClasses(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
location?: {
contents: string
@@ -75,6 +76,15 @@ export async function migrateLegacyClasses(
end: number
},
): Promise {
+ // These migrations are only safe when migrating from v3 to v4.
+ //
+ // Migrating from `rounded` to `rounded-sm` once is fine (v3 -> v4). But if we
+ // migrate again (v4 -> v4), then `rounded-sm` would be migrated to
+ // `rounded-xs` which is incorrect because we already migrated this.
+ if (!version.isMajor(3)) {
+ return rawCandidate
+ }
+
let defaultDesignSystem = await DESIGN_SYSTEMS.get(__dirname)
function* migrate(rawCandidate: string) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts
index 92fec5099546..f7e04dbf194d 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts
@@ -4,7 +4,7 @@ import { printCandidate } from './candidates'
export function migrateMaxWidthScreen(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts
index cc4e5a4a5fdc..842d627f94b1 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts
@@ -1,8 +1,10 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
-import { expect, test } from 'vitest'
+import { expect, test, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values'
import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-values'
import { migratePrefix } from './migrate-prefix'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
test.each([
// Arbitrary variants
@@ -78,6 +80,13 @@ test.each([
// Attribute selector wrapped in `&:is(…)`
['[&:is([data-visible])]:flex', 'data-visible:flex'],
+ // Media queries
+ ['[@media(pointer:fine)]:flex', 'pointer-fine:flex'],
+ ['[@media_(pointer_:_fine)]:flex', 'pointer-fine:flex'],
+ ['[@media_not_(pointer_:_fine)]:flex', 'not-pointer-fine:flex'],
+ ['[@media_print]:flex', 'print:flex'],
+ ['[@media_not_print]:flex', 'not-print:flex'],
+
// Compound arbitrary variants
['has-[[data-visible]]:flex', 'has-data-visible:flex'],
['has-[&:is([data-visible])]:flex', 'has-data-visible:flex'],
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts
index f0eec7485a29..c3325f2ced8e 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts
@@ -3,6 +3,7 @@ import { parseCandidate, type Candidate, type Variant } from '../../../../tailwi
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type'
+import * as ValueParser from '../../../../tailwindcss/src/value-parser'
import { printCandidate } from './candidates'
function memcpy(target: T, source: U): U {
@@ -15,7 +16,7 @@ function memcpy(target: T, source: U)
export function migrateModernizeArbitraryValues(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
@@ -146,6 +147,107 @@ export function migrateModernizeArbitraryValues(
continue
}
+ // Migrate `@media` variants
+ //
+ // E.g.: `[@media(scripting:none)]:` -> `noscript:`
+ if (
+ // Only top-level, so something like `in-[@media(scripting:none)]`
+ // (which is not valid anyway) is not supported
+ parent === null &&
+ // [@media(scripting:none)]:flex
+ // ^^^^^^^^^^^^^^^^^^^^^^
+ ast.nodes[0].nodes[0].type === 'tag' &&
+ ast.nodes[0].nodes[0].value.startsWith('@media')
+ ) {
+ // Replace all whitespace such that `@media (scripting: none)` and
+ // `@media(scripting:none)` are equivalent.
+ //
+ // As arbitrary variants that means that these are equivalent:
+ // - `[@media_(scripting:_none)]:`
+ // - `[@media(scripting:none)]:`
+ let parsed = ValueParser.parse(ast.nodes[0].toString().trim().replace('@media', ''))
+
+ // Drop whitespace
+ ValueParser.walk(parsed, (node, { replaceWith }) => {
+ // Drop whitespace nodes
+ if (node.kind === 'separator' && !node.value.trim()) {
+ replaceWith([])
+ }
+
+ // Trim whitespace
+ else {
+ node.value = node.value.trim()
+ }
+ })
+
+ let not = false
+ if (parsed[0]?.kind === 'word' && parsed[0].value === 'not') {
+ not = true
+ parsed.shift()
+ }
+
+ // Single keyword at-rules.
+ //
+ // E.g.: `[@media_print]:` -< `@media print` -> `print:`
+ if (parsed.length === 1 && parsed[0].kind === 'word') {
+ let key = parsed[0].value
+ let replacement: string | null = null
+ if (key === 'print') replacement = 'print'
+
+ if (replacement) {
+ changed = true
+ memcpy(variant, designSystem.parseVariant(`${not ? 'not-' : ''}${replacement}`))
+ }
+ }
+
+ // Key/value at-rules.
+ //
+ // E.g.: `[@media(scripting:none)]:` -> `scripting:`
+ if (
+ parsed.length === 1 &&
+ parsed[0].kind === 'function' && // `(` and `)` are considered a function
+ parsed[0].nodes.length === 3 &&
+ parsed[0].nodes[0].kind === 'word' &&
+ parsed[0].nodes[1].kind === 'separator' &&
+ parsed[0].nodes[1].value === ':' &&
+ parsed[0].nodes[2].kind === 'word'
+ ) {
+ let key = parsed[0].nodes[0].value
+ let value = parsed[0].nodes[2].value
+ let replacement: string | null = null
+
+ if (key === 'prefers-reduced-motion' && value === 'no-preference')
+ replacement = 'motion-safe'
+ if (key === 'prefers-reduced-motion' && value === 'reduce')
+ replacement = 'motion-reduce'
+
+ if (key === 'prefers-contrast' && value === 'more') replacement = 'contrast-more'
+ if (key === 'prefers-contrast' && value === 'less') replacement = 'contrast-less'
+
+ if (key === 'orientation' && value === 'portrait') replacement = 'portrait'
+ if (key === 'orientation' && value === 'landscape') replacement = 'landscape'
+
+ if (key === 'forced-colors' && value === 'active') replacement = 'forced-colors'
+
+ if (key === 'inverted-colors' && value === 'inverted') replacement = 'inverted-colors'
+
+ if (key === 'pointer' && value === 'none') replacement = 'pointer-none'
+ if (key === 'pointer' && value === 'coarse') replacement = 'pointer-coarse'
+ if (key === 'pointer' && value === 'fine') replacement = 'pointer-fine'
+ if (key === 'any-pointer' && value === 'none') replacement = 'any-pointer-none'
+ if (key === 'any-pointer' && value === 'coarse') replacement = 'any-pointer-coarse'
+ if (key === 'any-pointer' && value === 'fine') replacement = 'any-pointer-fine'
+
+ if (key === 'scripting' && value === 'none') replacement = 'noscript'
+
+ if (replacement) {
+ changed = true
+ memcpy(variant, designSystem.parseVariant(`${not ? 'not-' : ''}${replacement}`))
+ }
+ }
+ continue
+ }
+
let prefixedVariant: Variant | null = null
// Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible`
@@ -242,6 +344,8 @@ export function migrateModernizeArbitraryValues(
else if (value === ':required') return 'required'
else if (value === ':valid') return 'valid'
else if (value === ':invalid') return 'invalid'
+ else if (value === ':user-valid') return 'user-valid'
+ else if (value === ':user-invalid') return 'user-invalid'
else if (value === ':in-range') return 'in-range'
else if (value === ':out-of-range') return 'out-of-range'
else if (value === ':read-only') return 'read-only'
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts
index 04d9282ab7b1..f47e181af83c 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts
@@ -1,6 +1,8 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
-import { describe, expect, test } from 'vitest'
+import { describe, expect, test, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { migratePrefix } from './migrate-prefix'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
describe('for projects with configured prefix', () => {
test.each([
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts
index 95bb4c69c3ee..3e55dddf06d5 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts
@@ -2,16 +2,19 @@ import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/cand
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { segment } from '../../../../tailwindcss/src/utils/segment'
+import * as version from '../../utils/version'
import { printCandidate } from './candidates'
let seenDesignSystems = new WeakSet()
export function migratePrefix(
designSystem: DesignSystem,
- userConfig: Config,
+ userConfig: Config | null,
rawCandidate: string,
): string {
if (!designSystem.theme.prefix) return rawCandidate
+ if (!userConfig) return rawCandidate
+ if (!version.isMajor(3)) return rawCandidate
if (!seenDesignSystems.has(designSystem)) {
designSystem.utilities.functional('group', () => null)
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts
index cd54234efd4d..456671dd76ba 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts
@@ -1,6 +1,8 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
-import { expect, test } from 'vitest'
+import { expect, test, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { migrateSimpleLegacyClasses } from './migrate-simple-legacy-classes'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
test.each([
['overflow-ellipsis', 'text-ellipsis'],
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts
index dd6599152c7f..c87624a200b5 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts
@@ -1,10 +1,11 @@
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
+import * as version from '../../utils/version'
import { printCandidate } from './candidates'
// Classes that used to exist in Tailwind CSS v3, but do not exist in Tailwind
// CSS v4 anymore.
-const LEGACY_CLASS_MAP = {
+const LEGACY_CLASS_MAP: Record = {
'overflow-ellipsis': 'text-ellipsis',
'flex-grow': 'grow',
@@ -15,16 +16,33 @@ const LEGACY_CLASS_MAP = {
'decoration-clone': 'box-decoration-clone',
'decoration-slice': 'box-decoration-slice',
- 'outline-none': 'outline-hidden',
+ // Since v4.1.0
+ 'bg-left-top': 'bg-top-left',
+ 'bg-left-bottom': 'bg-bottom-left',
+ 'bg-right-top': 'bg-top-right',
+ 'bg-right-bottom': 'bg-bottom-right',
+ 'object-left-top': 'object-top-left',
+ 'object-left-bottom': 'object-bottom-left',
+ 'object-right-top': 'object-top-right',
+ 'object-right-bottom': 'object-bottom-right',
}
let seenDesignSystems = new WeakSet()
export function migrateSimpleLegacyClasses(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
+ // `outline-none` in v3 has the same meaning as `outline-hidden` in v4. However,
+ // `outline-none` in v4 _also_ exists but has a different meaning.
+ //
+ // We can only migrate `outline-none` to `outline-hidden` if we are migrating a
+ // v3 project to v4.
+ if (version.isMajor(3)) {
+ LEGACY_CLASS_MAP['outline-none'] = 'outline-hidden'
+ }
+
// Prepare design system with the unknown legacy classes
if (!seenDesignSystems.has(designSystem)) {
for (let old in LEGACY_CLASS_MAP) {
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
index 3abb4c96fcc4..6e99d6f9482d 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
@@ -21,7 +21,7 @@ export const enum Convert {
export function migrateThemeToVar(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
let convert = createConverter(designSystem)
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts
index 61589b8fed5a..1b14d533f87f 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts
@@ -1,7 +1,9 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
-import { expect, test } from 'vitest'
+import { expect, test, vi } from 'vitest'
+import * as versions from '../../utils/version'
import { migrateVariantOrder } from './migrate-variant-order'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
let css = dedent
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts
index ab18b6b78476..f9da9ecc28ec 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts
@@ -2,13 +2,23 @@ import { walk, type AstNode } from '../../../../tailwindcss/src/ast'
import { type Variant } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
+import * as version from '../../utils/version'
import { printCandidate } from './candidates'
export function migrateVariantOrder(
designSystem: DesignSystem,
- _userConfig: Config,
+ _userConfig: Config | null,
rawCandidate: string,
): string {
+ // This migration is only needed for Tailwind CSS v3
+ //
+ // Changing the variant order when migrating from v3 to v4 is fine, but
+ // migrating v4 to v4 would make it unsafe because the variant order would
+ // flip-flop every time you run the migration.
+ if (!version.isMajor(3)) {
+ return rawCandidate
+ }
+
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
if (candidate.variants.length <= 1) {
continue
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts
index c18e2f25e6a6..a80cb2499320 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts
@@ -1,9 +1,10 @@
import fs from 'node:fs/promises'
import path, { extname } from 'node:path'
+import { parseCandidate } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { spliceChangesIntoString, type StringChange } from '../../utils/splice-changes-into-string'
-import { extractRawCandidates } from './candidates'
+import { extractRawCandidates, printCandidate } from './candidates'
import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value'
import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection'
import { migrateBgGradient } from './migrate-bg-gradient'
@@ -20,7 +21,7 @@ import { migrateVariantOrder } from './migrate-variant-order'
export type Migration = (
designSystem: DesignSystem,
- userConfig: Config,
+ userConfig: Config | null,
rawCandidate: string,
location?: {
contents: string
@@ -47,7 +48,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
export async function migrateCandidate(
designSystem: DesignSystem,
- userConfig: Config,
+ userConfig: Config | null,
rawCandidate: string,
// Location is only set when migrating a candidate from a source file
location?: {
@@ -56,15 +57,28 @@ export async function migrateCandidate(
end: number
},
): Promise {
+ let original = rawCandidate
for (let migration of DEFAULT_MIGRATIONS) {
rawCandidate = await migration(designSystem, userConfig, rawCandidate, location)
}
+
+ // If nothing changed, let's parse it again and re-print it. This will migrate
+ // pretty print candidates to the new format. If it did change, we already had
+ // to re-print it.
+ //
+ // E.g.: `bg-red-500/[var(--my-opacity)]` -> `bg-red-500/(--my-opacity)`
+ if (rawCandidate === original) {
+ for (let candidate of parseCandidate(rawCandidate, designSystem)) {
+ return printCandidate(designSystem, candidate)
+ }
+ }
+
return rawCandidate
}
export default async function migrateContents(
designSystem: DesignSystem,
- userConfig: Config,
+ userConfig: Config | null,
contents: string,
extension: string,
): Promise {
@@ -93,7 +107,7 @@ export default async function migrateContents(
return spliceChangesIntoString(contents, changes)
}
-export async function migrate(designSystem: DesignSystem, userConfig: Config, file: string) {
+export async function migrate(designSystem: DesignSystem, userConfig: Config | null, file: string) {
let fullPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file)
let contents = await fs.readFile(fullPath, 'utf-8')
diff --git a/packages/@tailwindcss-upgrade/src/index.test.ts b/packages/@tailwindcss-upgrade/src/index.test.ts
index b86647c82f24..2eae32f305f1 100644
--- a/packages/@tailwindcss-upgrade/src/index.test.ts
+++ b/packages/@tailwindcss-upgrade/src/index.test.ts
@@ -2,10 +2,12 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import path from 'node:path'
import postcss from 'postcss'
-import { expect, it } from 'vitest'
+import { expect, it, vi } from 'vitest'
import { formatNodes } from './codemods/css/format-nodes'
import { migrateContents } from './codemods/css/migrate'
import { sortBuckets } from './codemods/css/sort-buckets'
+import * as versions from './utils/version'
+vi.spyOn(versions, 'isMajor').mockReturnValue(true)
const css = dedent
diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts
index 75bd47b523a4..1c679518f234 100644
--- a/packages/@tailwindcss-upgrade/src/index.ts
+++ b/packages/@tailwindcss-upgrade/src/index.ts
@@ -1,5 +1,6 @@
#!/usr/bin/env node
+import { Scanner } from '@tailwindcss/oxide'
import { globby } from 'globby'
import fs from 'node:fs/promises'
import path from 'node:path'
@@ -19,10 +20,9 @@ import { help } from './commands/help'
import { Stylesheet } from './stylesheet'
import { args, type Arg } from './utils/args'
import { isRepoDirty } from './utils/git'
-import { hoistStaticGlobParts } from './utils/hoist-static-glob-parts'
-import { getPackageVersion } from './utils/package-version'
import { pkg } from './utils/packages'
import { eprintln, error, header, highlight, info, relative, success } from './utils/renderer'
+import * as version from './utils/version'
const options = {
'--config': { type: 'string', description: 'Path to the configuration file', alias: '-c' },
@@ -59,15 +59,6 @@ async function run() {
}
}
- // Require an installed `tailwindcss` version < 4
- let tailwindVersion = await getPackageVersion('tailwindcss', base)
- if (tailwindVersion && Number(tailwindVersion.split('.')[0]) !== 3) {
- error(
- `Tailwind CSS v${tailwindVersion} found. The migration tool can only be run on v3 projects.`,
- )
- process.exit(1)
- }
-
{
// Stylesheet migrations
@@ -108,18 +99,21 @@ async function run() {
error(`${e?.message ?? e}`, { prefix: '↳ ' })
}
- // Ensure stylesheets are linked to configs
- try {
- await linkConfigsToStylesheets(stylesheets, {
- configPath: flags['--config'],
- base,
- })
- } catch (e: any) {
- error(`${e?.message ?? e}`, { prefix: '↳ ' })
+ // Ensure stylesheets are linked to configs. But this is only necessary when
+ // migrating from v3 to v4.
+ if (version.isMajor(3)) {
+ try {
+ await linkConfigsToStylesheets(stylesheets, {
+ configPath: flags['--config'],
+ base,
+ })
+ } catch (e: any) {
+ error(`${e?.message ?? e}`, { prefix: '↳ ' })
+ }
}
// Migrate js config files, linked to stylesheets
- if (stylesheets.some((sheet) => sheet.isTailwindRoot)) {
+ if (stylesheets.some((sheet) => sheet.isTailwindRoot && sheet.linkedConfigPath)) {
info('Migrating JavaScript configuration files…')
}
let configBySheet = new Map>>()
@@ -129,6 +123,7 @@ async function run() {
>()
for (let sheet of stylesheets) {
if (!sheet.isTailwindRoot) continue
+ if (!version.isMajor(3) && !sheet.linkedConfigPath) continue
let config = await prepareConfig(sheet.linkedConfigPath, { base })
configBySheet.set(sheet, config)
@@ -153,41 +148,6 @@ async function run() {
}
}
- // Migrate source files, linked to config files
- if (configBySheet.size > 0) {
- info('Migrating templates…')
- }
- {
- // Template migrations
- for (let config of configBySheet.values()) {
- let set = new Set()
- for (let globEntry of config.sources.flatMap((entry) => hoistStaticGlobParts(entry))) {
- let files = await globby([globEntry.pattern], {
- absolute: true,
- gitignore: true,
- cwd: globEntry.base,
- })
-
- for (let file of files) {
- set.add(file)
- }
- }
-
- let files = Array.from(set)
- files.sort()
-
- // Migrate each file
- await Promise.allSettled(
- files.map((file) => migrateTemplate(config.designSystem, config.userConfig, file)),
- )
-
- success(
- `Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`,
- { prefix: '↳ ' },
- )
- }
- }
-
// Migrate each CSS file
if (stylesheets.length > 0) {
info('Migrating stylesheets…')
@@ -195,20 +155,26 @@ async function run() {
await Promise.all(
stylesheets.map(async (sheet) => {
try {
- let config = configBySheet.get(sheet)!
- let jsConfigMigration = jsConfigMigrationBySheet.get(sheet)!
+ let config = configBySheet.get(sheet)
+ let jsConfigMigration = jsConfigMigrationBySheet.get(sheet) ?? null
if (!config) {
for (let parent of sheet.ancestors()) {
if (parent.isTailwindRoot) {
config ??= configBySheet.get(parent)!
- jsConfigMigration ??= jsConfigMigrationBySheet.get(parent)!
+ jsConfigMigration ??= jsConfigMigrationBySheet.get(parent) ?? null
break
}
}
}
- await migrateStylesheet(sheet, { ...config, jsConfigMigration })
+ await migrateStylesheet(sheet, {
+ newPrefix: config?.newPrefix ?? null,
+ designSystem: config?.designSystem ?? (await sheet.designSystem()),
+ userConfig: config?.userConfig ?? null,
+ configFilePath: config?.configFilePath ?? null,
+ jsConfigMigration,
+ })
} catch (e: any) {
error(`${e?.message ?? e} in ${highlight(relative(sheet.file!, base))}`, { prefix: '↳ ' })
}
@@ -216,31 +182,33 @@ async function run() {
)
// Split up stylesheets (as needed)
- try {
- await splitStylesheets(stylesheets)
- } catch (e: any) {
- error(`${e?.message ?? e}`, { prefix: '↳ ' })
- }
+ if (version.isMajor(3)) {
+ try {
+ await splitStylesheets(stylesheets)
+ } catch (e: any) {
+ error(`${e?.message ?? e}`, { prefix: '↳ ' })
+ }
- // Cleanup `@import "…" layer(utilities)`
- for (let sheet of stylesheets) {
- for (let importRule of sheet.importRules) {
- if (!importRule.raws.tailwind_injected_layer) continue
- let importedSheet = stylesheets.find(
- (sheet) => sheet.id === importRule.raws.tailwind_destination_sheet_id,
- )
- if (!importedSheet) continue
-
- // Only remove the `layer(…)` next to the import if any of the children
- // contain `@utility`. Otherwise `@utility` will not be top-level.
- if (
- !importedSheet.containsRule((node) => node.type === 'atrule' && node.name === 'utility')
- ) {
- continue
- }
+ // Cleanup `@import "…" layer(utilities)`
+ for (let sheet of stylesheets) {
+ for (let importRule of sheet.importRules) {
+ if (!importRule.raws.tailwind_injected_layer) continue
+ let importedSheet = stylesheets.find(
+ (sheet) => sheet.id === importRule.raws.tailwind_destination_sheet_id,
+ )
+ if (!importedSheet) continue
+
+ // Only remove the `layer(…)` next to the import if any of the children
+ // contain `@utility`. Otherwise `@utility` will not be top-level.
+ if (
+ !importedSheet.containsRule((node) => node.type === 'atrule' && node.name === 'utility')
+ ) {
+ continue
+ }
- // Make sure to remove the `layer(…)` from the `@import` at-rule
- importRule.params = importRule.params.replace(/ layer\([^)]+\)/, '').trim()
+ // Make sure to remove the `layer(…)` from the `@import` at-rule
+ importRule.params = importRule.params.replace(/ layer\([^)]+\)/, '').trim()
+ }
}
}
@@ -259,9 +227,80 @@ async function run() {
success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`, { prefix: '↳ ' })
}
}
+
+ info('Updating dependencies…')
+ try {
+ // Upgrade Tailwind CSS
+ await pkg(base).add(['tailwindcss@latest'])
+ success(`Updated package: ${highlight('tailwindcss')}`, { prefix: '↳ ' })
+ } catch {}
+
+ let tailwindRootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot && sheet.file)
+
+ // Migrate source files
+ if (tailwindRootStylesheets.length > 0) {
+ info('Migrating templates…')
+ }
+ {
+ let seenFiles = new Set()
+
+ // Template migrations
+ for (let sheet of tailwindRootStylesheets) {
+ let compiler = await sheet.compiler()
+ if (!compiler) continue
+ let designSystem = await sheet.designSystem()
+ if (!designSystem) continue
+
+ // Figure out the source files to migrate
+ let sources = (() => {
+ // Disable auto source detection
+ if (compiler.root === 'none') {
+ return []
+ }
+
+ // No root specified, use the base directory
+ if (compiler.root === null) {
+ return [{ base, pattern: '**/*', negated: false }]
+ }
+
+ // Use the specified root
+ return [{ ...compiler.root, negated: false }]
+ })().concat(compiler.sources)
+
+ let config = configBySheet.get(sheet)
+ let scanner = new Scanner({ sources })
+ let filesToMigrate = []
+ for (let file of scanner.files) {
+ if (seenFiles.has(file)) continue
+ seenFiles.add(file)
+ filesToMigrate.push(file)
+ }
+
+ // Migrate each file
+ await Promise.allSettled(
+ filesToMigrate.map((file) =>
+ migrateTemplate(designSystem, config?.userConfig ?? null, file),
+ ),
+ )
+
+ if (config?.configFilePath) {
+ success(
+ `Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`,
+ { prefix: '↳ ' },
+ )
+ } else {
+ success(
+ `Migrated templates for: ${highlight(relative(sheet.file ?? '', base))}`,
+ {
+ prefix: '↳ ',
+ },
+ )
+ }
+ }
+ }
}
- {
+ if (version.isMajor(3)) {
// PostCSS config migration
await migratePostCSSConfig(base)
}
@@ -272,12 +311,6 @@ async function run() {
await migratePrettierPlugin(base)
}
- try {
- // Upgrade Tailwind CSS
- await pkg(base).add(['tailwindcss@latest'])
- success(`Updated package: ${highlight('tailwindcss')}`, { prefix: '↳ ' })
- } catch {}
-
// Run all cleanup functions because we completed the migration
await Promise.allSettled(cleanup.map((fn) => fn()))
diff --git a/packages/@tailwindcss-upgrade/src/stylesheet.ts b/packages/@tailwindcss-upgrade/src/stylesheet.ts
index 092d9414b569..42cd1f3c6210 100644
--- a/packages/@tailwindcss-upgrade/src/stylesheet.ts
+++ b/packages/@tailwindcss-upgrade/src/stylesheet.ts
@@ -1,8 +1,10 @@
+import { __unstable__loadDesignSystem, compileAst } from '@tailwindcss/node'
import * as fsSync from 'node:fs'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as util from 'node:util'
import * as postcss from 'postcss'
+import { postCssAstToCssAst } from '../../@tailwindcss-postcss/src/ast'
export type StylesheetId = string
@@ -263,6 +265,25 @@ export class Stylesheet {
return false
}
+ async compiler(): Promise> | null> {
+ if (!this.isTailwindRoot) return null
+ if (!this.file) return null
+
+ return compileAst(postCssAstToCssAst(this.root), {
+ base: path.dirname(this.file),
+ onDependency() {},
+ })
+ }
+
+ async designSystem(): Promise> | null> {
+ if (!this.isTailwindRoot) return null
+ if (!this.file) return null
+
+ return __unstable__loadDesignSystem(this.root.toString(), {
+ base: path.dirname(this.file),
+ })
+ }
+
[util.inspect.custom]() {
return {
...this,
diff --git a/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts b/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts
deleted file mode 100644
index 1e6d8b1e861e..000000000000
--- a/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { expect, it } from 'vitest'
-import { hoistStaticGlobParts } from './hoist-static-glob-parts'
-
-it.each([
- // A basic glob
- [
- { base: '/projects/project-a', pattern: './src/**/*.html' },
- [{ base: '/projects/project-a/src', pattern: '**/*.html' }],
- ],
-
- // A glob pointing to a folder should result in `**/*`
- [
- { base: '/projects/project-a', pattern: './src' },
- [{ base: '/projects/project-a/src', pattern: '**/*' }],
- ],
-
- // A glob pointing to a file, should result in the file as the pattern
- [
- { base: '/projects/project-a', pattern: './src/index.html' },
- [{ base: '/projects/project-a/src', pattern: 'index.html' }],
- ],
-
- // A glob going up a directory, should result in the new directory as the base
- [
- { base: '/projects/project-a', pattern: '../project-b/src/**/*.html' },
- [{ base: '/projects/project-b/src', pattern: '**/*.html' }],
- ],
-
- // A glob with curlies, should be expanded to multiple globs
- [
- { base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.html' },
- [
- { base: '/projects/project-b/src', pattern: '**/*.html' },
- { base: '/projects/project-c/src', pattern: '**/*.html' },
- ],
- ],
- [
- { base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.{js,html}' },
- [
- { base: '/projects/project-b/src', pattern: '**/*.js' },
- { base: '/projects/project-b/src', pattern: '**/*.html' },
- { base: '/projects/project-c/src', pattern: '**/*.js' },
- { base: '/projects/project-c/src', pattern: '**/*.html' },
- ],
- ],
-])('should hoist the static parts of the glob: %s', (input, output) => {
- expect(hoistStaticGlobParts(input)).toEqual(output)
-})
diff --git a/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.ts b/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.ts
deleted file mode 100644
index 18d58799857b..000000000000
--- a/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { normalizePath } from '@tailwindcss/node'
-import braces from 'braces'
-import path from 'node:path'
-
-interface GlobEntry {
- base: string
- pattern: string
-}
-
-export function hoistStaticGlobParts(entry: GlobEntry): GlobEntry[] {
- return braces(entry.pattern, { expand: true }).map((pattern) => {
- let clone = { ...entry }
- let [staticPart, dynamicPart] = splitPattern(pattern)
-
- // Move static part into the `base`.
- let absolutePosixPath = normalizePath(entry.base)
-
- if (staticPart !== null) {
- clone.base = path.posix.join(absolutePosixPath, staticPart)
- } else {
- clone.base = absolutePosixPath
- }
-
- // Move dynamic part into the `pattern`.
- if (dynamicPart === null) {
- clone.pattern = '**/*'
- } else {
- clone.pattern = dynamicPart
- }
-
- // If the pattern looks like a file, move the file name from the `base` to
- // the `pattern`.
- let file = path.basename(clone.base)
- if (file.includes('.')) {
- clone.pattern = file
- clone.base = path.dirname(clone.base)
- }
-
- return clone
- })
-}
-
-// Split a glob pattern into a `static` and `dynamic` part.
-//
-// Assumption: we assume that all globs are expanded, which means that the only
-// dynamic parts are using `*`.
-//
-// E.g.:
-// Original input: `../project-b/**/*.{html,js}`
-// Expanded input: `../project-b/**/*.html` & `../project-b/**/*.js`
-// Split on first input: ("../project-b", "**/*.html")
-// Split on second input: ("../project-b", "**/*.js")
-function splitPattern(pattern: string): [staticPart: string | null, dynamicPart: string | null] {
- // No dynamic parts, so we can just return the input as-is.
- if (!pattern.includes('*')) {
- return [pattern, null]
- }
-
- let lastSlashPosition: number | null = null
-
- for (let i = 0; i < pattern.length; i++) {
- let c = pattern[i]
- if (c === '/') {
- lastSlashPosition = i
- }
-
- if (c === '*' || c === '!') {
- break
- }
- }
-
- // Very first character is a `*`, therefore there is no static part, only a
- // dynamic part.
- if (lastSlashPosition === null) {
- return [null, pattern]
- }
-
- let staticPart = pattern.slice(0, lastSlashPosition).trim()
- let dynamicPart = pattern.slice(lastSlashPosition + 1).trim()
-
- return [staticPart || null, dynamicPart || null]
-}
diff --git a/packages/@tailwindcss-upgrade/src/utils/package-version.ts b/packages/@tailwindcss-upgrade/src/utils/package-version.ts
index 833ccadf29c9..64a1e7ed1f30 100644
--- a/packages/@tailwindcss-upgrade/src/utils/package-version.ts
+++ b/packages/@tailwindcss-upgrade/src/utils/package-version.ts
@@ -1,3 +1,4 @@
+import { readFileSync } from 'node:fs'
import fs from 'node:fs/promises'
import { resolveJsId } from './resolve'
@@ -15,3 +16,14 @@ export async function getPackageVersion(pkg: string, base: string): Promise=${version}.0.0 <${version + 1}.0.0`)
+}
+
+let cache = new DefaultMap((base) => {
+ let tailwindVersion = getPackageVersionSync('tailwindcss', base)
+ if (!tailwindVersion) throw new Error('Tailwind CSS is not installed')
+ return tailwindVersion
+})
+
+function installedTailwindVersion(base = process.cwd()): string {
+ return cache.get(base)
+}
diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
index 8caec195e607..3a748fd8b306 100644
--- a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
+++ b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
@@ -116,10 +116,7 @@ test('config values can be merged into the theme', () => {
{ '--line-height': '3rem' },
])
expect(theme.resolve('2xl', ['--text'])).toEqual('2rem')
- expect(theme.resolveWith('2xl', ['--text'], ['--line-height'])).toEqual([
- '2rem',
- {},
- ])
+ expect(theme.resolveWith('2xl', ['--text'], ['--line-height'])).toEqual(['2rem', {}])
expect(theme.resolve('super-wide', ['--tracking'])).toEqual('0.25em')
expect(theme.resolve('super-loose', ['--leading'])).toEqual('3')
expect(theme.resolve('1/2', ['--width'])).toEqual('60%')
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1d571aeafffd..88f88d2287b3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -388,6 +388,9 @@ importers:
prettier:
specifier: 'catalog:'
version: 3.5.0
+ semver:
+ specifier: ^7.7.1
+ version: 7.7.1
tailwindcss:
specifier: workspace:*
version: link:../tailwindcss
@@ -407,6 +410,9 @@ importers:
'@types/postcss-import':
specifier: ^14.0.3
version: 14.0.3
+ '@types/semver':
+ specifier: ^7.7.0
+ version: 7.7.0
packages/@tailwindcss-vite:
dependencies:
@@ -2057,7 +2063,6 @@ packages:
'@parcel/watcher-darwin-arm64@2.5.1':
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.0':
@@ -2069,7 +2074,6 @@ packages:
'@parcel/watcher-darwin-x64@2.5.1':
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.0':
@@ -2117,7 +2121,6 @@ packages:
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.5.0':
@@ -2129,7 +2132,6 @@ packages:
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.5.0':
@@ -2141,7 +2143,6 @@ packages:
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.5.0':
@@ -2153,7 +2154,6 @@ packages:
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [linux]
'@parcel/watcher-wasm@2.5.0':
@@ -2195,7 +2195,6 @@ packages:
'@parcel/watcher-win32-x64@2.5.1':
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.0':
@@ -2385,6 +2384,9 @@ packages:
'@types/react@19.1.2':
resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==}
+ '@types/semver@7.7.0':
+ resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
+
'@types/ws@8.5.12':
resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
@@ -2615,7 +2617,6 @@ packages:
bun@1.2.8:
resolution: {integrity: sha512-X8r9UuXAruvpE37u/JVfvJI8KRdFf9hUdTLw00DMScnBe7Xerawd/VvmFVT9Y/NrmXDAdDp0Dm6N6bulZYTGvA==}
- cpu: [arm64, x64, aarch64]
os: [darwin, linux, win32]
hasBin: true
@@ -5973,6 +5974,8 @@ snapshots:
dependencies:
csstype: 3.1.3
+ '@types/semver@7.7.0': {}
+
'@types/ws@8.5.12':
dependencies:
'@types/node': 20.14.13
@@ -6783,7 +6786,7 @@ snapshots:
eslint: 9.24.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.24.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.24.0(jiti@2.4.2))
eslint-plugin-react: 7.37.2(eslint@9.24.0(jiti@2.4.2))
eslint-plugin-react-hooks: 5.0.0(eslint@9.24.0(jiti@2.4.2))
@@ -6803,7 +6806,7 @@ snapshots:
eslint: 9.24.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.24.0(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.24.0(jiti@2.4.2))
eslint-plugin-react: 7.37.2(eslint@9.24.0(jiti@2.4.2))
eslint-plugin-react-hooks: 5.0.0(eslint@9.24.0(jiti@2.4.2))
@@ -6828,13 +6831,13 @@ snapshots:
debug: 4.4.0
enhanced-resolve: 5.18.1
eslint: 9.24.0(jiti@2.4.2)
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2))
fast-glob: 3.3.3
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.24.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@@ -6847,20 +6850,20 @@ snapshots:
debug: 4.4.0
enhanced-resolve: 5.18.1
eslint: 9.24.0(jiti@2.4.2)
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2))
fast-glob: 3.3.3
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.24.0(jiti@2.4.2))
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -6871,7 +6874,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -6882,7 +6885,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.24.0(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -6893,7 +6896,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.24.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -6911,7 +6914,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.24.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -6922,7 +6925,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.24.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.24.0(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -7314,7 +7317,7 @@ snapshots:
is-bun-module@1.2.1:
dependencies:
- semver: 7.6.3
+ semver: 7.7.1
is-callable@1.2.7: {}