mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
This PR improves the robustness when running the upgrade script. Right now when you run it and if you run into issues, it could be that an error with stack trace is printed in the terminal. This PR improves most of the cases where this happens to ensure the output is easier to parse as a human. # Test plan: Used SourceGraph to find some popular open source repositories that use Tailwind and tried to run the upgrade tool on those repositories. If a repository fails to upgrade, then that's a good candidate for this PR to showcase the improved error messages. github.com/docker/docs | Before | After | | --- | --- | | <img width="1455" alt="image" src="https://github.com/user-attachments/assets/ae28c1c1-8472-45a2-89f7-ed74a703e216"> | <img width="1455" alt="image" src="https://github.com/user-attachments/assets/6bf4ec79-ddfc-47c4-8ba0-051566cb0116"> | github.com/parcel-bundler/parcel | Before | After | | --- | --- | | <img width="1455" alt="image" src="https://github.com/user-attachments/assets/826e510f-df7a-4672-9895-8e13da1d03a8"> | <img width="1455" alt="image" src="https://github.com/user-attachments/assets/a75146f5-bfac-4c96-a02b-be00ef671f73"> | github.com/vercel/next.js | Before | After | | --- | --- | | <img width="1455" alt="image" src="https://github.com/user-attachments/assets/8d6c3744-f210-4164-b1ee-51950d44b349"> | <img width="1455" alt="image" src="https://github.com/user-attachments/assets/b2739a9a-9629-411d-a506-3993a5867caf"> |
134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import { __unstable__loadDesignSystem, compile } from '@tailwindcss/node'
|
|
import fs from 'node:fs/promises'
|
|
import path, { dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { loadModule } from '../../../@tailwindcss-node/src/compile'
|
|
import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config'
|
|
import type { Config } from '../../../tailwindcss/src/compat/plugin-api'
|
|
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
|
|
import { error, highlight, relative } from '../utils/renderer'
|
|
import { migratePrefix } from './codemods/prefix'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
|
|
const css = String.raw
|
|
|
|
export async function prepareConfig(
|
|
configFilePath: string | null,
|
|
options: { base: string },
|
|
): Promise<{
|
|
designSystem: DesignSystem
|
|
globs: { base: string; pattern: string }[]
|
|
userConfig: Config
|
|
configFilePath: string
|
|
|
|
newPrefix: string | null
|
|
}> {
|
|
try {
|
|
if (configFilePath === null) {
|
|
configFilePath = await detectConfigPath(options.base)
|
|
} else if (!path.isAbsolute(configFilePath)) {
|
|
configFilePath = path.resolve(options.base, configFilePath)
|
|
}
|
|
|
|
// We create a relative path from the current file to the config file. This is
|
|
// required so that the base for Tailwind CSS can bet inside the
|
|
// @tailwindcss-upgrade package and we can require `tailwindcss` properly.
|
|
let relative = path.relative(__dirname, configFilePath)
|
|
|
|
// If the path points to a file in the same directory, `path.relative` will
|
|
// remove the leading `./` and we need to add it back in order to still
|
|
// consider the path relative
|
|
if (!relative.startsWith('.')) {
|
|
relative = './' + relative
|
|
}
|
|
|
|
let userConfig = await createResolvedUserConfig(configFilePath)
|
|
|
|
let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null
|
|
let input = css`
|
|
@import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''};
|
|
@config './${relative}';
|
|
`
|
|
|
|
let [compiler, designSystem] = await Promise.all([
|
|
compile(input, { base: __dirname, onDependency: () => {} }),
|
|
__unstable__loadDesignSystem(input, { base: __dirname }),
|
|
])
|
|
|
|
return {
|
|
designSystem,
|
|
globs: compiler.globs,
|
|
userConfig,
|
|
newPrefix,
|
|
configFilePath,
|
|
}
|
|
} catch (e: any) {
|
|
error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' })
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
async function createResolvedUserConfig(fullConfigPath: string): Promise<Config> {
|
|
let [noopDesignSystem, unresolvedUserConfig] = await Promise.all([
|
|
__unstable__loadDesignSystem(
|
|
css`
|
|
@import 'tailwindcss';
|
|
`,
|
|
{ base: __dirname },
|
|
),
|
|
loadModule(fullConfigPath, __dirname, () => {}).then((result) => result.module) as Config,
|
|
])
|
|
|
|
return resolveConfig(noopDesignSystem, [
|
|
{ base: dirname(fullConfigPath), config: unresolvedUserConfig },
|
|
]).resolvedConfig as any
|
|
}
|
|
|
|
const DEFAULT_CONFIG_FILES = [
|
|
'./tailwind.config.js',
|
|
'./tailwind.config.cjs',
|
|
'./tailwind.config.mjs',
|
|
'./tailwind.config.ts',
|
|
'./tailwind.config.cts',
|
|
'./tailwind.config.mts',
|
|
]
|
|
export async function detectConfigPath(start: string, end: string = start) {
|
|
for (let base of parentPaths(start, end)) {
|
|
for (let file of DEFAULT_CONFIG_FILES) {
|
|
let fullPath = path.resolve(base, file)
|
|
try {
|
|
await fs.access(fullPath)
|
|
return fullPath
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
`No configuration file found for ${highlight(relative(start))}. Please provide a path to the Tailwind CSS v3 config file via the ${highlight('--config')} option.`,
|
|
)
|
|
}
|
|
|
|
// Yields all parent paths from `from` to `to` (inclusive on both ends)
|
|
function* parentPaths(from: string, to: string = from) {
|
|
let fromAbsolute = path.resolve(from)
|
|
let toAbsolute = path.resolve(to)
|
|
|
|
if (!fromAbsolute.startsWith(toAbsolute)) {
|
|
throw new Error(`The path ${from} is not a parent of ${to}`)
|
|
}
|
|
|
|
if (fromAbsolute === toAbsolute) {
|
|
yield fromAbsolute
|
|
return
|
|
}
|
|
|
|
let current = fromAbsolute
|
|
do {
|
|
yield current
|
|
current = path.dirname(current)
|
|
} while (current !== toAbsolute)
|
|
yield toAbsolute
|
|
}
|