feat(shadcn): add next 16 to init (#8550)

* feat: add next 16 init

* chore: changeset

* fix: tests

* fix
This commit is contained in:
shadcn
2025-10-23 17:20:45 +04:00
committed by GitHub
parent b70059b25b
commit 6bddba986d
4 changed files with 93 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add Next.js 16 support for init command

View File

@@ -80,7 +80,8 @@ export const initOptionsSchema = z.object({
return true
},
{
message: "Invalid template. Please use 'next' or 'next-monorepo'.",
message:
"Invalid template. Please use 'next', 'next-16' or 'next-monorepo'.",
}
),
baseColor: z
@@ -109,7 +110,7 @@ export const init = new Command()
.argument("[components...]", "names, url or local path to component")
.option(
"-t, --template <template>",
"the template to use. (next, next-monorepo)"
"the template to use. (next, next-16, next-monorepo)"
)
.option(
"-b, --base-color <base-color>",

View File

@@ -121,7 +121,33 @@ describe("createProject", () => {
expect(execa).toHaveBeenCalledWith(
"npx",
expect.arrayContaining(["create-next-app@latest", "/test/my-app"]),
expect.arrayContaining(["create-next-app@15", "/test/my-app"]),
expect.any(Object)
)
})
it("should create a Next.js 16 project when next-16 template is selected", async () => {
vi.mocked(prompts).mockResolvedValue({ type: "next-16", name: "my-app" })
const result = await createProject({
cwd: "/test",
force: false,
srcDir: false,
})
expect(result).toEqual({
projectPath: "/test/my-app",
projectName: "my-app",
template: TEMPLATES["next-16"],
})
expect(execa).toHaveBeenCalledWith(
"npx",
expect.arrayContaining([
"create-next-app@latest",
"/test/my-app",
"--no-react-compiler",
]),
expect.any(Object)
)
})
@@ -195,4 +221,38 @@ describe("createProject", () => {
expect(mockExit).toHaveBeenCalledWith(1)
})
it("should include --no-react-compiler flag for Next.js 16 (latest)", async () => {
vi.mocked(prompts).mockResolvedValue({ type: "next-16", name: "my-app" })
await createProject({
cwd: "/test",
force: false,
srcDir: false,
})
expect(execa).toHaveBeenCalledWith(
"npx",
expect.arrayContaining(["--no-react-compiler"]),
expect.any(Object)
)
})
it("should not include --no-react-compiler flag for Next.js 15", async () => {
vi.mocked(prompts).mockResolvedValue({ type: "next", name: "my-app" })
await createProject({
cwd: "/test",
force: false,
srcDir: false,
})
const execaCalls = vi.mocked(execa).mock.calls
const createNextCall = execaCalls.find(
(call) => Array.isArray(call[1]) && call[1].includes("create-next-app@15")
)
expect(createNextCall).toBeDefined()
expect(createNextCall?.[1]).not.toContain("--no-react-compiler")
})
})

View File

@@ -17,6 +17,7 @@ const MONOREPO_TEMPLATE_URL =
export const TEMPLATES = {
next: "next",
"next-16": "next-16",
"next-monorepo": "next-monorepo",
} as const
@@ -37,7 +38,7 @@ export async function createProject(
: "next"
let projectName: string =
template === TEMPLATES.next ? "my-app" : "my-monorepo"
let nextVersion = "latest"
let nextVersion = "15"
const isRemoteComponent =
options.components?.length === 1 &&
@@ -72,7 +73,8 @@ export async function createProject(
options.cwd
)} does not contain a package.json file.\n Would you like to start a new project?`,
choices: [
{ title: "Next.js", value: "next" },
{ title: "Next.js 15", value: "next" },
{ title: "Next.js 16", value: "next-16" },
{ title: "Next.js (Monorepo)", value: "next-monorepo" },
],
initial: 0,
@@ -92,6 +94,10 @@ export async function createProject(
template = type ?? template
projectName = name
if (type === "next-16") {
nextVersion = "latest"
}
}
const packageManager = await getPackageManager(options.cwd, {
@@ -125,7 +131,7 @@ export async function createProject(
process.exit(1)
}
if (template === TEMPLATES.next) {
if (template === TEMPLATES.next || template === TEMPLATES["next-16"]) {
await createNextProject(projectPath, {
version: nextVersion,
cwd: options.cwd,
@@ -157,7 +163,9 @@ async function createNextProject(
}
) {
const createSpinner = spinner(
`Creating a new Next.js project. This may take a few minutes.`
`Creating a new Next.js ${
options.version.startsWith("latest") ? "16" : "15"
} project. This may take a few minutes.`
).start()
// Note: pnpm fails here. Fallback to npx with --use-PACKAGE-MANAGER.
@@ -179,6 +187,13 @@ async function createNextProject(
args.push("--turbopack")
}
if (
options.version.startsWith("latest") ||
options.version.startsWith("canary")
) {
args.push("--no-react-compiler")
}
try {
await execa(
"npx",
@@ -195,7 +210,11 @@ async function createNextProject(
process.exit(1)
}
createSpinner?.succeed("Creating a new Next.js project.")
createSpinner?.succeed(
`Creating a new Next.js ${
options.version.startsWith("latest") ? "16" : "15"
} project.`
)
}
async function createMonorepoProject(