mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-02-09 02:49:29 +08:00
feat(shadcn): middleware to proxy (#8555)
* feat: implement getFrameworkVersion * feat(shadcn): add transformNext transformer * feat(shadcn): rename * chore: update * chore: changeset * fix * fix: small refactor
This commit is contained in:
5
.changeset/major-keys-begin.md
Normal file
5
.changeset/major-keys-begin.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
rename middleware to proxy for Next.js 16
|
||||
@@ -18,6 +18,7 @@ export type ProjectInfo = {
|
||||
tailwindConfigFile: string | null
|
||||
tailwindCssFile: string | null
|
||||
tailwindVersion: TailwindVersion
|
||||
frameworkVersion: string | null
|
||||
aliasPrefix: string | null
|
||||
}
|
||||
|
||||
@@ -75,6 +76,7 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
|
||||
tailwindConfigFile,
|
||||
tailwindCssFile,
|
||||
tailwindVersion,
|
||||
frameworkVersion: null,
|
||||
aliasPrefix,
|
||||
}
|
||||
|
||||
@@ -84,6 +86,10 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
|
||||
? FRAMEWORKS["next-app"]
|
||||
: FRAMEWORKS["next-pages"]
|
||||
type.isRSC = isUsingAppDir
|
||||
type.frameworkVersion = await getFrameworkVersion(
|
||||
type.framework,
|
||||
packageJson
|
||||
)
|
||||
return type
|
||||
}
|
||||
|
||||
@@ -165,6 +171,42 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
|
||||
return type
|
||||
}
|
||||
|
||||
export async function getFrameworkVersion(
|
||||
framework: Framework,
|
||||
packageJson: ReturnType<typeof getPackageInfo>
|
||||
) {
|
||||
if (!packageJson) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Only detect Next.js version for now.
|
||||
if (!["next-app", "next-pages"].includes(framework.name)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const version =
|
||||
packageJson.dependencies?.next || packageJson.devDependencies?.next
|
||||
|
||||
if (!version) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Extract full semver (major.minor.patch), handling ^, ~, etc.
|
||||
const versionMatch = version.match(/^[\^~]?(\d+\.\d+\.\d+)/)
|
||||
if (versionMatch) {
|
||||
return versionMatch[1] // e.g., "16.0.0"
|
||||
}
|
||||
|
||||
// For ranges like ">=15.0.0 <16.0.0", extract the first version.
|
||||
const rangeMatch = version.match(/(\d+\.\d+\.\d+)/)
|
||||
if (rangeMatch) {
|
||||
return rangeMatch[1]
|
||||
}
|
||||
|
||||
// For "latest", "canary", "rc", etc., return the tag as-is.
|
||||
return version
|
||||
}
|
||||
|
||||
export async function getTailwindVersion(
|
||||
cwd: string
|
||||
): Promise<ProjectInfo["tailwindVersion"]> {
|
||||
|
||||
424
packages/shadcn/src/utils/transformers/transform-next.test.ts
Normal file
424
packages/shadcn/src/utils/transformers/transform-next.test.ts
Normal file
@@ -0,0 +1,424 @@
|
||||
import { FRAMEWORKS } from "@/src/utils/frameworks"
|
||||
import { type Config } from "@/src/utils/get-config"
|
||||
import { transformNext } from "@/src/utils/transformers/transform-next"
|
||||
import { describe, expect, test, vi } from "vitest"
|
||||
|
||||
import { transform } from "../transformers"
|
||||
|
||||
const testConfig: Config = {
|
||||
style: "new-york",
|
||||
tsx: true,
|
||||
rsc: true,
|
||||
tailwind: {
|
||||
baseColor: "neutral",
|
||||
cssVariables: true,
|
||||
config: "tailwind.config.ts",
|
||||
css: "tailwind.css",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
resolvedPaths: {
|
||||
cwd: "/test-project",
|
||||
components: "/test-project/components",
|
||||
utils: "/test-project/lib/utils",
|
||||
ui: "/test-project/ui",
|
||||
lib: "/test-project/lib",
|
||||
hooks: "/test-project/hooks",
|
||||
tailwindConfig: "tailwind.config.ts",
|
||||
tailwindCss: "tailwind.css",
|
||||
},
|
||||
}
|
||||
|
||||
vi.mock("@/src/utils/get-project-info", () => ({
|
||||
getProjectInfo: vi.fn(),
|
||||
}))
|
||||
|
||||
describe("transformNext", () => {
|
||||
describe("Next.js 16+ transformations", () => {
|
||||
test("should transform function declaration export", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "16.0.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: `import { NextResponse } from "next/server"
|
||||
|
||||
export function middleware(request: Request) {
|
||||
return NextResponse.next()
|
||||
}`,
|
||||
config: testConfig,
|
||||
},
|
||||
[transformNext]
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"import { NextResponse } from "next/server"
|
||||
|
||||
export function proxy(request: Request) {
|
||||
return NextResponse.next()
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should transform async function declaration", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "16.1.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: `import { NextResponse } from "next/server"
|
||||
|
||||
export async function middleware(request: Request) {
|
||||
return NextResponse.next()
|
||||
}`,
|
||||
config: testConfig,
|
||||
},
|
||||
[transformNext]
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"import { NextResponse } from "next/server"
|
||||
|
||||
export async function proxy(request: Request) {
|
||||
return NextResponse.next()
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should transform const arrow function export", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "16.0.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: `import { NextResponse } from "next/server"
|
||||
|
||||
export const middleware = (request: Request) => {
|
||||
return NextResponse.next()
|
||||
}`,
|
||||
config: testConfig,
|
||||
},
|
||||
[transformNext]
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"import { NextResponse } from "next/server"
|
||||
|
||||
export const proxy = (request: Request) => {
|
||||
return NextResponse.next()
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should transform named export with alias", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "16.0.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: `import { NextResponse } from "next/server"
|
||||
|
||||
function handler(request: Request) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
export { handler as middleware }`,
|
||||
config: testConfig,
|
||||
},
|
||||
[transformNext]
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"import { NextResponse } from "next/server"
|
||||
|
||||
function handler(request: Request) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
export { handler as proxy }"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Next.js < 16 or unknown versions (no transformation)", () => {
|
||||
test("should not transform for Next.js 15", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "15.0.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `import { NextResponse } from "next/server"
|
||||
|
||||
export function middleware(request: Request) {
|
||||
return NextResponse.next()
|
||||
}`
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for Next.js 15
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
test("should not transform when frameworkVersion is null", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: null,
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `import { NextResponse } from "next/server"
|
||||
|
||||
export function middleware(request: Request) {
|
||||
return NextResponse.next()
|
||||
}`
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext when frameworkVersion is null
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
test("should not transform for canary tag (unknown version)", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "canary",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `import { NextResponse } from "next/server"
|
||||
|
||||
export function middleware(request: Request) {
|
||||
return NextResponse.next()
|
||||
}`
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for canary tag
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
test("should not transform for latest tag (unknown version)", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "latest",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `import { NextResponse } from "next/server"
|
||||
|
||||
export function middleware(request: Request) {
|
||||
return NextResponse.next()
|
||||
}`
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for latest tag
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Non-middleware files", () => {
|
||||
test("should not transform non-middleware files", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "16.0.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `export function middleware() {
|
||||
return "not a middleware file"
|
||||
}`
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "utils.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for non-middleware files
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
test("should not transform nested middleware files", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["next-app"],
|
||||
frameworkVersion: "16.0.0",
|
||||
isSrcDir: false,
|
||||
isRSC: true,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `export function middleware() {
|
||||
return "nested middleware"
|
||||
}`
|
||||
|
||||
// Nested middleware files should not be transformed
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "lib/middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for nested middleware files
|
||||
)
|
||||
).toBe(input)
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "lib/supabase/middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for nested middleware files
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Non-Next.js projects", () => {
|
||||
test("should not transform for Vite projects", async () => {
|
||||
const { getProjectInfo } = await import("@/src/utils/get-project-info")
|
||||
vi.mocked(getProjectInfo).mockResolvedValue({
|
||||
framework: FRAMEWORKS["vite"],
|
||||
frameworkVersion: null,
|
||||
isSrcDir: false,
|
||||
isRSC: false,
|
||||
isTsx: true,
|
||||
tailwindConfigFile: null,
|
||||
tailwindCssFile: null,
|
||||
tailwindVersion: "v4",
|
||||
aliasPrefix: "@",
|
||||
})
|
||||
|
||||
const input = `export function middleware() {
|
||||
return "some middleware"
|
||||
}`
|
||||
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "middleware.ts",
|
||||
raw: input,
|
||||
config: testConfig,
|
||||
},
|
||||
[] // Don't include transformNext for non-Next.js projects
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
})
|
||||
})
|
||||
33
packages/shadcn/src/utils/transformers/transform-next.ts
Normal file
33
packages/shadcn/src/utils/transformers/transform-next.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Transformer } from "@/src/utils/transformers"
|
||||
|
||||
export const transformNext: Transformer = async ({ sourceFile }) => {
|
||||
// export function middleware.
|
||||
sourceFile.getFunctions().forEach((func) => {
|
||||
if (func.getName() === "middleware") {
|
||||
func.rename("proxy")
|
||||
}
|
||||
})
|
||||
|
||||
// export const middleware.
|
||||
sourceFile.getVariableDeclarations().forEach((variable) => {
|
||||
if (variable.getName() === "middleware") {
|
||||
variable.rename("proxy")
|
||||
}
|
||||
})
|
||||
|
||||
// export { handler as middleware }.
|
||||
sourceFile.getExportDeclarations().forEach((exportDecl) => {
|
||||
const namedExports = exportDecl.getNamedExports()
|
||||
namedExports.forEach((namedExport) => {
|
||||
if (namedExport.getName() === "middleware") {
|
||||
namedExport.setName("proxy")
|
||||
}
|
||||
const aliasNode = namedExport.getAliasNode()
|
||||
if (aliasNode?.getText() === "middleware") {
|
||||
namedExport.setAlias("proxy")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return sourceFile
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import { transform } from "@/src/utils/transformers"
|
||||
import { transformCssVars } from "@/src/utils/transformers/transform-css-vars"
|
||||
import { transformIcons } from "@/src/utils/transformers/transform-icons"
|
||||
import { transformImport } from "@/src/utils/transformers/transform-import"
|
||||
import { transformNext } from "@/src/utils/transformers/transform-next"
|
||||
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
|
||||
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
|
||||
import prompts from "prompts"
|
||||
@@ -138,6 +139,9 @@ export async function updateFiles(
|
||||
transformCssVars,
|
||||
transformTwPrefixes,
|
||||
transformIcons,
|
||||
...(_isNext16Middleware(filePath, projectInfo, config)
|
||||
? [transformNext]
|
||||
: []),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -186,6 +190,11 @@ export async function updateFiles(
|
||||
}
|
||||
}
|
||||
|
||||
// Rename middleware.ts to proxy.ts for Next.js 16+.
|
||||
if (_isNext16Middleware(filePath, projectInfo, config)) {
|
||||
filePath = filePath.replace(/middleware\.(ts|js)$/, "proxy.$1")
|
||||
}
|
||||
|
||||
// Create the target directory if it doesn't exist.
|
||||
if (!existsSync(targetDir)) {
|
||||
await fs.mkdir(targetDir, { recursive: true })
|
||||
@@ -715,3 +724,26 @@ export function toAliasedImport(
|
||||
// but usually config.aliases already include it.
|
||||
return `${aliasBase}${suffix}${keepExt}`
|
||||
}
|
||||
|
||||
function _isNext16Middleware(
|
||||
filePath: string,
|
||||
projectInfo: ProjectInfo | null,
|
||||
config: Config
|
||||
) {
|
||||
const isRootMiddleware =
|
||||
filePath === path.join(config.resolvedPaths.cwd, "middleware.ts") ||
|
||||
filePath === path.join(config.resolvedPaths.cwd, "middleware.js")
|
||||
|
||||
const isNextJs =
|
||||
projectInfo?.framework.name === "next-app" ||
|
||||
projectInfo?.framework.name === "next-pages"
|
||||
|
||||
if (!isRootMiddleware || !isNextJs || !projectInfo?.frameworkVersion) {
|
||||
return false
|
||||
}
|
||||
|
||||
const majorVersion = parseInt(projectInfo.frameworkVersion.split(".")[0])
|
||||
const isNext16Plus = !isNaN(majorVersion) && majorVersion >= 16
|
||||
|
||||
return isNext16Plus
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import path from "path"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { FRAMEWORKS } from "../../src/utils/frameworks"
|
||||
import { getProjectInfo } from "../../src/utils/get-project-info"
|
||||
import {
|
||||
getFrameworkVersion,
|
||||
getProjectInfo,
|
||||
} from "../../src/utils/get-project-info"
|
||||
|
||||
describe("get project info", async () => {
|
||||
test.each([
|
||||
@@ -16,6 +19,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "app/globals.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: "@",
|
||||
},
|
||||
},
|
||||
@@ -29,6 +33,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "src/app/styles.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: "#",
|
||||
},
|
||||
},
|
||||
@@ -42,6 +47,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "styles/globals.css",
|
||||
tailwindVersion: "v4",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
@@ -55,6 +61,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "src/styles/globals.css",
|
||||
tailwindVersion: "v4",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: "@",
|
||||
},
|
||||
},
|
||||
@@ -68,6 +75,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "src/styles/globals.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: "14.2.4",
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
@@ -81,6 +89,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "src/styles/globals.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: "13.4.2",
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
@@ -94,6 +103,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "app/tailwind.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
@@ -107,6 +117,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "app/tailwind.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
@@ -120,6 +131,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.js",
|
||||
tailwindCssFile: "src/index.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: null,
|
||||
aliasPrefix: null,
|
||||
},
|
||||
},
|
||||
@@ -131,3 +143,134 @@ describe("get project info", async () => {
|
||||
).toStrictEqual(type)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getFrameworkVersion", () => {
|
||||
describe("Next.js version detection", () => {
|
||||
test.each([
|
||||
{
|
||||
name: "exact semver",
|
||||
input: "16.0.0",
|
||||
framework: "next-app",
|
||||
expected: "16.0.0",
|
||||
},
|
||||
{
|
||||
name: "caret prefix",
|
||||
input: "^16.1.2",
|
||||
framework: "next-app",
|
||||
expected: "16.1.2",
|
||||
},
|
||||
{
|
||||
name: "tilde prefix",
|
||||
input: "~15.0.3",
|
||||
framework: "next-app",
|
||||
expected: "15.0.3",
|
||||
},
|
||||
{
|
||||
name: "version range",
|
||||
input: ">=15.0.0 <16.0.0",
|
||||
framework: "next-app",
|
||||
expected: "15.0.0",
|
||||
},
|
||||
{
|
||||
name: "latest tag",
|
||||
input: "latest",
|
||||
framework: "next-app",
|
||||
expected: "latest",
|
||||
},
|
||||
{
|
||||
name: "canary tag",
|
||||
input: "canary",
|
||||
framework: "next-app",
|
||||
expected: "canary",
|
||||
},
|
||||
{
|
||||
name: "rc tag",
|
||||
input: "rc",
|
||||
framework: "next-app",
|
||||
expected: "rc",
|
||||
},
|
||||
])(
|
||||
`should extract $name ($input) -> $expected`,
|
||||
async ({ input, framework, expected }) => {
|
||||
const packageJson = {
|
||||
dependencies: {
|
||||
next: input,
|
||||
},
|
||||
}
|
||||
const version = await getFrameworkVersion(
|
||||
FRAMEWORKS[framework as keyof typeof FRAMEWORKS],
|
||||
packageJson
|
||||
)
|
||||
expect(version).toBe(expected)
|
||||
}
|
||||
)
|
||||
|
||||
test("should handle version in devDependencies", async () => {
|
||||
const packageJson = {
|
||||
devDependencies: {
|
||||
next: "16.0.0",
|
||||
},
|
||||
}
|
||||
const version = await getFrameworkVersion(
|
||||
FRAMEWORKS["next-pages"],
|
||||
packageJson
|
||||
)
|
||||
expect(version).toBe("16.0.0")
|
||||
})
|
||||
|
||||
test("should return null when next is not in dependencies", async () => {
|
||||
const packageJson = {
|
||||
dependencies: {
|
||||
react: "^18.0.0",
|
||||
},
|
||||
}
|
||||
const version = await getFrameworkVersion(
|
||||
FRAMEWORKS["next-app"],
|
||||
packageJson
|
||||
)
|
||||
expect(version).toBe(null)
|
||||
})
|
||||
|
||||
test("should return null when packageJson is null", async () => {
|
||||
const version = await getFrameworkVersion(FRAMEWORKS["next-app"], null)
|
||||
expect(version).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Other frameworks", () => {
|
||||
test.each([
|
||||
{
|
||||
name: "Vite",
|
||||
framework: "vite",
|
||||
package: "vite",
|
||||
version: "^5.0.0",
|
||||
},
|
||||
{
|
||||
name: "Remix",
|
||||
framework: "remix",
|
||||
package: "@remix-run/react",
|
||||
version: "^2.0.0",
|
||||
},
|
||||
{
|
||||
name: "Astro",
|
||||
framework: "astro",
|
||||
package: "astro",
|
||||
version: "^4.0.0",
|
||||
},
|
||||
])(
|
||||
`should return null for $name`,
|
||||
async ({ framework, package: pkg, version: ver }) => {
|
||||
const packageJson = {
|
||||
dependencies: {
|
||||
[pkg]: ver,
|
||||
},
|
||||
}
|
||||
const version = await getFrameworkVersion(
|
||||
FRAMEWORKS[framework as keyof typeof FRAMEWORKS],
|
||||
packageJson
|
||||
)
|
||||
expect(version).toBe(null)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user