diff --git a/javascript/packages/tailwind-class-sorter/package.json b/javascript/packages/tailwind-class-sorter/package.json index 5745df71a..3e337abf1 100644 --- a/javascript/packages/tailwind-class-sorter/package.json +++ b/javascript/packages/tailwind-class-sorter/package.json @@ -32,6 +32,8 @@ "build": "yarn clean && rollup -c", "dev": "rollup -c -w", "clean": "rimraf dist", + "setup-fixtures": "bash scripts/setup-fixtures.sh", + "pretest": "yarn setup-fixtures", "test": "vitest run", "test:watch": "vitest --watch", "prepublishOnly": "yarn clean && yarn build && yarn test" diff --git a/javascript/packages/tailwind-class-sorter/rollup.config.mjs b/javascript/packages/tailwind-class-sorter/rollup.config.mjs index aa09e840c..379f33da2 100644 --- a/javascript/packages/tailwind-class-sorter/rollup.config.mjs +++ b/javascript/packages/tailwind-class-sorter/rollup.config.mjs @@ -11,7 +11,16 @@ export default [ format: "esm", sourcemap: true, }, - external: ["tailwindcss", "tailwindcss/loadConfig", "tailwindcss/resolveConfig", "fs/promises", "path", "url"], + external: [ + "tailwindcss", + "tailwindcss/loadConfig.js", + "tailwindcss/resolveConfig.js", + "tailwindcss/lib/lib/generateRules.js", + "tailwindcss/lib/lib/setupContextUtils.js", + "fs/promises", + "path", + "url" + ], plugins: [ nodeResolve({ preferBuiltins: true }), commonjs(), @@ -32,7 +41,16 @@ export default [ format: "cjs", sourcemap: true, }, - external: ["tailwindcss", "tailwindcss/loadConfig", "tailwindcss/resolveConfig", "fs/promises", "path", "url"], + external: [ + "tailwindcss", + "tailwindcss/loadConfig.js", + "tailwindcss/resolveConfig.js", + "tailwindcss/lib/lib/generateRules.js", + "tailwindcss/lib/lib/setupContextUtils.js", + "fs/promises", + "path", + "url" + ], plugins: [ nodeResolve({ preferBuiltins: true }), commonjs(), diff --git a/javascript/packages/tailwind-class-sorter/scripts/setup-fixtures.sh b/javascript/packages/tailwind-class-sorter/scripts/setup-fixtures.sh new file mode 100755 index 000000000..e1c109be7 --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/scripts/setup-fixtures.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +echo "Setting up test fixtures..." + +if [ -d "test/fixtures/project-v3" ]; then + echo "Installing dependencies for project-v3..." + cd test/fixtures/project-v3 + yarn install --frozen-lockfile --non-interactive + cd ../../.. +fi + +if [ -d "test/fixtures/project-v4" ]; then + echo "Installing dependencies for project-v4..." + cd test/fixtures/project-v4 + yarn install --frozen-lockfile --non-interactive + cd ../../.. +fi + +echo "Fixture setup complete!" diff --git a/javascript/packages/tailwind-class-sorter/src/config.ts b/javascript/packages/tailwind-class-sorter/src/config.ts index 722e2a860..e5a8190c1 100644 --- a/javascript/packages/tailwind-class-sorter/src/config.ts +++ b/javascript/packages/tailwind-class-sorter/src/config.ts @@ -8,12 +8,6 @@ import { createJiti, type Jiti } from 'jiti' import postcss from 'postcss' // @ts-ignore import postcssImport from 'postcss-import' -// @ts-ignore -import { generateRules as generateRulesFallback } from 'tailwindcss/lib/lib/generateRules' -// @ts-ignore -import { createContext as createContextFallback } from 'tailwindcss/lib/lib/setupContextUtils' -import loadConfigFallback from 'tailwindcss/loadConfig' -import resolveConfigFallback from 'tailwindcss/resolveConfig' import type { RequiredConfig } from 'tailwindcss/types/config.js' import { expiringMap } from './expiring-map.js' import { resolveCssFrom, resolveJsFrom } from './resolve' @@ -84,22 +78,53 @@ async function loadTailwindConfig( tailwindConfigPath: string | null, entryPoint: string | null, ): Promise { - let createContext = createContextFallback - let generateRules = generateRulesFallback - let resolveConfig = resolveConfigFallback - let loadConfig = loadConfigFallback + let createContext: any + let generateRules: any + let resolveConfig: any + let loadConfig: any let tailwindConfig: RequiredConfig = { content: [] } try { - let pkgFile = resolveJsFrom(baseDir, `${pkgName}/package.json`) - let pkgDir = path.dirname(pkgFile) + let pkgPath = resolveJsFrom(baseDir, pkgName) + let pkgJsonPath: string + + try { + const Module = require('module') + const requireFromBase = Module.createRequire(path.join(baseDir, 'index.js')) + + pkgJsonPath = requireFromBase.resolve(`${pkgName}/package.json`) + } catch { + // Fallback: assume pkgPath is in a subdirectory of the package + // Let's walk up until we find package.json + let currentDir = path.dirname(pkgPath) + + while (currentDir !== path.dirname(currentDir)) { + const candidatePkgJson = path.join(currentDir, 'package.json') + + try { + require('fs').accessSync(candidatePkgJson) + pkgJsonPath = candidatePkgJson + break + } catch {} + + currentDir = path.dirname(currentDir) + } + + if (!pkgJsonPath!) { + throw new Error('Could not find Tailwind CSS package.json') + } + } + + let pkgDir = path.dirname(pkgJsonPath) try { let v4 = await loadV4(baseDir, pkgDir, pkgName, entryPoint) if (v4) { return v4 } - } catch {} + } catch (err) { + // V4 loading failed, will try v3 below + } resolveConfig = require(path.join(pkgDir, 'resolveConfig')) createContext = require( @@ -111,7 +136,9 @@ async function loadTailwindConfig( // Prior to `tailwindcss@3.3.0` this won't exist so we load it last loadConfig = require(path.join(pkgDir, 'loadConfig')) - } catch {} + } catch (err: any) { + // Tailwind isn't installed or loading failed, will use defaults + } if (tailwindConfigPath) { try { @@ -123,10 +150,15 @@ async function loadTailwindConfig( } } - // suppress "empty content" warning + if (!resolveConfig || !createContext || !generateRules) { + return { + context: null, + generateRules: null, + } + } + tailwindConfig.content = ['no-op'] - // Create the context let context = createContext(resolveConfig(tailwindConfig)) return { @@ -340,5 +372,29 @@ function getEntryPoint(options: SortTailwindClassesOptions, baseDir: string): st return path.resolve(baseDir, options.tailwindConfig) } + try { + const commonPaths = [ + 'app/assets/tailwind/application.css', + 'app/assets/stylesheets/application.tailwind.css', + 'app/assets/stylesheets/application.css', + 'src/styles/tailwind.css', + 'src/tailwind.css', + 'styles/tailwind.css', + 'tailwind.css', + 'app.css', + 'src/app.css', + ] + + for (const cssPath of commonPaths) { + const fullPath = path.resolve(baseDir, cssPath) + try { + require('fs').accessSync(fullPath, require('fs').constants.R_OK) + return fullPath + } catch { + // File doesn't exist or isn't readable, continue to next path + } + } + } catch {} + return null } diff --git a/javascript/packages/tailwind-class-sorter/src/sorting.ts b/javascript/packages/tailwind-class-sorter/src/sorting.ts index 30802b6c8..ec05204fa 100644 --- a/javascript/packages/tailwind-class-sorter/src/sorting.ts +++ b/javascript/packages/tailwind-class-sorter/src/sorting.ts @@ -17,6 +17,10 @@ function getClassOrderPolyfill( classes: string[], { env }: { env: SortEnv }, ): [string, bigint | null][] { + if (!env.context || !env.generateRules) { + return classes.map(name => [name, null] as [string, bigint | null]) + } + // A list of utilities that are used by certain Tailwind CSS utilities but // that don't exist on their own. This will result in them "not existing" and // sorting could be weird since you still require them in order to make the @@ -48,6 +52,10 @@ function getClassOrderPolyfill( } function reorderClasses(classList: string[], { env }: { env: SortEnv }) { + if (!env.context) { + return classList.map(name => [name, null] as [string, bigint | null]) + } + let orderedClasses = env.context.getClassOrder ? env.context.getClassOrder(classList) : getClassOrderPolyfill(classList, { env }) diff --git a/javascript/packages/tailwind-class-sorter/src/types.ts b/javascript/packages/tailwind-class-sorter/src/types.ts index df7b803c5..5e92ca45d 100644 --- a/javascript/packages/tailwind-class-sorter/src/types.ts +++ b/javascript/packages/tailwind-class-sorter/src/types.ts @@ -38,18 +38,18 @@ export interface TailwindContext { } export interface SortEnv { - context: TailwindContext - generateRules: ( + context: TailwindContext | null + generateRules: (( classes: Iterable, context: TailwindContext, - ) => [bigint][] + ) => [bigint][]) | null options: SortTailwindClassesOptions } export interface ContextContainer { - context: any - generateRules: ( + context: any | null + generateRules: (( classes: Iterable, context: TailwindContext, - ) => [bigint][] + ) => [bigint][]) | null } diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/package.json b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/package.json new file mode 100644 index 000000000..ad1823fe8 --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/package.json @@ -0,0 +1,7 @@ +{ + "name": "test-fixture-v3", + "private": true, + "dependencies": { + "tailwindcss": "^3.4.0" + } +} diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/tailwind.config.js b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/tailwind.config.js new file mode 100644 index 000000000..e3e21a01c --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/tailwind.config.js @@ -0,0 +1,18 @@ +module.exports = { + content: [], + theme: { + extend: { + colors: { + 'brand-primary': '#3b82f6', + 'brand-secondary': '#10b981', + 'neon-pink': '#ff1493', + 'accent': { + 50: '#f0f9ff', + 100: '#e0f2fe', + 500: '#06b6d4', + 900: '#0c4a6e', + } + } + } + } +} diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/yarn.lock b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/yarn.lock new file mode 100644 index 000000000..fd08fbe62 --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v3/yarn.lock @@ -0,0 +1,719 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.21.7: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + +lilconfig@^3.1.1, lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pirates@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.1.0.tgz#003b63c6edde948766e40f3daf7e997ae43a5ce6" + integrity sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw== + dependencies: + camelcase-css "^2.0.1" + +"postcss-load-config@^4.0.2 || ^5.0 || ^6.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" + integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== + dependencies: + lilconfig "^3.1.1" + +postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + dependencies: + postcss-selector-parser "^6.1.1" + +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.47: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +resolve@^1.1.7, resolve@^1.22.8: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== + dependencies: + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + dependencies: + ansi-regex "^6.0.1" + +sucrase@^3.35.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tailwindcss@^3.4.0: + version "3.4.18" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.18.tgz#9fa9650aace186644b608242f1e57d2d55593301" + integrity sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.6.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.2" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.7" + lilconfig "^3.1.3" + micromatch "^4.0.8" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.1.1" + postcss "^8.4.47" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.2 || ^5.0 || ^6.0" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/v4-theme.css b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/app/assets/stylesheets/application.tailwind.css similarity index 100% rename from javascript/packages/tailwind-class-sorter/test/fixtures/v4-theme.css rename to javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/app/assets/stylesheets/application.tailwind.css diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/package.json b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/package.json new file mode 100644 index 000000000..3ea125d86 --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/package.json @@ -0,0 +1,7 @@ +{ + "name": "test-fixture-v4", + "private": true, + "dependencies": { + "tailwindcss": "^4.1.0" + } +} diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/yarn.lock b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/yarn.lock new file mode 100644 index 000000000..f32973fa3 --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/test/fixtures/project-v4/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +tailwindcss@^4.1.0: + version "4.1.17" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.17.tgz#e6dcb7a9c60cef7522169b5f207ffec2fd652286" + integrity sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q== diff --git a/javascript/packages/tailwind-class-sorter/test/fixtures/tailwind.config.js b/javascript/packages/tailwind-class-sorter/test/fixtures/tailwind.config.js index 41b7d8147..091060409 100644 --- a/javascript/packages/tailwind-class-sorter/test/fixtures/tailwind.config.js +++ b/javascript/packages/tailwind-class-sorter/test/fixtures/tailwind.config.js @@ -1,4 +1,3 @@ -/** @type {import('tailwindcss').Config} */ module.exports = { content: [], theme: { @@ -6,11 +5,12 @@ module.exports = { colors: { 'custom-primary': '#3b82f6', 'custom-secondary': '#10b981', - 'brand': { - 'light': '#dbeafe', - 'DEFAULT': '#2563eb', - 'dark': '#1e40af', - }, + 'brand': '#2563eb', + 'brand-primary': '#3b82f6', + 'brand-secondary': '#10b981', + 'brand-dark': '#1e40af', + 'brand-light': '#60a5fa', + 'neon-pink': '#ff1493', 'accent': { 50: '#f0f9ff', 100: '#e0f2fe', @@ -26,7 +26,6 @@ module.exports = { 'custom-sm': '0.75rem', 'custom-xl': '1.5rem', } - }, - }, - plugins: [], + } + } } diff --git a/javascript/packages/tailwind-class-sorter/test/tailwind-v4.test.ts b/javascript/packages/tailwind-class-sorter/test/tailwind-v4.test.ts index 67aad0e19..5989e9615 100644 --- a/javascript/packages/tailwind-class-sorter/test/tailwind-v4.test.ts +++ b/javascript/packages/tailwind-class-sorter/test/tailwind-v4.test.ts @@ -2,111 +2,103 @@ import path from 'path' import { describe, it, expect } from 'vitest' import { sortTailwindClasses } from '../src/index' -const v4StylesheetPath = path.join(__dirname, 'fixtures', 'v4-theme.css') +const v4ProjectDir = path.join(__dirname, 'fixtures', 'project-v4') describe('sortTailwindClasses with TailwindCSS v4 stylesheet support', () => { - it('accepts tailwindStylesheet option (v4 CSS config)', async () => { + it('auto-detects v4 CSS config from baseDir', async () => { const input = 'px-4 bg-neon-pink text-white bg-blue-500 text-brand-primary' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) - expect(result).toBe('bg-neon-pink text-brand-primary bg-blue-500 px-4 text-white') + expect(result).toBe('bg-blue-500 bg-neon-pink px-4 text-brand-primary text-white') + }) + + it('sorts custom v4 colors with modifiers', async () => { + const input = 'hover:bg-brand-secondary text-white bg-brand-primary px-6' + const result = await sortTailwindClasses(input, { + baseDir: v4ProjectDir + }) + + expect(result).toBe('bg-brand-primary px-6 text-white hover:bg-brand-secondary') }) it('handles v4 stylesheet with standard classes', async () => { const input = 'bg-accent-900 text-accent-50 border-accent-100 bg-accent-500' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) - expect(result).toBe('bg-accent-900 text-accent-50 border-accent-100 bg-accent-500') + expect(result).toBe('border-accent-100 bg-accent-500 bg-accent-900 text-accent-50') }) - it('sorts with v4 stylesheet but fallback behavior', async () => { + it('sorts with v4 custom spacing', async () => { const input = 'p-88 m-18 p-4 m-2' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) - expect(result).toBe('p-88 m-18 m-2 p-4') + expect(result).toBe('m-2 m-18 p-4 p-88') }) it('handles typography with v4 stylesheet', async () => { const input = 'text-custom-xl text-sm text-custom-sm text-base font-display' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) expect(result).toBe('text-custom-xl text-custom-sm font-display text-base text-sm') }) - it('processes complex class combinations with v4 stylesheet option', async () => { + it('processes complex class combinations with v4 config', async () => { const input = 'flex items-center bg-neon-lime text-brand-secondary p-88 hover:bg-blue-600 rounded-lg shadow-md' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) - expect(result).toBe('bg-neon-lime text-brand-secondary p-88 flex items-center rounded-lg shadow-md hover:bg-blue-600') + expect(result).toBe('flex items-center rounded-lg bg-neon-lime p-88 text-brand-secondary shadow-md hover:bg-blue-600') }) - it('maintains color grouping with v4 stylesheet', async () => { + it('maintains color grouping with v4 custom colors', async () => { const input = 'bg-neon-pink text-neon-cyan border-neon-lime hover:bg-accent-500' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) - expect(result).toBe('bg-neon-pink text-neon-cyan border-neon-lime hover:bg-accent-500') + expect(result).toBe('border-neon-lime bg-neon-pink text-neon-cyan hover:bg-accent-500') }) - it('handles responsive classes with v4 stylesheet', async () => { + it('handles responsive classes with v4 custom breakpoints', async () => { const input = 'text-sm md:text-lg 3xl:text-custom-xl lg:text-xl' const result = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + baseDir: v4ProjectDir }) expect(result).toBe('3xl:text-custom-xl text-sm md:text-lg lg:text-xl') }) - it('handles missing v4 stylesheet gracefully', async () => { - const originalWarn = console.warn - console.warn = () => {} + it('compares v4 vs v3 config behavior', async () => { + const v3ProjectDir = path.join(__dirname, 'fixtures', 'project-v3') + const input = 'px-4 bg-neon-pink bg-blue-500 text-brand-primary' - const input = 'px-4 bg-neon-pink text-white' - const result = await sortTailwindClasses(input, { - tailwindStylesheet: './non-existent-v4-theme.css' + const resultWithV3 = await sortTailwindClasses(input, { + baseDir: v3ProjectDir }) - expect(result).toBe('bg-neon-pink px-4 text-white') - - console.warn = originalWarn - }) - - it('compares v4 stylesheet vs no config behavior', async () => { - const input = 'px-4 bg-neon-pink bg-blue-500 unknown-class' - - const resultWithoutConfig = await sortTailwindClasses(input) - const resultWithV4Config = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + const resultWithV4 = await sortTailwindClasses(input, { + baseDir: v4ProjectDir }) - expect(resultWithoutConfig).toBe('bg-neon-pink unknown-class bg-blue-500 px-4') - expect(resultWithV4Config).toBe('bg-neon-pink unknown-class bg-blue-500 px-4') + expect(resultWithV3).toBe('bg-blue-500 bg-neon-pink px-4 text-brand-primary') + expect(resultWithV4).toBe('bg-blue-500 bg-neon-pink px-4 text-brand-primary') }) - it('validates v4 stylesheet option is accepted', async () => { - const jsConfigPath = path.join(__dirname, 'fixtures', 'tailwind.config.js') - const input = 'bg-custom-primary px-4 text-white' - - const resultWithJSConfig = await sortTailwindClasses(input, { - tailwindConfig: jsConfigPath, - }) - - const resultWithV4Stylesheet = await sortTailwindClasses(input, { - tailwindStylesheet: v4StylesheetPath + it('handles missing config gracefully', async () => { + const input = 'px-4 bg-neon-pink text-white' + const result = await sortTailwindClasses(input, { + baseDir: '/tmp/nonexistent-project-dir' }) - expect(resultWithJSConfig).toBe('bg-custom-primary px-4 text-white') - expect(resultWithV4Stylesheet).toBe('bg-custom-primary px-4 text-white') + expect(result).toBe('px-4 bg-neon-pink text-white') }) }) diff --git a/javascript/packages/tailwind-class-sorter/test/tailwind-version-compatibility.test.ts b/javascript/packages/tailwind-class-sorter/test/tailwind-version-compatibility.test.ts new file mode 100644 index 000000000..ce7e93c20 --- /dev/null +++ b/javascript/packages/tailwind-class-sorter/test/tailwind-version-compatibility.test.ts @@ -0,0 +1,237 @@ +import path from 'path' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { sortTailwindClasses, TailwindClassSorter } from '../src/index' + +const v3ConfigPath = path.join(__dirname, 'fixtures', 'tailwind.config.js') +const v4StylesheetPath = path.join(__dirname, 'fixtures', 'v4-theme.css') + +describe('Tailwind v3 and v4 Compatibility', () => { + describe('Tailwind v3 Support (with JS config)', () => { + it('should work with Tailwind v3 config file', async () => { + const input = 'px-4 bg-blue-500 text-white rounded py-2' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('rounded bg-blue-500 px-4 py-2 text-white') + }) + + it('should handle custom colors from v3 config', async () => { + const input = 'bg-custom-primary text-custom-secondary px-4' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('bg-custom-primary px-4 text-custom-secondary') + }) + + it('should work with TailwindClassSorter for v3', async () => { + const sorter = await TailwindClassSorter.fromConfig({ + tailwindConfig: v3ConfigPath + }) + + const result = sorter.sortClasses('hover:bg-primary focus:outline-none px-4') + expect(result).toBe('hover:bg-primary px-4 focus:outline-none') + }) + + it('should handle multiple sorts with same v3 sorter instance', async () => { + const sorter = await TailwindClassSorter.fromConfig({ + tailwindConfig: v3ConfigPath + }) + + const result1 = sorter.sortClasses('px-4 bg-blue-500 text-white') + const result2 = sorter.sortClasses('grid gap-4 mt-8 grid-cols-3') + + expect(result1).toBe('bg-blue-500 px-4 text-white') + expect(result2).toBe('mt-8 grid grid-cols-3 gap-4') + }) + }) + + describe('Tailwind v4 Support (with CSS stylesheet)', () => { + it('should work with Tailwind v4 stylesheet', async () => { + const input = 'px-4 bg-neon-pink text-white rounded py-2' + const result = await sortTailwindClasses(input, { + tailwindStylesheet: v4StylesheetPath + }) + + expect(result).toBe('bg-neon-pink rounded px-4 py-2 text-white') + }) + + it('should handle v4 custom colors', async () => { + const input = 'bg-neon-pink text-brand-primary hover:bg-neon-lime' + const result = await sortTailwindClasses(input, { + tailwindStylesheet: v4StylesheetPath + }) + + expect(result).toBe('bg-neon-pink text-brand-primary hover:bg-neon-lime') + }) + + it('should work with TailwindClassSorter for v4', async () => { + const sorter = await TailwindClassSorter.fromConfig({ + tailwindStylesheet: v4StylesheetPath + }) + + const result = sorter.sortClasses('hover:bg-neon-lime focus:outline-none px-4') + expect(result).toBe('hover:bg-neon-lime px-4 focus:outline-none') + }) + + it('should handle multiple sorts with same v4 sorter instance', async () => { + const sorter = await TailwindClassSorter.fromConfig({ + tailwindStylesheet: v4StylesheetPath + }) + + const result1 = sorter.sortClasses('px-4 bg-neon-pink text-white') + const result2 = sorter.sortClasses('flex items-center gap-4 bg-accent-500') + + expect(result1).toBe('bg-neon-pink px-4 text-white') + expect(result2).toBe('bg-accent-500 flex items-center gap-4') + }) + }) + + describe('Fallback to default when no config', () => { + it('should work without any config (default Tailwind)', async () => { + const input = 'px-4 bg-blue-500 text-white rounded py-2' + const result = await sortTailwindClasses(input) + + expect(result).toBe('rounded bg-blue-500 px-4 py-2 text-white') + }) + + it('should handle standard Tailwind classes without config', async () => { + const input = 'flex items-center justify-between gap-4 p-4' + const result = await sortTailwindClasses(input) + + expect(result).toBe('flex items-center justify-between gap-4 p-4') + }) + + it('should work with TailwindClassSorter without config', async () => { + const sorter = await TailwindClassSorter.fromConfig() + + const result = sorter.sortClasses('hover:bg-blue-600 px-4 bg-blue-500') + expect(result).toBe('bg-blue-500 px-4 hover:bg-blue-600') + }) + }) + + describe('Edge cases and error handling', () => { + it('should handle empty class string', async () => { + const result = await sortTailwindClasses('', { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('') + }) + + it('should handle single class', async () => { + const result = await sortTailwindClasses('px-4', { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('px-4') + }) + + it('should handle unknown classes gracefully with v3', async () => { + const input = 'unknown-class px-4 another-unknown bg-blue-500' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath + }) + + expect(result).toContain('unknown-class') + expect(result).toContain('another-unknown') + expect(result).toContain('px-4') + expect(result).toContain('bg-blue-500') + }) + + it('should handle unknown classes gracefully with v4', async () => { + const input = 'unknown-class px-4 another-unknown bg-neon-pink' + const result = await sortTailwindClasses(input, { + tailwindStylesheet: v4StylesheetPath + }) + + expect(result).toBe('unknown-class another-unknown bg-neon-pink px-4') + }) + + it('should handle non-existent config file gracefully', async () => { + const originalWarn = console.warn + const warnings: string[] = [] + console.warn = (...args: any[]) => warnings.push(args.join(' ')) + + const input = 'px-4 bg-blue-500' + const result = await sortTailwindClasses(input, { + tailwindConfig: './non-existent.config.js' + }) + + expect(result).toBe('bg-blue-500 px-4') + + console.warn = originalWarn + }) + }) + + describe('Preserve options', () => { + it('should preserve duplicates when requested with v3', async () => { + const input = 'px-4 bg-blue-500 px-4 text-white' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath, + tailwindPreserveDuplicates: true + }) + + expect(result).toBe('bg-blue-500 px-4 px-4 text-white') + }) + + it('should remove duplicates by default with v3', async () => { + const input = 'px-4 bg-blue-500 px-4 text-white' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath, + tailwindPreserveDuplicates: false + }) + + expect(result).toBe('bg-blue-500 px-4 text-white') + }) + + it('should preserve whitespace when requested', async () => { + const input = 'px-4 bg-blue-500 text-white' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath, + tailwindPreserveWhitespace: true + }) + + expect(result).toBe('bg-blue-500 px-4 text-white') + }) + }) + + describe('Real-world scenarios', () => { + it('should handle complex component classes with v3', async () => { + const input = 'hover:shadow-lg transition-all duration-300 px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 active:scale-95 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('rounded-lg bg-blue-600 px-6 py-3 font-semibold text-white transition-all duration-300 hover:bg-blue-700 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 active:scale-95') + }) + + it('should handle complex component classes with v4', async () => { + const input = 'hover:shadow-lg transition-all duration-300 px-6 py-3 bg-neon-pink text-white rounded-lg font-semibold hover:bg-accent-500 active:scale-95' + const result = await sortTailwindClasses(input, { + tailwindStylesheet: v4StylesheetPath + }) + + expect(result).toBe('bg-neon-pink hover:bg-accent-500 rounded-lg px-6 py-3 font-semibold text-white transition-all duration-300 hover:shadow-lg active:scale-95') + }) + + it('should handle responsive design classes with v3', async () => { + const input = 'text-sm md:text-base lg:text-lg xl:text-xl 2xl:text-2xl px-4 md:px-6 lg:px-8' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('px-4 text-sm md:px-6 md:text-base lg:px-8 lg:text-lg xl:text-xl 2xl:text-2xl') + }) + + it('should handle grid and flexbox layouts', async () => { + const input = 'grid-cols-3 grid gap-4 md:grid-cols-4 lg:grid-cols-6 px-4 container mx-auto' + const result = await sortTailwindClasses(input, { + tailwindConfig: v3ConfigPath + }) + + expect(result).toBe('container mx-auto grid grid-cols-3 gap-4 px-4 md:grid-cols-4 lg:grid-cols-6') + }) + }) +})