From 3119f94d47426dae860001a4cfb4ef23396dd41d Mon Sep 17 00:00:00 2001 From: shadcn Date: Mon, 9 Jun 2025 16:17:44 +0400 Subject: [PATCH] feat: only show npm flag for react 18 deps (#7573) * feat: only show npm flag for react 18 deps * tests: update --- .changeset/shaggy-penguins-vanish.md | 5 + .../src/utils/updaters/update-dependencies.ts | 12 +- .../fixtures/project-npm-react19/package.json | 3 +- .../updaters/update-dependencies.test.ts | 296 ++++++++++-------- 4 files changed, 173 insertions(+), 143 deletions(-) create mode 100644 .changeset/shaggy-penguins-vanish.md diff --git a/.changeset/shaggy-penguins-vanish.md b/.changeset/shaggy-penguins-vanish.md new file mode 100644 index 0000000000..8ec8a31137 --- /dev/null +++ b/.changeset/shaggy-penguins-vanish.md @@ -0,0 +1,5 @@ +--- +"shadcn": patch +--- + +update npm flag diff --git a/packages/shadcn/src/utils/updaters/update-dependencies.ts b/packages/shadcn/src/utils/updaters/update-dependencies.ts index a12f9799be..6d272eaf52 100644 --- a/packages/shadcn/src/utils/updaters/update-dependencies.ts +++ b/packages/shadcn/src/utils/updaters/update-dependencies.ts @@ -34,7 +34,7 @@ export async function updateDependencies( // Offer to use --force or --legacy-peer-deps if using React 19 with npm. let flag = "" - if (isUsingReact19(config) && packageManager === "npm") { + if (shouldPromptForNpmFlag(config) && packageManager === "npm") { if (options.silent) { flag = "force" } else { @@ -98,12 +98,18 @@ export async function updateDependencies( dependenciesSpinner?.succeed() } -function isUsingReact19(config: Config) { +function shouldPromptForNpmFlag(config: Config) { const packageInfo = getPackageInfo(config.resolvedPaths.cwd, false) if (!packageInfo?.dependencies?.react) { return false } - return /^(?:\^|~)?19(?:\.\d+)*(?:-.*)?$/.test(packageInfo.dependencies.react) + const hasReact19 = /^(?:\^|~)?19(?:\.\d+)*(?:-.*)?$/.test( + packageInfo.dependencies.react + ) + const hasReactDayPicker8 = + packageInfo.dependencies["react-day-picker"]?.startsWith("8") + + return hasReact19 && hasReactDayPicker8 } diff --git a/packages/shadcn/test/fixtures/project-npm-react19/package.json b/packages/shadcn/test/fixtures/project-npm-react19/package.json index 61ef91b3c7..4032e8cfe0 100644 --- a/packages/shadcn/test/fixtures/project-npm-react19/package.json +++ b/packages/shadcn/test/fixtures/project-npm-react19/package.json @@ -5,6 +5,7 @@ "author": "shadcn", "license": "MIT", "dependencies": { - "react": "19.0.0" + "react": "19.0.0", + "react-day-picker": "8.0.0" } } diff --git a/packages/shadcn/test/utils/updaters/update-dependencies.test.ts b/packages/shadcn/test/utils/updaters/update-dependencies.test.ts index 7e0c36e682..f13b576a6b 100644 --- a/packages/shadcn/test/utils/updaters/update-dependencies.test.ts +++ b/packages/shadcn/test/utils/updaters/update-dependencies.test.ts @@ -1,139 +1,157 @@ -import { vi, describe, afterEach, test, expect } from "vitest" -import { execa } from "execa" -import prompts from "prompts" -import { updateDependencies } from "../../../src/utils/updaters/update-dependencies" -import path from "path" - -vi.mock("execa") -vi.mock("prompts") - -describe("updateDependencies", () => { - afterEach(() => { - vi.restoreAllMocks() - }) - - test.each([ - { - description: "npm without react 19 includes no additional flags", - options: { silent: true }, - dependencies: ["first", "second", "third"], - devDependencies: ["fourth"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-npm") - } - }, - expectedPackageManager: "npm", - expectedArgs: ["install", "first", "second", "third"], - expectedDevArgs: ["install", "-D", "fourth"] - }, - { - description: "npm with react 19 applies force prompt when silent", - options: { silent: true }, - dependencies: ["first", "second", "third"], - devDependencies: ["fourth"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-npm-react19") - } - }, - expectedPackageManager: "npm", - expectedArgs: ["install", "--force", "first", "second", "third"], - expectedDevArgs: ["install", "--force", "-D", "fourth"] - }, - { - description: "npm with react 19 prompts for flag when not silent", - flagPrompt: "legacy-peer-deps", - dependencies: ["first", "second", "third"], - devDependencies: ["fourth"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-npm-react19") - } - }, - expectedPackageManager: "npm", - expectedArgs: ["install", "--legacy-peer-deps", "first", "second", "third"], - expectedDevArgs: ["install", "--legacy-peer-deps", "-D", "fourth"] - }, - { - description: "deno uses npm: package prefix", - dependencies: ["first", "second", "third"], - devDependencies: ["fourth"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-deno") - } - }, - expectedPackageManager: "deno", - expectedArgs: ["add", "npm:first", "npm:second", "npm:third"], - expectedDevArgs: ["add", "-D", "npm:fourth"] - }, - { - description: "bun uses bun", - dependencies: ["first", "second", "third"], - devDependencies: ["fourth"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-bun") - } - }, - expectedPackageManager: "bun", - expectedArgs: ["add", "first", "second", "third"], - expectedDevArgs: ["add", "-D", "fourth"] - }, - { - description: "pnpm uses pnpm", - dependencies: ["first", "second", "third"], - devDependencies: ["fourth"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-pnpm") - } - }, - expectedPackageManager: "pnpm", - expectedArgs: ["add", "first", "second", "third"], - expectedDevArgs: ["add", "-D", "fourth"] - }, - { - description: "deduplicates input dependencies", - options: { silent: true }, - dependencies: ["first", "first"], - devDependencies: ["second", "second"], - config: { - resolvedPaths: { - cwd: path.resolve(__dirname, "../../fixtures/project-npm") - } - }, - expectedPackageManager: "npm", - expectedArgs: ["install", "first"], - expectedDevArgs: ["install", "-D", "second"] - } - ])("$description", async ({ options, flagPrompt, config, dependencies, devDependencies, expectedPackageManager, expectedArgs, expectedDevArgs }) => { - - vi.mocked(prompts).mockResolvedValue({ flag: flagPrompt }) - - await updateDependencies( - dependencies, - devDependencies, - config, - options ?? {} - ) - - if (flagPrompt) { - expect(prompts).toHaveBeenCalled() - } - - - expect(execa).toHaveBeenCalledWith( - expectedPackageManager, - expectedArgs, - { cwd: config?.resolvedPaths.cwd } - ) - - expect(execa).toHaveBeenCalledWith( - expectedPackageManager, - expectedDevArgs, - { cwd: config?.resolvedPaths.cwd } - ) - }) -}) \ No newline at end of file +import path from "path" +import { execa } from "execa" +import prompts from "prompts" +import { afterEach, describe, expect, test, vi } from "vitest" + +import { updateDependencies } from "../../../src/utils/updaters/update-dependencies" + +vi.mock("execa") +vi.mock("prompts") + +describe("updateDependencies", () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + test.each([ + { + description: + "npm without react-day-picker v8 includes no additional flags", + options: { silent: true }, + dependencies: ["first", "second", "third"], + devDependencies: ["fourth"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-npm"), + }, + }, + expectedPackageManager: "npm", + expectedArgs: ["install", "first", "second", "third"], + expectedDevArgs: ["install", "-D", "fourth"], + }, + { + description: + "npm with react-day-picker v8 applies force prompt when silent", + options: { silent: true }, + dependencies: ["first", "second", "third"], + devDependencies: ["fourth"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-npm-react19"), + }, + }, + expectedPackageManager: "npm", + expectedArgs: ["install", "--force", "first", "second", "third"], + expectedDevArgs: ["install", "--force", "-D", "fourth"], + }, + { + description: + "npm with react-day-picker v8 prompts for flag when not silent", + flagPrompt: "legacy-peer-deps", + dependencies: ["first", "second", "third"], + devDependencies: ["fourth"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-npm-react19"), + }, + }, + expectedPackageManager: "npm", + expectedArgs: [ + "install", + "--legacy-peer-deps", + "first", + "second", + "third", + ], + expectedDevArgs: ["install", "--legacy-peer-deps", "-D", "fourth"], + }, + { + description: "deno uses npm: package prefix", + dependencies: ["first", "second", "third"], + devDependencies: ["fourth"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-deno"), + }, + }, + expectedPackageManager: "deno", + expectedArgs: ["add", "npm:first", "npm:second", "npm:third"], + expectedDevArgs: ["add", "-D", "npm:fourth"], + }, + { + description: "bun uses bun", + dependencies: ["first", "second", "third"], + devDependencies: ["fourth"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-bun"), + }, + }, + expectedPackageManager: "bun", + expectedArgs: ["add", "first", "second", "third"], + expectedDevArgs: ["add", "-D", "fourth"], + }, + { + description: "pnpm uses pnpm", + dependencies: ["first", "second", "third"], + devDependencies: ["fourth"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-pnpm"), + }, + }, + expectedPackageManager: "pnpm", + expectedArgs: ["add", "first", "second", "third"], + expectedDevArgs: ["add", "-D", "fourth"], + }, + { + description: "deduplicates input dependencies", + options: { silent: true }, + dependencies: ["first", "first"], + devDependencies: ["second", "second"], + config: { + resolvedPaths: { + cwd: path.resolve(__dirname, "../../fixtures/project-npm"), + }, + }, + expectedPackageManager: "npm", + expectedArgs: ["install", "first"], + expectedDevArgs: ["install", "-D", "second"], + }, + ])( + "$description", + async ({ + options, + flagPrompt, + config, + dependencies, + devDependencies, + expectedPackageManager, + expectedArgs, + expectedDevArgs, + }) => { + vi.mocked(prompts).mockResolvedValue({ flag: flagPrompt }) + + await updateDependencies( + dependencies, + devDependencies, + config, + options ?? {} + ) + + if (flagPrompt) { + expect(prompts).toHaveBeenCalled() + } + + expect(execa).toHaveBeenCalledWith(expectedPackageManager, expectedArgs, { + cwd: config?.resolvedPaths.cwd, + }) + + expect(execa).toHaveBeenCalledWith( + expectedPackageManager, + expectedDevArgs, + { cwd: config?.resolvedPaths.cwd } + ) + } + ) +})