Files
supabase/scripts/tw-rename-utilities.ts
Ivan Vasilov 56de26fe22 chore: Migrate the monorepo to use Tailwind v4 (#45318)
This PR migrates the whole monorepo to use Tailwind v4:
- Removed `@tailwindcss/container-queries` plugin since it's included by
default in v4,
- Bump all instances of Tailwind to v4. Made minimal changes to the
shared config to remove non-supported features (`alpha` mentions),
- Migrate all apps to be compatible with v4 configs,
- Fix the `typography.css` import in 3 apps,
- Add missing rules which were included by default in v3,
- Run `pnpm dlx @tailwindcss/upgrade` on all apps, which renames a lot
of classes
- Rename all misnamed classes according to
https://tailwindcss.com/docs/upgrade-guide#renamed-utilities in all
apps.

---------

Co-authored-by: Jordi Enric <jordi.err@gmail.com>
2026-04-30 10:53:24 +00:00

132 lines
3.6 KiB
TypeScript

/**
* Replace renamed Tailwind CSS v3 utilities with their v4 equivalents.
* https://tailwindcss.com/docs/upgrade-guide#renamed-utilities
*
* Usage:
* npx tsx scripts/tw-rename-utilities.ts [--dry-run] [paths...]
* node --experimental-strip-types scripts/tw-rename-utilities.ts [--dry-run] [paths...]
*/
import * as fs from 'node:fs'
import * as path from 'node:path'
const EXTENSIONS = new Set([
'.tsx',
'.ts',
'.jsx',
'.js',
'.html',
'.css',
'.scss',
'.mdx',
'.md',
'.vue',
'.svelte',
])
const SKIP_DIRS = new Set(['node_modules', '.git', '.next', 'dist', 'build'])
// Word-boundary helpers:
// _B — not preceded by [-a-zA-Z0-9_] (prevents matching mid-class-name)
// _E — not followed by [-a-zA-Z0-9_(] (prevents shadow-md, blur(...), etc.)
const B = '(?<![a-zA-Z0-9_\\-])'
const E = '(?![a-zA-Z0-9_\\-(])'
// Ordering matters: -sm → -xs must run before bare → -sm to avoid double-replacement.
const RENAMES: [RegExp, string][] = [
// -sm → -xs
[new RegExp(`${B}shadow-sm${E}`, 'g'), 'shadow-xs'],
[new RegExp(`${B}drop-shadow-sm${E}`, 'g'), 'drop-shadow-xs'],
[new RegExp(`${B}blur-sm${E}`, 'g'), 'blur-xs'],
[new RegExp(`${B}backdrop-blur-sm${E}`, 'g'), 'backdrop-blur-xs'],
[new RegExp(`${B}rounded-sm${E}`, 'g'), 'rounded-xs'],
// bare → -sm
[new RegExp(`${B}shadow${E}`, 'g'), 'shadow-sm'],
[new RegExp(`${B}drop-shadow${E}`, 'g'), 'drop-shadow-sm'],
[new RegExp(`${B}blur${E}`, 'g'), 'blur-sm'],
[new RegExp(`${B}backdrop-blur${E}`, 'g'), 'backdrop-blur-sm'],
[new RegExp(`${B}rounded${E}`, 'g'), 'rounded-sm'],
// simple renames
[new RegExp(`${B}outline-none${E}`, 'g'), 'outline-hidden'],
[new RegExp(`${B}ring${E}`, 'g'), 'ring-3'],
]
function processFile(filePath: string, dryRun: boolean): number {
let text: string
try {
text = fs.readFileSync(filePath, 'utf8')
} catch {
return 0
}
let total = 0
for (const [pattern, replacement] of RENAMES) {
const next = text.replace(pattern, () => {
total++
return replacement
})
text = next
}
if (total === 0) return 0
if (!dryRun) fs.writeFileSync(filePath, text, 'utf8')
const label = dryRun ? '[dry-run] ' : ''
const s = total === 1 ? '' : 's'
console.log(`${label}${filePath} (${total} replacement${s})`)
return total
}
function* walkDir(dir: string): Generator<string> {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (SKIP_DIRS.has(entry.name)) continue
const full = path.join(dir, entry.name)
if (entry.isDirectory()) {
yield* walkDir(full)
} else if (EXTENSIONS.has(path.extname(entry.name))) {
yield full
}
}
}
function collectFiles(roots: string[]): string[] {
const files: string[] = []
for (const root of roots) {
const stat = fs.statSync(root, { throwIfNoEntry: false })
if (!stat) {
console.warn(`Warning: path not found: ${root}`)
continue
}
if (stat.isFile()) {
files.push(root)
} else if (stat.isDirectory()) {
files.push(...walkDir(root))
}
}
return files
}
function main() {
const args = process.argv.slice(2)
const dryRun = args.includes('--dry-run') || args.includes('-n')
const paths = args.filter((a) => !a.startsWith('-'))
const roots = paths.length > 0 ? paths : ['.']
const files = collectFiles(roots)
let totalFiles = 0
let totalReplacements = 0
for (const file of files) {
const n = processFile(file, dryRun)
if (n > 0) {
totalFiles++
totalReplacements += n
}
}
const label = dryRun ? '[dry-run] ' : ''
console.log(`\n${label}Done — ${totalReplacements} replacement(s) across ${totalFiles} file(s).`)
}
main()