diff --git a/CHANGELOG.md b/CHANGELOG.md index ee34d8e92ff2..9f0f0756f0e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Detect classes in new files when using `@tailwindcss/postcss` ([#14829](https://github.com/tailwindlabs/tailwindcss/pull/14829)) +- _Upgrade (experimental)_: Install `@tailwindcss/postcss` next to `tailwindcss` ([#14830](https://github.com/tailwindlabs/tailwindcss/pull/14830)) ## [4.0.0-alpha.31] - 2024-10-29 diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 848e42bcfe61..4dd581b0c897 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -544,6 +544,100 @@ test( }) expect(packageJson.dependencies).not.toHaveProperty('autoprefixer') expect(packageJson.dependencies).not.toHaveProperty('postcss-import') + expect(packageJson.dependencies).toMatchObject({ + '@tailwindcss/postcss': expect.stringContaining('4.0.0'), + }) + }, +) + +test( + '`@tailwindcss/postcss` should be installed in dependencies when `tailwindcss` exists in dependencies', + { + fs: { + 'package.json': json` + { + "dependencies": { + "postcss": "^8", + "tailwindcss": "^3", + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.js': js` + /** @type {import('tailwindcss').Config} */ + module.exports = { + content: ['./src/**/*.{html,js}'], + } + `, + 'postcss.config.js': js` + module.exports = { + plugins: { + tailwindcss: {}, + }, + } + `, + 'src/index.html': html` +
+ `, + 'src/index.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ fs, exec }) => { + await exec('npx @tailwindcss/upgrade') + + let packageJsonContent = await fs.read('package.json') + let packageJson = JSON.parse(packageJsonContent) + expect(packageJson.dependencies).toMatchObject({ + '@tailwindcss/postcss': expect.stringContaining('4.0.0'), + }) + }, +) + +test( + '`@tailwindcss/postcss` should be installed in devDependencies when `tailwindcss` exists in dev dependencies', + { + fs: { + 'package.json': json` + { + "devDependencies": { + "postcss": "^8", + "tailwindcss": "^3", + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.js': js` + /** @type {import('tailwindcss').Config} */ + module.exports = { + content: ['./src/**/*.{html,js}'], + } + `, + 'postcss.config.js': js` + module.exports = { + plugins: { + tailwindcss: {}, + }, + } + `, + 'src/index.html': html` + + `, + 'src/index.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ fs, exec }) => { + await exec('npx @tailwindcss/upgrade') + + let packageJsonContent = await fs.read('package.json') + let packageJson = JSON.parse(packageJsonContent) expect(packageJson.devDependencies).toMatchObject({ '@tailwindcss/postcss': expect.stringContaining('4.0.0'), }) @@ -617,7 +711,7 @@ test( }) expect(packageJson.dependencies).not.toHaveProperty('autoprefixer') expect(packageJson.dependencies).not.toHaveProperty('postcss-import') - expect(packageJson.devDependencies).toMatchObject({ + expect(packageJson.dependencies).toMatchObject({ '@tailwindcss/postcss': expect.stringContaining('4.0.0'), }) }, @@ -694,7 +788,7 @@ test( }) expect(packageJson.dependencies).not.toHaveProperty('autoprefixer') expect(packageJson.dependencies).not.toHaveProperty('postcss-import') - expect(packageJson.devDependencies).toMatchObject({ + expect(packageJson.dependencies).toMatchObject({ '@tailwindcss/postcss': expect.stringContaining('4.0.0'), }) }, diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index 767bec8f882c..a0434b92f064 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -199,7 +199,7 @@ async function run() { try { // Upgrade Tailwind CSS - await pkg('add tailwindcss@next', base) + await pkg(base).add(['tailwindcss@next']) } catch {} // Remove the JS config if it was fully migrated diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts index 2b383425d2fd..1f4d6c46868b 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -83,19 +83,25 @@ export async function migratePostCSSConfig(base: string) { } if (didAddPostcssClient) { - try { - await pkg('add -D @tailwindcss/postcss@next', base) - } catch {} + let location = Object.hasOwn(packageJson?.dependencies ?? {}, 'tailwindcss') + ? ('dependencies' as const) + : Object.hasOwn(packageJson?.devDependencies ?? {}, 'tailwindcss') + ? ('devDependencies' as const) + : null + + if (location !== null) { + try { + await pkg(base).add(['@tailwindcss/postcss@next'], location) + } catch {} + } } if (didRemoveAutoprefixer || didRemovePostCSSImport) { try { let packagesToRemove = [ didRemoveAutoprefixer ? 'autoprefixer' : null, didRemovePostCSSImport ? 'postcss-import' : null, - ] - .filter(Boolean) - .join(' ') - await pkg(`remove ${packagesToRemove}`, base) + ].filter(Boolean) as string[] + await pkg(base).remove(packagesToRemove) } catch {} } diff --git a/packages/@tailwindcss-upgrade/src/migrate-prettier.ts b/packages/@tailwindcss-upgrade/src/migrate-prettier.ts index 9ff1e824db9f..359b4888180d 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-prettier.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-prettier.ts @@ -8,7 +8,7 @@ export async function migratePrettierPlugin(base: string) { try { let packageJson = await fs.readFile(packageJsonPath, 'utf-8') if (packageJson.includes('prettier-plugin-tailwindcss')) { - await pkg('add prettier-plugin-tailwindcss@latest', base) + await pkg(base).add(['prettier-plugin-tailwindcss@latest']) success(`Prettier plugin migrated to latest version.`) } } catch {} diff --git a/packages/@tailwindcss-upgrade/src/utils/packages.ts b/packages/@tailwindcss-upgrade/src/utils/packages.ts index 837816b4ccc8..0c6de106327a 100644 --- a/packages/@tailwindcss-upgrade/src/utils/packages.ts +++ b/packages/@tailwindcss-upgrade/src/utils/packages.ts @@ -1,25 +1,36 @@ -import { execSync } from 'node:child_process' +import { exec as execCb } from 'node:child_process' import fs from 'node:fs/promises' import { dirname, resolve } from 'node:path' +import { promisify } from 'node:util' +import { DefaultMap } from '../../../tailwindcss/src/utils/default-map' import { warn } from './renderer' -let didWarnAboutPackageManager = false +const exec = promisify(execCb) -export async function pkg(command: string, base: string): Promise