(1/n) shadcn: add tailwind version detection (#6478)

* feat(shadcn): add tailwind version detection

* chore: changeset
This commit is contained in:
shadcn
2025-01-30 11:49:09 +04:00
committed by GitHub
parent e85920c191
commit 8f6a64f176
11 changed files with 96 additions and 68 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add tailwind version detection

View File

@@ -81,7 +81,16 @@ export async function preFlightInit(
const tailwindSpinner = spinner(`Validating Tailwind CSS.`, {
silent: options.silent,
}).start()
if (!projectInfo?.tailwindConfigFile || !projectInfo?.tailwindCssFile) {
if (
projectInfo.tailwindVersion === "v3" &&
(!projectInfo?.tailwindConfigFile || !projectInfo?.tailwindCssFile)
) {
errors[ERRORS.TAILWIND_NOT_CONFIGURED] = true
tailwindSpinner?.fail()
} else if (
projectInfo.tailwindVersion === "v4" &&
!projectInfo?.tailwindCssFile
) {
errors[ERRORS.TAILWIND_NOT_CONFIGURED] = true
tailwindSpinner?.fail()
} else {

View File

@@ -3,7 +3,6 @@ import { highlighter } from "@/src/utils/highlighter"
import { resolveImport } from "@/src/utils/resolve-import"
import { cosmiconfig } from "cosmiconfig"
import fg from "fast-glob"
import fs from "fs-extra"
import { loadConfig } from "tsconfig-paths"
import { z } from "zod"
@@ -27,7 +26,7 @@ export const rawConfigSchema = z
rsc: z.coerce.boolean().default(false),
tsx: z.coerce.boolean().default(true),
tailwind: z.object({
config: z.string(),
config: z.string().optional(),
css: z.string(),
baseColor: z.string(),
cssVariables: z.boolean().default(true),
@@ -96,7 +95,9 @@ export async function resolveConfigPaths(cwd: string, config: RawConfig) {
...config,
resolvedPaths: {
cwd,
tailwindConfig: path.resolve(cwd, config.tailwind.config),
tailwindConfig: config.tailwind.config
? path.resolve(cwd, config.tailwind.config)
: "",
tailwindCss: path.resolve(cwd, config.tailwind.css),
utils: await resolveImport(config.aliases["utils"], tsConfig),
components: await resolveImport(config.aliases["components"], tsConfig),

View File

@@ -19,6 +19,7 @@ type ProjectInfo = {
isTsx: boolean
tailwindConfigFile: string | null
tailwindCssFile: string | null
tailwindVersion: "v3" | "v4" | null
aliasPrefix: string | null
}
@@ -43,6 +44,7 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
isTsx,
tailwindConfigFile,
tailwindCssFile,
tailwindVersion,
aliasPrefix,
packageJson,
] = await Promise.all([
@@ -55,6 +57,7 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
isTypeScriptProject(cwd),
getTailwindConfigFile(cwd),
getTailwindCssFile(cwd),
getTailwindVersion(cwd),
getTsConfigAliasPrefix(cwd),
getPackageInfo(cwd, false),
])
@@ -70,6 +73,7 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
isTsx,
tailwindConfigFile,
tailwindCssFile,
tailwindVersion,
aliasPrefix,
}
@@ -121,21 +125,50 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
return type
}
export async function getTailwindVersion(
cwd: string
): Promise<ProjectInfo["tailwindVersion"]> {
const packageInfo = getPackageInfo(cwd)
if (
!packageInfo?.dependencies?.tailwindcss &&
!packageInfo?.devDependencies?.tailwindcss
) {
return null
}
if (
/^(?:\^|~)?3(?:\.\d+)*(?:-.*)?$/.test(
packageInfo?.dependencies?.tailwindcss ||
packageInfo?.devDependencies?.tailwindcss ||
""
)
) {
return "v3"
}
return "v4"
}
export async function getTailwindCssFile(cwd: string) {
const files = await fg.glob(["**/*.css", "**/*.scss"], {
cwd,
deep: 5,
ignore: PROJECT_SHARED_IGNORE,
})
const [files, tailwindVersion] = await Promise.all([
fg.glob(["**/*.css", "**/*.scss"], {
cwd,
deep: 5,
ignore: PROJECT_SHARED_IGNORE,
}),
getTailwindVersion(cwd),
])
if (!files.length) {
return null
}
const needle =
tailwindVersion === "v4" ? `@import "tailwindcss"` : "@tailwind base"
for (const file of files) {
const contents = await fs.readFile(path.resolve(cwd, file), "utf8")
// Assume that if the file contains `@tailwind base` it's the main css file.
if (contents.includes("@tailwind base")) {
if (contents.includes(needle)) {
return file
}
}
@@ -237,8 +270,8 @@ export async function getProjectConfig(
if (
!projectInfo ||
!projectInfo.tailwindConfigFile ||
!projectInfo.tailwindCssFile
!projectInfo.tailwindCssFile ||
(projectInfo.tailwindVersion === "v3" && !projectInfo.tailwindConfigFile)
) {
return null
}
@@ -249,7 +282,7 @@ export async function getProjectConfig(
tsx: projectInfo.isTsx,
style: "new-york",
tailwind: {
config: projectInfo.tailwindConfigFile,
config: projectInfo.tailwindConfigFile ?? "",
baseColor: "zinc",
css: projectInfo.tailwindCssFile,
cssVariables: true,

View File

@@ -0,0 +1,14 @@
{
"name": "next-app-src",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"tailwindcss": "3.1.2"
}
}

View File

@@ -7,5 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"tailwindcss": "^3.0.0"
}
}

View File

@@ -7,5 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"tailwindcss": "^4.0.0"
}
}

View File

@@ -1,27 +1 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@import "tailwindcss";

View File

@@ -7,5 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"devDependencies": {
"tailwindcss": "4.1.2"
}
}

View File

@@ -1,27 +1 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@import "tailwindcss";

View File

@@ -15,6 +15,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "app/globals.css",
tailwindVersion: "v3",
aliasPrefix: "@",
},
},
@@ -27,6 +28,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "src/app/styles.css",
tailwindVersion: "v3",
aliasPrefix: "#",
},
},
@@ -39,6 +41,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "styles/globals.css",
tailwindVersion: "v4",
aliasPrefix: "~",
},
},
@@ -51,6 +54,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "src/styles/globals.css",
tailwindVersion: "v4",
aliasPrefix: "@",
},
},
@@ -63,6 +67,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "src/styles/globals.css",
tailwindVersion: "v3",
aliasPrefix: "~",
},
},
@@ -75,6 +80,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "src/styles/globals.css",
tailwindVersion: "v3",
aliasPrefix: "~",
},
},
@@ -87,6 +93,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "app/tailwind.css",
tailwindVersion: "v3",
aliasPrefix: "~",
},
},
@@ -99,6 +106,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.ts",
tailwindCssFile: "app/tailwind.css",
tailwindVersion: "v3",
aliasPrefix: "~",
},
},
@@ -111,6 +119,7 @@ describe("get project info", async () => {
isTsx: true,
tailwindConfigFile: "tailwind.config.js",
tailwindCssFile: "src/index.css",
tailwindVersion: "v3",
aliasPrefix: null,
},
},