Skip to content

Commit 93f9c99

Browse files
authored
Improve robustness when upgrading (#15038)
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"> |
1 parent 08c6c96 commit 93f9c99

File tree

9 files changed

+88
-69
lines changed

9 files changed

+88
-69
lines changed

packages/@tailwindcss-upgrade/src/index.ts

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async function run() {
8484
// Load and parse all stylesheets
8585
for (let result of loadResults) {
8686
if (result.status === 'rejected') {
87-
error(`${result.reason}`)
87+
error(`${result.reason?.message ?? result.reason}`, { prefix: '↳ ' })
8888
}
8989
}
9090

@@ -95,8 +95,8 @@ async function run() {
9595
// Analyze the stylesheets
9696
try {
9797
await analyzeStylesheets(stylesheets)
98-
} catch (e: unknown) {
99-
error(`${e}`)
98+
} catch (e: any) {
99+
error(`${e?.message ?? e}`, { prefix: '↳ ' })
100100
}
101101

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

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

137139
if (jsConfigMigration !== null) {
138140
success(
139-
`↳ Migrated configuration file: ${highlight(relative(config.configFilePath, base))}`,
141+
`Migrated configuration file: ${highlight(relative(config.configFilePath, base))}`,
142+
{ prefix: '↳ ' },
140143
)
141144
}
142145
}
143146

144147
// Migrate source files, linked to config files
145-
info('Migrating templates…')
148+
if (configBySheet.size > 0) {
149+
info('Migrating templates…')
150+
}
146151
{
147152
// Template migrations
148153
for (let config of configBySheet.values()) {
@@ -168,43 +173,44 @@ async function run() {
168173
)
169174

170175
success(
171-
`↳ Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`,
176+
`Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`,
177+
{ prefix: '↳ ' },
172178
)
173179
}
174180
}
175181

176182
// Migrate each CSS file
177-
info('Migrating stylesheets…')
178-
let migrateResults = await Promise.allSettled(
179-
stylesheets.map((sheet) => {
180-
let config = configBySheet.get(sheet)!
181-
let jsConfigMigration = jsConfigMigrationBySheet.get(sheet)!
182-
183-
if (!config) {
184-
for (let parent of sheet.ancestors()) {
185-
if (parent.isTailwindRoot) {
186-
config ??= configBySheet.get(parent)!
187-
jsConfigMigration ??= jsConfigMigrationBySheet.get(parent)!
188-
break
183+
if (stylesheets.length > 0) {
184+
info('Migrating stylesheets…')
185+
}
186+
await Promise.all(
187+
stylesheets.map(async (sheet) => {
188+
try {
189+
let config = configBySheet.get(sheet)!
190+
let jsConfigMigration = jsConfigMigrationBySheet.get(sheet)!
191+
192+
if (!config) {
193+
for (let parent of sheet.ancestors()) {
194+
if (parent.isTailwindRoot) {
195+
config ??= configBySheet.get(parent)!
196+
jsConfigMigration ??= jsConfigMigrationBySheet.get(parent)!
197+
break
198+
}
189199
}
190200
}
191-
}
192201

193-
return migrateStylesheet(sheet, { ...config, jsConfigMigration })
202+
await migrateStylesheet(sheet, { ...config, jsConfigMigration })
203+
} catch (e: any) {
204+
error(`${e?.message ?? e} in ${highlight(relative(sheet.file!, base))}`, { prefix: '↳ ' })
205+
}
194206
}),
195207
)
196208

197-
for (let result of migrateResults) {
198-
if (result.status === 'rejected') {
199-
error(`${result.reason}`)
200-
}
201-
}
202-
203209
// Split up stylesheets (as needed)
204210
try {
205211
await splitStylesheets(stylesheets)
206-
} catch (e: unknown) {
207-
error(`${e}`)
212+
} catch (e: any) {
213+
error(`${e?.message ?? e}`, { prefix: '↳ ' })
208214
}
209215

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

243249
if (sheet.isTailwindRoot) {
244-
success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`)
250+
success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`, { prefix: '↳ ' })
245251
}
246252
}
247253
}
@@ -260,7 +266,7 @@ async function run() {
260266
try {
261267
// Upgrade Tailwind CSS
262268
await pkg(base).add(['tailwindcss@next'])
263-
success(`Updated package: ${highlight('tailwindcss')}`)
269+
success(`Updated package: ${highlight('tailwindcss')}`, { prefix: '↳ ' })
264270
} catch {}
265271

266272
// Run all cleanup functions because we completed the migration

packages/@tailwindcss-upgrade/src/migrate-js-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export async function migrateJsConfig(
4444

4545
if (!canMigrateConfig(unresolvedConfig, source)) {
4646
info(
47-
`↳ 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.`,
47+
`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.`,
48+
{ prefix: '↳ ' },
4849
)
4950
return null
5051
}

packages/@tailwindcss-upgrade/src/migrate-postcss.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export async function migratePostCSSConfig(base: string) {
9292
if (location !== null) {
9393
try {
9494
await pkg(base).add(['@tailwindcss/postcss@next'], location)
95-
success(`Installed package: ${highlight('@tailwindcss/postcss')}`)
95+
success(`Installed package: ${highlight('@tailwindcss/postcss')}`, { prefix: '↳ ' })
9696
} catch {}
9797
}
9898
}
@@ -104,13 +104,15 @@ export async function migratePostCSSConfig(base: string) {
104104
].filter(Boolean) as string[]
105105
await pkg(base).remove(packagesToRemove)
106106
for (let pkg of packagesToRemove) {
107-
success(`Removed package: ${highlight(pkg)}`)
107+
success(`Removed package: ${highlight(pkg)}`, { prefix: '↳ ' })
108108
}
109109
} catch {}
110110
}
111111

112112
if (didMigrate && jsConfigPath) {
113-
success(`↳ Migrated PostCSS configuration: ${highlight(relative(jsConfigPath, base))}`)
113+
success(`Migrated PostCSS configuration: ${highlight(relative(jsConfigPath, base))}`, {
114+
prefix: '↳ ',
115+
})
114116
}
115117
}
116118

packages/@tailwindcss-upgrade/src/migrate-prettier.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export async function migratePrettierPlugin(base: string) {
99
let packageJson = await fs.readFile(packageJsonPath, 'utf-8')
1010
if (packageJson.includes('prettier-plugin-tailwindcss')) {
1111
await pkg(base).add(['prettier-plugin-tailwindcss@latest'])
12-
success(`Updated package: ${highlight('prettier-plugin-tailwindcss')}`)
12+
success(`Updated package: ${highlight('prettier-plugin-tailwindcss')}`, { prefix: '↳ ' })
1313
}
1414
} catch {}
1515
}

packages/@tailwindcss-upgrade/src/migrate.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ export async function analyze(stylesheets: Stylesheet[]) {
120120
resolvedPath = resolveCssId(id, basePath)
121121
}
122122
} catch (err) {
123-
console.warn(`Failed to resolve import: ${id}. Skipping.`)
124-
console.error(err)
123+
error(
124+
`Failed to resolve import: ${highlight(id)} in ${highlight(relative(node.source?.input.file!, basePath))}. Skipping.`,
125+
{ prefix: '↳ ' },
126+
)
125127
return
126128
}
127129

@@ -334,10 +336,12 @@ export async function analyze(stylesheets: Stylesheet[]) {
334336
}
335337
}
336338

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

340-
throw new Error(error + lines.join('\n'))
343+
throw new Error(error + lines.join('\n'))
344+
}
341345
}
342346

343347
export async function linkConfigs(
@@ -347,7 +351,7 @@ export async function linkConfigs(
347351
let rootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot)
348352
if (rootStylesheets.length === 0) {
349353
throw new Error(
350-
'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`.',
354+
`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')}.`,
351355
)
352356
}
353357
let withoutAtConfig = rootStylesheets.filter((sheet) => {
@@ -396,6 +400,7 @@ export async function linkConfigs(
396400
for (let sheet of problematicStylesheets) {
397401
error(
398402
`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.`,
403+
{ prefix: '↳ ' },
399404
)
400405
}
401406

@@ -407,7 +412,8 @@ export async function linkConfigs(
407412
try {
408413
if (!sheet || !sheet.file) return
409414
success(
410-
`↳ Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`,
415+
`Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`,
416+
{ prefix: '↳ ' },
411417
)
412418

413419
// Link the `@config` directive to the root stylesheets
@@ -447,7 +453,7 @@ export async function linkConfigs(
447453
}
448454
}
449455
} catch (e: any) {
450-
error('Could not load the configuration file: ' + e.message)
456+
error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' })
451457
process.exit(1)
452458
}
453459
}

packages/@tailwindcss-upgrade/src/template/prepare-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export async function prepareConfig(
6565
configFilePath,
6666
}
6767
} catch (e: any) {
68-
error('Could not load the configuration file: ' + e.message)
68+
error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' })
6969
process.exit(1)
7070
}
7171
}

packages/@tailwindcss-upgrade/src/utils/extract-static-plugins.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,8 @@ export function findStaticPlugins(source: string): [string, null | StaticPluginO
272272
}
273273
}
274274
return plugins
275-
} catch (error) {
276-
console.error(error)
275+
} catch (error: any) {
276+
error(`${error?.message ?? error}`, { prefix: '↳ ' })
277277
return null
278278
}
279279
}

packages/@tailwindcss-upgrade/src/utils/renderer.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,32 +89,36 @@ export function indent(value: string, offset = 0) {
8989
return `${' '.repeat(offset + UI.indent)}${value}`
9090
}
9191

92-
export function success(message: string, print = eprintln) {
93-
wordWrap(message, process.stderr.columns - 3).map((line) => {
94-
return print(`${pc.green('\u2502')} ${line}`)
92+
function log(message: string, { art = pc.gray('\u2502'), prefix = '', print = eprintln }) {
93+
let prefixLength = prefix.length
94+
let padding = ' '
95+
let paddingLength = padding.length
96+
let artLength = stripVTControlCharacters(art).length
97+
let availableWidth = process.stderr.columns
98+
let totalWidth = availableWidth - prefixLength - paddingLength * 2 - artLength
99+
100+
wordWrap(message, totalWidth).map((line, idx) => {
101+
return print(
102+
`${art}${padding}${idx === 0 ? prefix : ' '.repeat(prefixLength)}${line}${padding}`,
103+
)
95104
})
96105
print()
97106
}
98107

99-
export function info(message: string, print = eprintln) {
100-
wordWrap(message, process.stderr.columns - 3).map((line) => {
101-
return print(`${pc.blue('\u2502')} ${line}`)
102-
})
103-
print()
108+
export function success(message: string, { prefix = '', print = eprintln } = {}) {
109+
log(message, { art: pc.green('\u2502'), prefix, print })
104110
}
105111

106-
export function error(message: string, print = eprintln) {
107-
wordWrap(message, process.stderr.columns - 3).map((line) => {
108-
return print(`${pc.red('\u2502')} ${line}`)
109-
})
110-
print()
112+
export function info(message: string, { prefix = '', print = eprintln } = {}) {
113+
log(message, { art: pc.blue('\u2502'), prefix, print })
111114
}
112115

113-
export function warn(message: string, print = eprintln) {
114-
wordWrap(message, process.stderr.columns - 3).map((line) => {
115-
return print(`${pc.yellow('\u2502')} ${line}`)
116-
})
117-
print()
116+
export function error(message: string, { prefix = '', print = eprintln } = {}) {
117+
log(message, { art: pc.red('\u2502'), prefix, print })
118+
}
119+
120+
export function warn(message: string, { prefix = '', print = eprintln } = {}) {
121+
log(message, { art: pc.yellow('\u2502'), prefix, print })
118122
}
119123

120124
// Rust inspired functions to print to the console:

packages/tailwindcss/src/at-import.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export function parseImportParams(params: ValueParser.ValueAstNode[]) {
8585
}
8686

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

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

100100
if ('nodes' in node) {
101101
layer = ValueParser.toCss(node.nodes)

0 commit comments

Comments
 (0)