With the changes in #14672, it now becomes trivial to actually resolve the config (while still retaining the reset behavior). This means that we can now convert JS configs that use _functions_, e.g.: ```ts import { type Config } from 'tailwindcss' export default { theme: { extend: { colors: ({ colors }) => ({ gray: colors.neutral, }), }, }, } satisfies Config ``` This becomes: ```css @import 'tailwindcss'; @theme { --color-gray-50: #fafafa; --color-gray-100: #f5f5f5; --color-gray-200: #e5e5e5; --color-gray-300: #d4d4d4; --color-gray-400: #a3a3a3; --color-gray-500: #737373; --color-gray-600: #525252; --color-gray-700: #404040; --color-gray-800: #262626; --color-gray-900: #171717; --color-gray-950: #0a0a0a; } ``` --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
195 lines
5.1 KiB
JavaScript
195 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { globby } from 'globby'
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import postcss from 'postcss'
|
|
import { formatNodes } from './codemods/format-nodes'
|
|
import { help } from './commands/help'
|
|
import {
|
|
analyze as analyzeStylesheets,
|
|
migrate as migrateStylesheet,
|
|
split as splitStylesheets,
|
|
} from './migrate'
|
|
import { migrateJsConfig } from './migrate-js-config'
|
|
import { migratePostCSSConfig } from './migrate-postcss'
|
|
import { Stylesheet } from './stylesheet'
|
|
import { migrate as migrateTemplate } from './template/migrate'
|
|
import { prepareConfig } from './template/prepare-config'
|
|
import { args, type Arg } from './utils/args'
|
|
import { isRepoDirty } from './utils/git'
|
|
import { pkg } from './utils/packages'
|
|
import { eprintln, error, header, highlight, info, success } from './utils/renderer'
|
|
|
|
const options = {
|
|
'--config': { type: 'string', description: 'Path to the configuration file', alias: '-c' },
|
|
'--help': { type: 'boolean', description: 'Display usage information', alias: '-h' },
|
|
'--force': { type: 'boolean', description: 'Force the migration', alias: '-f' },
|
|
'--version': { type: 'boolean', description: 'Display the version number', alias: '-v' },
|
|
} satisfies Arg
|
|
const flags = args(options)
|
|
|
|
if (flags['--help']) {
|
|
help({
|
|
usage: ['npx @tailwindcss/upgrade'],
|
|
options,
|
|
})
|
|
process.exit(0)
|
|
}
|
|
|
|
async function run() {
|
|
let base = process.cwd()
|
|
|
|
eprintln(header())
|
|
eprintln()
|
|
|
|
if (!flags['--force']) {
|
|
if (isRepoDirty()) {
|
|
error('Git directory is not clean. Please stash or commit your changes before migrating.')
|
|
info(
|
|
`You may use the ${highlight('--force')} flag to silence this warning and perform the migration.`,
|
|
)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
let config = await prepareConfig(flags['--config'], { base })
|
|
|
|
{
|
|
// Template migrations
|
|
|
|
info('Migrating templates using the provided configuration file.')
|
|
|
|
let set = new Set<string>()
|
|
for (let { pattern, base } of config.globs) {
|
|
let files = await globby([pattern], {
|
|
absolute: true,
|
|
gitignore: true,
|
|
cwd: 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('Template migration complete.')
|
|
}
|
|
|
|
// Migrate JS config
|
|
|
|
info('Migrating JavaScript configuration files using the provided configuration file.')
|
|
let jsConfigMigration = await migrateJsConfig(config.designSystem, config.configFilePath, base)
|
|
|
|
{
|
|
// Stylesheet migrations
|
|
|
|
// Use provided files
|
|
let files = flags._.map((file) => path.resolve(base, file))
|
|
|
|
// Discover CSS files in case no files were provided
|
|
if (files.length === 0) {
|
|
info(
|
|
'No input stylesheets provided. Searching for CSS files in the current directory and its subdirectories…',
|
|
)
|
|
|
|
files = await globby(['**/*.css'], {
|
|
absolute: true,
|
|
gitignore: true,
|
|
})
|
|
}
|
|
|
|
// Ensure we are only dealing with CSS files
|
|
files = files.filter((file) => file.endsWith('.css'))
|
|
|
|
// Analyze the stylesheets
|
|
let loadResults = await Promise.allSettled(files.map((filepath) => Stylesheet.load(filepath)))
|
|
|
|
// Load and parse all stylesheets
|
|
for (let result of loadResults) {
|
|
if (result.status === 'rejected') {
|
|
error(`${result.reason}`)
|
|
}
|
|
}
|
|
|
|
let stylesheets = loadResults
|
|
.filter((result) => result.status === 'fulfilled')
|
|
.map((result) => result.value)
|
|
|
|
// Analyze the stylesheets
|
|
try {
|
|
await analyzeStylesheets(stylesheets)
|
|
} catch (e: unknown) {
|
|
error(`${e}`)
|
|
}
|
|
|
|
// Migrate each file
|
|
let migrateResults = await Promise.allSettled(
|
|
stylesheets.map((sheet) => migrateStylesheet(sheet, { ...config, jsConfigMigration })),
|
|
)
|
|
|
|
for (let result of migrateResults) {
|
|
if (result.status === 'rejected') {
|
|
error(`${result.reason}`)
|
|
}
|
|
}
|
|
|
|
// Split up stylesheets (as needed)
|
|
try {
|
|
await splitStylesheets(stylesheets)
|
|
} catch (e: unknown) {
|
|
error(`${e}`)
|
|
}
|
|
|
|
// Format nodes
|
|
for (let sheet of stylesheets) {
|
|
await postcss([formatNodes()]).process(sheet.root!, { from: sheet.file! })
|
|
}
|
|
|
|
// Write all files to disk
|
|
for (let sheet of stylesheets) {
|
|
if (!sheet.file) continue
|
|
|
|
await fs.writeFile(sheet.file, sheet.root.toString())
|
|
}
|
|
|
|
success('Stylesheet migration complete.')
|
|
}
|
|
|
|
{
|
|
// PostCSS config migration
|
|
await migratePostCSSConfig(base)
|
|
}
|
|
|
|
try {
|
|
// Upgrade Tailwind CSS
|
|
await pkg('add tailwindcss@next', base)
|
|
} catch {}
|
|
|
|
// Remove the JS config if it was fully migrated
|
|
if (jsConfigMigration !== null) {
|
|
await fs.rm(config.configFilePath)
|
|
}
|
|
|
|
// Figure out if we made any changes
|
|
if (isRepoDirty()) {
|
|
success('Verify the changes and commit them to your repository.')
|
|
} else {
|
|
success('No changes were made to your repository.')
|
|
}
|
|
}
|
|
|
|
run()
|
|
.then(() => process.exit(0))
|
|
.catch((err) => {
|
|
console.error(err)
|
|
process.exit(1)
|
|
})
|