Skip to content

Commit

Permalink
Improve robustness when upgrading (#15038)
Browse files Browse the repository at this point in the history
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">
|
  • Loading branch information
RobinMalfait authored Nov 19, 2024
1 parent 08c6c96 commit 93f9c99
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 69 deletions.
72 changes: 39 additions & 33 deletions packages/@tailwindcss-upgrade/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async function run() {
// Load and parse all stylesheets
for (let result of loadResults) {
if (result.status === 'rejected') {
error(`${result.reason}`)
error(`${result.reason?.message ?? result.reason}`, { prefix: '↳ ' })
}
}

Expand All @@ -95,8 +95,8 @@ async function run() {
// Analyze the stylesheets
try {
await analyzeStylesheets(stylesheets)
} catch (e: unknown) {
error(`${e}`)
} catch (e: any) {
error(`${e?.message ?? e}`, { prefix: '↳ ' })
}

// Ensure stylesheets are linked to configs
Expand All @@ -105,12 +105,14 @@ async function run() {
configPath: flags['--config'],
base,
})
} catch (e: unknown) {
error(`${e}`)
} catch (e: any) {
error(`${e?.message ?? e}`, { prefix: '↳ ' })
}

// Migrate js config files, linked to stylesheets
info('Migrating JavaScript configuration files…')
if (stylesheets.some((sheet) => sheet.isTailwindRoot)) {
info('Migrating JavaScript configuration files…')
}
let configBySheet = new Map<Stylesheet, Awaited<ReturnType<typeof prepareConfig>>>()
let jsConfigMigrationBySheet = new Map<
Stylesheet,
Expand All @@ -136,13 +138,16 @@ async function run() {

if (jsConfigMigration !== null) {
success(
`↳ Migrated configuration file: ${highlight(relative(config.configFilePath, base))}`,
`Migrated configuration file: ${highlight(relative(config.configFilePath, base))}`,
{ prefix: '↳ ' },
)
}
}

// Migrate source files, linked to config files
info('Migrating templates…')
if (configBySheet.size > 0) {
info('Migrating templates…')
}
{
// Template migrations
for (let config of configBySheet.values()) {
Expand All @@ -168,43 +173,44 @@ async function run() {
)

success(
`↳ Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`,
`Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`,
{ prefix: '↳ ' },
)
}
}

// Migrate each CSS file
info('Migrating stylesheets…')
let migrateResults = await Promise.allSettled(
stylesheets.map((sheet) => {
let config = configBySheet.get(sheet)!
let jsConfigMigration = jsConfigMigrationBySheet.get(sheet)!

if (!config) {
for (let parent of sheet.ancestors()) {
if (parent.isTailwindRoot) {
config ??= configBySheet.get(parent)!
jsConfigMigration ??= jsConfigMigrationBySheet.get(parent)!
break
if (stylesheets.length > 0) {
info('Migrating stylesheets…')
}
await Promise.all(
stylesheets.map(async (sheet) => {
try {
let config = configBySheet.get(sheet)!
let jsConfigMigration = jsConfigMigrationBySheet.get(sheet)!

if (!config) {
for (let parent of sheet.ancestors()) {
if (parent.isTailwindRoot) {
config ??= configBySheet.get(parent)!
jsConfigMigration ??= jsConfigMigrationBySheet.get(parent)!
break
}
}
}
}

return migrateStylesheet(sheet, { ...config, jsConfigMigration })
await migrateStylesheet(sheet, { ...config, jsConfigMigration })
} catch (e: any) {
error(`${e?.message ?? e} in ${highlight(relative(sheet.file!, base))}`, { prefix: '↳ ' })
}
}),
)

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}`)
} catch (e: any) {
error(`${e?.message ?? e}`, { prefix: '↳ ' })
}

// Cleanup `@import "…" layer(utilities)`
Expand Down Expand Up @@ -241,7 +247,7 @@ async function run() {
await fs.writeFile(sheet.file, sheet.root.toString())

if (sheet.isTailwindRoot) {
success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`)
success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`, { prefix: '↳ ' })
}
}
}
Expand All @@ -260,7 +266,7 @@ async function run() {
try {
// Upgrade Tailwind CSS
await pkg(base).add(['tailwindcss@next'])
success(`Updated package: ${highlight('tailwindcss')}`)
success(`Updated package: ${highlight('tailwindcss')}`, { prefix: '↳ ' })
} catch {}

// Run all cleanup functions because we completed the migration
Expand Down
3 changes: 2 additions & 1 deletion packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export async function migrateJsConfig(

if (!canMigrateConfig(unresolvedConfig, source)) {
info(
`↳ The configuration file at ${highlight(relative(fullConfigPath, base))} could not be automatically migrated to the new CSS configuration format, so your CSS has been updated to load your existing configuration file.`,
`The configuration file at ${highlight(relative(fullConfigPath, base))} could not be automatically migrated to the new CSS configuration format, so your CSS has been updated to load your existing configuration file.`,
{ prefix: '↳ ' },
)
return null
}
Expand Down
8 changes: 5 additions & 3 deletions packages/@tailwindcss-upgrade/src/migrate-postcss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export async function migratePostCSSConfig(base: string) {
if (location !== null) {
try {
await pkg(base).add(['@tailwindcss/postcss@next'], location)
success(`Installed package: ${highlight('@tailwindcss/postcss')}`)
success(`Installed package: ${highlight('@tailwindcss/postcss')}`, { prefix: '↳ ' })
} catch {}
}
}
Expand All @@ -104,13 +104,15 @@ export async function migratePostCSSConfig(base: string) {
].filter(Boolean) as string[]
await pkg(base).remove(packagesToRemove)
for (let pkg of packagesToRemove) {
success(`Removed package: ${highlight(pkg)}`)
success(`Removed package: ${highlight(pkg)}`, { prefix: '↳ ' })
}
} catch {}
}

if (didMigrate && jsConfigPath) {
success(`↳ Migrated PostCSS configuration: ${highlight(relative(jsConfigPath, base))}`)
success(`Migrated PostCSS configuration: ${highlight(relative(jsConfigPath, base))}`, {
prefix: '↳ ',
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@tailwindcss-upgrade/src/migrate-prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function migratePrettierPlugin(base: string) {
let packageJson = await fs.readFile(packageJsonPath, 'utf-8')
if (packageJson.includes('prettier-plugin-tailwindcss')) {
await pkg(base).add(['prettier-plugin-tailwindcss@latest'])
success(`Updated package: ${highlight('prettier-plugin-tailwindcss')}`)
success(`Updated package: ${highlight('prettier-plugin-tailwindcss')}`, { prefix: '↳ ' })
}
} catch {}
}
22 changes: 14 additions & 8 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ export async function analyze(stylesheets: Stylesheet[]) {
resolvedPath = resolveCssId(id, basePath)
}
} catch (err) {
console.warn(`Failed to resolve import: ${id}. Skipping.`)
console.error(err)
error(
`Failed to resolve import: ${highlight(id)} in ${highlight(relative(node.source?.input.file!, basePath))}. Skipping.`,
{ prefix: '↳ ' },
)
return
}

Expand Down Expand Up @@ -334,10 +336,12 @@ export async function analyze(stylesheets: Stylesheet[]) {
}
}

let error = `You have one or more stylesheets that are imported into a utility layer and non-utility layer.\n`
error += `We cannot convert stylesheets under these conditions. Please look at the following stylesheets:\n`
{
let error = `You have one or more stylesheets that are imported into a utility layer and non-utility layer.\n`
error += `We cannot convert stylesheets under these conditions. Please look at the following stylesheets:\n`

throw new Error(error + lines.join('\n'))
throw new Error(error + lines.join('\n'))
}
}

export async function linkConfigs(
Expand All @@ -347,7 +351,7 @@ export async function linkConfigs(
let rootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot)
if (rootStylesheets.length === 0) {
throw new Error(
'Cannot find any CSS files that reference Tailwind CSS.\nBefore your project can be upgraded you need to create a CSS file that imports Tailwind CSS or uses `@tailwind`.',
`Cannot find any CSS files that reference Tailwind CSS.\nBefore your project can be upgraded you need to create a CSS file that imports Tailwind CSS or uses ${highlight('@tailwind')}.`,
)
}
let withoutAtConfig = rootStylesheets.filter((sheet) => {
Expand Down Expand Up @@ -396,6 +400,7 @@ export async function linkConfigs(
for (let sheet of problematicStylesheets) {
error(
`Could not determine configuration file for: ${highlight(relative(sheet.file!, base))}\nUpdate your stylesheet to use ${highlight('@config')} to specify the correct configuration file explicitly and then run the upgrade tool again.`,
{ prefix: '↳ ' },
)
}

Expand All @@ -407,7 +412,8 @@ export async function linkConfigs(
try {
if (!sheet || !sheet.file) return
success(
`↳ Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`,
`Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`,
{ prefix: '↳ ' },
)

// Link the `@config` directive to the root stylesheets
Expand Down Expand Up @@ -447,7 +453,7 @@ export async function linkConfigs(
}
}
} catch (e: any) {
error('Could not load the configuration file: ' + e.message)
error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' })
process.exit(1)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function prepareConfig(
configFilePath,
}
} catch (e: any) {
error('Could not load the configuration file: ' + e.message)
error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' })
process.exit(1)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ export function findStaticPlugins(source: string): [string, null | StaticPluginO
}
}
return plugins
} catch (error) {
console.error(error)
} catch (error: any) {
error(`${error?.message ?? error}`, { prefix: '↳ ' })
return null
}
}
Expand Down
40 changes: 22 additions & 18 deletions packages/@tailwindcss-upgrade/src/utils/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,32 +89,36 @@ export function indent(value: string, offset = 0) {
return `${' '.repeat(offset + UI.indent)}${value}`
}

export function success(message: string, print = eprintln) {
wordWrap(message, process.stderr.columns - 3).map((line) => {
return print(`${pc.green('\u2502')} ${line}`)
function log(message: string, { art = pc.gray('\u2502'), prefix = '', print = eprintln }) {
let prefixLength = prefix.length
let padding = ' '
let paddingLength = padding.length
let artLength = stripVTControlCharacters(art).length
let availableWidth = process.stderr.columns
let totalWidth = availableWidth - prefixLength - paddingLength * 2 - artLength

wordWrap(message, totalWidth).map((line, idx) => {
return print(
`${art}${padding}${idx === 0 ? prefix : ' '.repeat(prefixLength)}${line}${padding}`,
)
})
print()
}

export function info(message: string, print = eprintln) {
wordWrap(message, process.stderr.columns - 3).map((line) => {
return print(`${pc.blue('\u2502')} ${line}`)
})
print()
export function success(message: string, { prefix = '', print = eprintln } = {}) {
log(message, { art: pc.green('\u2502'), prefix, print })
}

export function error(message: string, print = eprintln) {
wordWrap(message, process.stderr.columns - 3).map((line) => {
return print(`${pc.red('\u2502')} ${line}`)
})
print()
export function info(message: string, { prefix = '', print = eprintln } = {}) {
log(message, { art: pc.blue('\u2502'), prefix, print })
}

export function warn(message: string, print = eprintln) {
wordWrap(message, process.stderr.columns - 3).map((line) => {
return print(`${pc.yellow('\u2502')} ${line}`)
})
print()
export function error(message: string, { prefix = '', print = eprintln } = {}) {
log(message, { art: pc.red('\u2502'), prefix, print })
}

export function warn(message: string, { prefix = '', print = eprintln } = {}) {
log(message, { art: pc.yellow('\u2502'), prefix, print })
}

// Rust inspired functions to print to the console:
Expand Down
4 changes: 2 additions & 2 deletions packages/tailwindcss/src/at-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function parseImportParams(params: ValueParser.ValueAstNode[]) {
}

if (node.kind === 'function' && node.value.toLowerCase() === 'url') {
throw new Error('url functions are not supported')
throw new Error('`url(…)` functions are not supported')
}

if (!uri) throw new Error('Unable to find uri')
Expand All @@ -95,7 +95,7 @@ export function parseImportParams(params: ValueParser.ValueAstNode[]) {
node.value.toLowerCase() === 'layer'
) {
if (layer) throw new Error('Multiple layers')
if (supports) throw new Error('layers must be defined before support conditions')
if (supports) throw new Error('`layer(…)` must be defined before `supports(…)` conditions')

if ('nodes' in node) {
layer = ValueParser.toCss(node.nodes)
Expand Down

0 comments on commit 93f9c99

Please sign in to comment.