-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
14abf96
commit d2662d8
Showing
5 changed files
with
197 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import fs from 'node:fs/promises' | ||
import path from 'node:path' | ||
import { pkg } from './utils/packages' | ||
import { info, success, warn } from './utils/renderer' | ||
|
||
// Migrates simple PostCSS setups. This is to cover non-dynamic config files | ||
// similar to the ones we have all over our docs: | ||
// | ||
// ```js | ||
// module.exports = { | ||
// plugins: { | ||
// tailwindcss: {}, | ||
// autoprefixer: {}, | ||
// } | ||
// } | ||
export async function migratePostCSSConfig(base: string) { | ||
let configPath = await detectConfigPath(base) | ||
if (configPath === null) { | ||
// TODO: We can look for an eventual config inside package.json | ||
return | ||
} | ||
|
||
info(`Attempt to upgrade the PostCSS config in file: ${configPath}`) | ||
|
||
let isSimpleConfig = await isSimplePostCSSConfig(base, configPath) | ||
if (!isSimpleConfig) { | ||
warn(`The PostCSS config contains dynamic JavaScript and can not be automatically migrated.`) | ||
return | ||
} | ||
|
||
let didAddPostcssClient = false | ||
let didRemoveAutoprefixer = false | ||
|
||
let fullPath = path.resolve(base, configPath) | ||
let content = await fs.readFile(fullPath, 'utf-8') | ||
let lines = content.split('\n') | ||
let newLines: string[] = [] | ||
for (let line of lines) { | ||
if (line.includes('tailwindcss:')) { | ||
didAddPostcssClient = true | ||
newLines.push(line.replace('tailwindcss:', `'@tailwindcss/postcss':`)) | ||
} else if (line.includes('autoprefixer:')) { | ||
didRemoveAutoprefixer = true | ||
} else { | ||
newLines.push(line) | ||
} | ||
} | ||
await fs.writeFile(fullPath, newLines.join('\n')) | ||
|
||
if (didAddPostcssClient) { | ||
try { | ||
await pkg('add -D @tailwindcss/postcss@next', base) | ||
} catch {} | ||
} | ||
if (didRemoveAutoprefixer) { | ||
try { | ||
await pkg('remove autoprefixer', base) | ||
} catch {} | ||
} | ||
|
||
success(`PostCSS config in file ${configPath} has been upgraded.`) | ||
} | ||
|
||
const CONFIG_FILE_LOCATIONS = [ | ||
'.postcssrc.js', | ||
'.postcssrc.mjs', | ||
'.postcssrc.cjs', | ||
'.postcssrc.ts', | ||
'.postcssrc.mts', | ||
'.postcssrc.cts', | ||
'postcss.config.js', | ||
'postcss.config.mjs', | ||
'postcss.config.cjs', | ||
'postcss.config.ts', | ||
'postcss.config.mts', | ||
'postcss.config.cts', | ||
] | ||
async function detectConfigPath(base: string): Promise<null | string> { | ||
for (let file of CONFIG_FILE_LOCATIONS) { | ||
let fullPath = path.resolve(base, file) | ||
try { | ||
await fs.access(fullPath) | ||
return file | ||
} catch {} | ||
} | ||
return null | ||
} | ||
|
||
async function isSimplePostCSSConfig(base: string, configPath: string): Promise<boolean> { | ||
let fullPath = path.resolve(base, configPath) | ||
let content = await fs.readFile(fullPath, 'utf-8') | ||
return ( | ||
content.includes('tailwindcss:') && !(content.includes('require') || content.includes('import')) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { execSync } from 'node:child_process' | ||
import fs from 'node:fs/promises' | ||
import { dirname, resolve } from 'node:path' | ||
import { warn } from './renderer' | ||
|
||
let didWarnAboutPackageManager = false | ||
|
||
export async function pkg(command: string, base: string): Promise<Buffer | void> { | ||
let packageManager = await detectPackageManager(base) | ||
if (!packageManager) { | ||
if (!didWarnAboutPackageManager) { | ||
didWarnAboutPackageManager = true | ||
warn('Could not detect a package manager. Please manually update `tailwindcss` to v4.') | ||
} | ||
return | ||
} | ||
return execSync(`${packageManager} ${command}`, { | ||
cwd: base, | ||
}) | ||
} | ||
|
||
async function detectPackageManager(base: string): Promise<null | string> { | ||
do { | ||
// 1. Check package.json for a `packageManager` field | ||
let packageJsonPath = resolve(base, 'package.json') | ||
try { | ||
let packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8') | ||
let packageJson = JSON.parse(packageJsonContent) | ||
if (packageJson.packageManager) { | ||
if (packageJson.packageManager.includes('yarn')) { | ||
return 'yarn' | ||
} | ||
if (packageJson.packageManager.includes('pnpm')) { | ||
return 'pnpm' | ||
} | ||
if (packageJson.packageManager.includes('npm')) { | ||
return 'npm' | ||
} | ||
} | ||
} catch {} | ||
|
||
// 2. Check for common lockfiles | ||
try { | ||
await fs.access(resolve(base, 'pnpm-lock.yaml')) | ||
return 'pnpm' | ||
} catch {} | ||
|
||
try { | ||
await fs.access(resolve(base, 'yarn.lock')) | ||
return 'yarn' | ||
} catch {} | ||
|
||
try { | ||
await fs.access(resolve(base, 'package-lock.json')) | ||
return 'npm' | ||
} catch {} | ||
|
||
// 3. If no lockfile is found, we might be in a monorepo | ||
base = dirname(base) | ||
} while (true) | ||
} |