Compare commits

...

44 Commits

Author SHA1 Message Date
github-actions[bot]
b34f3fdc4f chore(release): version packages (#7941)
* chore(release): version packages

* chore: deps

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-08-27 19:10:35 +04:00
shadcn
2ecf876fa1 chore: beta to latest 2025-08-27 12:35:02 +04:00
shadcn
dcd2c3ef14 chore: beta to latest 2025-08-27 12:27:10 +04:00
shadcn
17422714f6 feat(shadcn): mcp init (#8086) 2025-08-27 12:05:21 +04:00
shadcn
fc27ba2692 fix(shadcn): fix --defaults options (#8081)
* fix(shadcn): fix defaults options

* chore: changeset
2025-08-26 15:49:43 +04:00
shadcn
f854190b53 fix(shadcn): load env (#8061) 2025-08-25 14:05:12 +04:00
shadcn
396275e46a feat(www): switch to md from mdx (#8019)
* feat(www): switch to md from mdx

* feat(www): update url
2025-08-13 16:08:20 +04:00
shadcn
296feb28a2 feat(shadcn): new mcp server (#8012)
* feat(shadcn): add getRegistriesConfig api

* feat(shadcn): add new mcp command

* feat(shadcn): add get_item_examples_from_registries and get_add_command_for_items

* feat: remove getRegistriesConfig

* chore: changeset
2025-08-13 11:15:26 +04:00
shadcn
a941287411 deps(shadcn): bump all dependencies (#8004)
* deps(shadcn): bump all dependencies

* chore: pin deps for fumadocs
2025-08-11 20:21:40 +04:00
shadcn
2e34c95c4e feat(shadcn): update search results format (#8003) 2025-08-11 15:52:02 +04:00
shadcn
fed7e3bfdc feat(shadcn): update signatures of apis (#8001)
* feat(shadcn): improve apis signature

* feat(shadcn): update signature of apis

* fix: tests snapshot
2025-08-11 14:34:10 +04:00
shadcn
4f5333ea7a feat(shadcn): add view command (#7994)
* feat(shadcn): add view command

* test(shadcn): add tests for view command

* feat(shadcn): allow shadow config in view command

* chore: changeset

* test(shadcn): skip view

* test(shadcn): update view port number

* feat(shadcn): add list command

* fix

* feat(shadcn): implement search command

* fix: tests

* fix

* chore: update changesets
2025-08-11 13:01:05 +04:00
shadcn
b5b8deedde chore: update changesets 2025-08-10 18:48:43 +04:00
shadcn
7d71b02fb1 feat(shadcn): add getRegistry (#7992) 2025-08-10 18:13:50 +04:00
shadcn
b3639227d0 feat(shadcn): deprecate fetchRegistry and resolveRegistryTree (#7990) 2025-08-10 16:25:04 +04:00
shadcn
a4a3600757 feat: move schema exports to shadcn/schema (#7989) 2025-08-10 16:05:53 +04:00
shadcn
a426fea941 refactor(shadcn): add getRegistryItems and resolveRegistryItems (#7983)
* feat(shadcn): refactor fetchFromRegistry

* refactor(shadcn): better api

* chore: changeset

* fix

* fix

* refactor

* refactor(shadcn): update getRegistryItems

* refactor(shadcn): error handling

* fix: getRegistryItems header context

* fix: tests

* feat(shadcn): export errors

* refactor(shadcn): getRegistryItems getRegistry

* fix

* fix

* fix

* fix

* chore: changeset

* chore: remove minor changeset
2025-08-10 15:20:38 +04:00
shadcn
6e870c3993 feat(shadcn): copy registry.json for build command (#7972)
* feat(shadcn): copy registry.json on build

* chore: changeset
2025-08-07 20:48:59 +04:00
shadcn
68aa3389de tests(shadcn): add more tests to cover registryResolveItemTree (#7971)
* tests(shadcn): add more registryResolveItemTree tests

* tests(shadcn): add more tests to cover registryResolveItemTree
2025-08-07 15:44:23 +04:00
shadcn
2e9ccede8f feat(shadcn): deduplicate files by target (#7969) 2025-08-07 14:33:17 +04:00
shadcn
fc8927a1f9 fix(shadcn): monorepo in nix system (#7962)
* debug: do not cd after init

* chore: changeset
2025-08-07 14:25:23 +04:00
shadcn
ccfd14946b feat: update schema.json to allow registries field (#7959) 2025-08-06 16:55:02 +04:00
shadcn
01c02b289a feat: add registry.json for all styles (#7958)
* feat: add style to registry

* feat: build registry.json for all styles
2025-08-06 16:22:32 +04:00
shadcn
a80ab37483 feat(shadcn): update file handling for monorepo (#7955)
* feat(shadcn): update monorepo handling

* feat(shadcn): update file handling for monorepo

* chore: changeset
2025-08-06 15:13:51 +04:00
shadcn
469250115f feat: update dependencies in monorepo (#7956) 2025-08-06 15:03:28 +04:00
shadcn
2c164b0f22 feat(shadcn): update registry dependencies resolution algorithm (#7948)
* feat(shadcn): update dependency resolution algorithm

* feat(shadcn): rename style to base-style

* feat(shadcn): init from namespaced

* fix(shadcn): force validation early

* chore: changeset

* fix(shadcn): headers

* fix: smh

* fix(shadcn): restore backup on exit and error
2025-08-06 13:38:08 +04:00
shadcn
578f83cbef chore: changeset (#7940) 2025-08-04 14:40:36 +04:00
shadcn
07eda36b13 feat(shadcn): add namespaced registries support (#7919)
* chore(shadcn): implement registries poc

* feat(shadcn): refactor our initial implementation

* feat(shadcn): properly resolve namespaced registryDependencies

* feat(shadcn): resolve namespaced registries recursively

* fix

* feat(shadcn): implement dotenv support

* test(shadcn): mock shadcn registry

* fix

* fix

* fix

* refactor(shadcn): update functions and tests

* refactor(shadcn): add fetchFromRegistry (#7937)

* fix

* feat(shadcn): add shadcn as a built-in registry

* fix

* feat(shadcn): update no framework and shadcn
2025-08-04 14:35:41 +04:00
shadcn
0eccdc9c5f docs: add docs for envVars 2025-07-30 12:22:40 +04:00
shadcn
0940c6aec7 chore: update deps 2025-07-30 12:11:45 +04:00
github-actions[bot]
e244952500 chore(release): version packages (#7909)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-30 12:10:45 +04:00
shadcn
0e3d6b24d3 test: fix flaky remote registry test (#7910)
* test: fix flaky remote registry test

* fix

* fix: test

* fix

* fix

* fix

* fix

* fix

* tests: cleanup
2025-07-30 12:06:13 +04:00
shadcn
cef5af9ed3 ci: bump version for changeset action 2025-07-29 17:08:48 +04:00
shadcn
6deb0fdbb6 chore: remove tests form changesets 2025-07-29 17:01:45 +04:00
shadcn
e9ae79f874 ci: fix 2025-07-29 16:57:16 +04:00
shadcn
d891132f2a test: remove init tests (#7908)
* test(shadcn): remove init test

* chore: changeset
2025-07-29 16:45:52 +04:00
shadcn
873f7f2773 feat: add tests package (#7907)
* feat: add tests package

* fix

* fix

* debug

* debug

* debug

* fix

* debug

* fix: no concurrent

* fix

* test: add vite-app tests

* test: add tests
2025-07-29 16:31:10 +04:00
shadcn
e6778dee87 feat(shadcn): add envVars to schema (#7902)
* feat(shadcn): add envVars to schema

* fix(shadcn): tests

* chore: changeset
2025-07-28 12:14:46 +04:00
shadcn
97a8de1c1b feat: update handling of env files in registry (#7896)
* feat: handle env update

* tests(shadcn): add tests for env helpers

* test(shadcn): update files test

* feat(shadcn): implement file alternatives

* test(shadcn): fix alternative handling

* fix(shadcn): env var logging

* test(shadcn): add tests for multi line env

* chore: changeset

* ci: update
2025-07-27 12:28:39 +04:00
shadcn
19d7fbb731 Use v4 blocks for Open in v0 (#7898)
* feat(www): use v4 blocks for v0

* fix: defaultIndex
2025-07-27 11:34:07 +04:00
shadcn
a9ab05ad83 Merge branch 'main' of github.com:shadcn-ui/ui 2025-07-23 11:57:19 +04:00
shadcn
6ac114ae68 feat: update hero 2025-07-23 11:57:05 +04:00
Mohit Khatri
d5770e4350 fix: resolve table overflow styling issues (#7874) 2025-07-23 11:32:05 +04:00
shadcn
4730276256 fix: spacing 2025-07-23 11:30:23 +04:00
221 changed files with 65353 additions and 8543 deletions

View File

@@ -7,5 +7,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["www", "v4"]
"ignore": ["www", "v4", "tests"]
}

View File

@@ -41,7 +41,7 @@ jobs:
- name: Create Version PR or Publish to NPM
id: changesets
uses: changesets/action@v1.4.1
uses: changesets/action@v1
with:
commit: "chore(release): version packages"
title: "chore(release): version packages"

View File

@@ -8,6 +8,9 @@ jobs:
test:
runs-on: ubuntu-latest
name: pnpm test
env:
NEXT_PUBLIC_APP_URL: http://localhost:4000
NEXT_PUBLIC_V0_URL: https://v0.dev
steps:
- uses: actions/checkout@v3
with:
@@ -39,4 +42,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build packages
run: pnpm build --filter=shadcn
- run: pnpm test

View File

@@ -15,9 +15,9 @@ import { PageNav } from "@/components/page-nav"
import { ThemeSelector } from "@/components/theme-selector"
import { Button } from "@/registry/new-york-v4/ui/button"
const title = "Build your Component Library"
const title = "The Foundation for your Design System"
const description =
"A set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code."
"A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code."
export const dynamic = "force-static"
export const revalidate = false
@@ -51,14 +51,14 @@ export default function IndexPage() {
<div className="flex flex-1 flex-col">
<PageHeader>
<Announcement />
<PageHeaderHeading>{title}</PageHeaderHeading>
<PageHeaderHeading className="max-w-4xl">{title}</PageHeaderHeading>
<PageHeaderDescription>{description}</PageHeaderDescription>
<PageActions>
<Button asChild size="sm">
<Link href="/docs/installation">Get Started</Link>
</Button>
<Button asChild size="sm" variant="ghost">
<Link href="/blocks">Browse Blocks</Link>
<Link href="/docs/components">View Components</Link>
</Button>
</PageActions>
</PageHeader>

View File

@@ -75,7 +75,7 @@ export function DataTable<TData, TValue>({
return (
<div className="flex flex-col gap-4">
<DataTableToolbar table={table} />
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,7 +1,7 @@
import * as React from "react"
import { Metadata } from "next"
import { notFound } from "next/navigation"
import { registryItemSchema } from "shadcn/registry"
import { registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { siteConfig } from "@/lib/config"

View File

@@ -1,5 +1,5 @@
import * as React from "react"
import { registryItemFileSchema } from "shadcn/registry"
import { registryItemFileSchema } from "shadcn/schema"
import { z } from "zod"
import { highlightCode } from "@/lib/highlight-code"

View File

@@ -17,7 +17,7 @@ import {
Terminal,
} from "lucide-react"
import { ImperativePanelHandle } from "react-resizable-panels"
import { registryItemFileSchema, registryItemSchema } from "shadcn/registry"
import { registryItemFileSchema, registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { trackEvent } from "@/lib/events"

View File

@@ -209,7 +209,7 @@ export function CardsPayments() {
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,5 +1,5 @@
import * as React from "react"
import { registryItemSchema } from "shadcn/registry"
import { registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { highlightCode } from "@/lib/highlight-code"

View File

@@ -28,7 +28,7 @@ Help me understand how to use it. Be ready to explain concepts, give examples, o
const menuItems = {
markdown: (url: string) => (
<a href={`${url}.mdx`} target="_blank" rel="noopener noreferrer">
<a href={`${url}.md`} target="_blank" rel="noopener noreferrer">
<svg strokeLinejoin="round" viewBox="0 0 22 16">
<path
fillRule="evenodd"
@@ -54,7 +54,7 @@ const menuItems = {
>
<path d="M56 50.203V14h14v46.156C70 65.593 65.593 70 60.156 70c-2.596 0-5.158-1-7-2.843L0 14h19.797L56 50.203ZM147 56h-14V23.953L100.953 56H133v14H96.687C85.814 70 77 61.186 77 50.312V14h14v32.156L123.156 14H91V0h36.312C138.186 0 147 8.814 147 19.688V56Z" />
</svg>
Open in v0
<span className="-translate-x-[2px]">Open in v0</span>
</a>
),
chatgpt: (url: string) => (

View File

@@ -3,7 +3,7 @@ import { Icons } from "@/components/icons"
import { Button } from "@/registry/new-york-v4/ui/button"
// v0 uses the default style.
const V0_STYLE = "default"
const V0_STYLE = "new-york-v4"
export function OpenInV0Button({
name,

View File

@@ -185,7 +185,7 @@ export function DataTable<TData, TValue>({
})
return (
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
@@ -425,7 +425,7 @@ export function DataTable<TData, TValue>({
return (
<div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
{ // .... }
</Table>
@@ -499,7 +499,7 @@ export function DataTable<TData, TValue>({
return (
<div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
@@ -602,7 +602,7 @@ export function DataTable<TData, TValue>({
className="max-w-sm"
/>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
@@ -715,7 +715,7 @@ export function DataTable<TData, TValue>({
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
@@ -805,7 +805,7 @@ export function DataTable<TData, TValue>({
return (
<div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table />
</div>
</div>

View File

@@ -369,6 +369,31 @@ Note: you need to define both `@keyframes` in css and `theme` in cssVars to use
}
```
## Add environment variables
You can add environment variables using the `envVars` field.
```json title="example-item.json" showLineNumbers {5-9}
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-item",
"type": "registry:item",
"envVars": {
"NEXT_PUBLIC_APP_URL": "http://localhost:4000",
"DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/postgres",
"OPENAI_API_KEY": ""
}
}
```
Environment variables are added to the `.env.local` or `.env` file. Existing variables are not overwritten.
<Callout>
**IMPORTANT:** Use `envVars` to add development or example variables. Do NOT use it to add production variables.
</Callout>
## Universal Items
As of `2.9.0`, you can create universal items that can be installed without framework detection or components.json.

View File

@@ -296,13 +296,35 @@ Use `css` to add new rules to the project's CSS file eg. `@layer base`, `@layer
}
```
### envVars
Use `envVars` to add environment variables to your registry item.
```json title="registry-item.json" showLineNumbers
{
"envVars": {
"NEXT_PUBLIC_APP_URL": "http://localhost:4000",
"DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/postgres",
"OPENAI_API_KEY": ""
}
}
```
Environment variables are added to the `.env.local` or `.env` file. Existing variables are not overwritten.
<Callout>
**IMPORTANT:** Use `envVars` to add development or example variables. Do NOT use it to add production variables.
</Callout>
### docs
Use `docs` to show custom documentation or message when installing your registry item via the CLI.
```json title="registry-item.json" showLineNumbers
{
"docs": "Remember to add the FOO_BAR environment variable to your .env file."
"docs": "To get an OPENAI_API_KEY, sign up for an account at https://platform.openai.com."
}
```

View File

@@ -1,6 +1,6 @@
"use server"
import { registryItemSchema } from "shadcn/registry"
import { registryItemSchema } from "shadcn/schema"
import { z } from "zod"
export async function getAllBlockIds(

View File

@@ -3,7 +3,7 @@ export const siteConfig = {
url: "https://ui.shadcn.com",
ogImage: "https://ui.shadcn.com/og.jpg",
description:
"A set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code.",
"A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code.",
links: {
twitter: "https://twitter.com/shadcn",
github: "https://github.com/shadcn-ui/ui",

View File

@@ -1,7 +1,7 @@
import { promises as fs } from "fs"
import { tmpdir } from "os"
import path from "path"
import { registryItemFileSchema, registryItemSchema } from "shadcn/registry"
import { registryItemFileSchema, registryItemSchema } from "shadcn/schema"
import { Project, ScriptKind } from "ts-morph"
import { z } from "zod"

View File

@@ -68,12 +68,17 @@ const nextConfig = {
destination: "/view/:name",
permanent: true,
},
{
source: "/docs/:path*.mdx",
destination: "/docs/:path*.md",
permanent: true,
},
]
},
rewrites() {
return [
{
source: "/docs/:path*.mdx",
source: "/docs/:path*.md",
destination: "/llm/:path*",
},
]

View File

@@ -54,7 +54,7 @@
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.7",
"@tabler/icons-react": "^3.31.0",
"@tailwindcss/postcss": "^4.0.1",
"@tailwindcss/postcss": "^4.1.11",
"@tanstack/react-table": "^8.9.1",
"@vercel/analytics": "^1.4.1",
"change-case": "^5.4.4",
@@ -65,10 +65,10 @@
"date-fns": "^4.1.0",
"embla-carousel-autoplay": "8.5.2",
"embla-carousel-react": "8.5.2",
"fumadocs-core": "^15.3.1",
"fumadocs-docgen": "^2.0.0",
"fumadocs-mdx": "^11.6.3",
"fumadocs-ui": "^15.3.1",
"fumadocs-core": "15.3.1",
"fumadocs-docgen": "2.0.0",
"fumadocs-mdx": "11.6.3",
"fumadocs-ui": "15.3.1",
"input-otp": "^1.4.2",
"jotai": "^2.1.0",
"little-date": "^1.0.0",
@@ -86,7 +86,7 @@
"recharts": "2.15.1",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"shadcn": "2.9.3",
"shadcn": "3.0.0",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",
@@ -108,7 +108,7 @@
"eslint-config-next": "15.3.1",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^4.1.7",
"tailwindcss": "^4.1.11",
"tw-animate-css": "^1.2.4",
"typescript": "^5",
"unist-builder": "3.0.0",

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,8 @@
{
"name": "default",
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "index",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
@@ -9,6 +11,7 @@
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
@@ -16,6 +19,5 @@
]
}
},
"cssVars": {},
"files": []
"cssVars": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "style",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react"
],
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
"require(\"tailwindcss-animate\")"
]
}
},
"cssVars": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "style",
"type": "registry:style",
"dependencies": [
"class-variance-authority",
"lucide-react"
],
"devDependencies": [
"tw-animate-css"
],
"registryDependencies": [
"utils"
],
"files": [],
"cssVars": {}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,8 @@
{
"name": "new-york",
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "index",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
@@ -9,6 +11,7 @@
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
@@ -16,6 +19,5 @@
]
}
},
"cssVars": {},
"files": []
"cssVars": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "style",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react"
],
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
"require(\"tailwindcss-animate\")"
]
}
},
"cssVars": {}
}

View File

@@ -56,6 +56,42 @@
}
},
"required": ["utils", "components"]
},
"registries": {
"type": "object",
"patternProperties": {
"^@": {
"oneOf": [
{
"type": "string",
"pattern": "\\{name\\}"
},
{
"type": "object",
"properties": {
"url": {
"type": "string",
"pattern": "\\{name\\}"
},
"params": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": ["url"]
}
]
}
},
"additionalProperties": false
}
},
"required": ["style", "tailwind", "rsc", "aliases"]

View File

@@ -191,6 +191,13 @@
]
}
},
"envVars": {
"type": "object",
"description": "Environment variables required by the registry item. Key-value pairs that will be added to the project's .env file.",
"additionalProperties": {
"type": "string"
}
},
"meta": {
"type": "object",
"description": "Additional metadata for the registry item. This is an object with any key value pairs.",

View File

@@ -18,6 +18,22 @@
"files": [],
"cssVars": {}
},
{
"name": "style",
"type": "registry:style",
"dependencies": [
"class-variance-authority",
"lucide-react"
],
"devDependencies": [
"tw-animate-css"
],
"registryDependencies": [
"utils"
],
"files": [],
"cssVars": {}
},
{
"name": "accordion",
"type": "registry:ui",

View File

@@ -16,6 +16,16 @@ export const Index: Record<string, any> = {
categories: undefined,
meta: undefined,
},
"style": {
name: "style",
description: "",
type: "registry:style",
registryDependencies: ["utils"],
files: [],
component: null,
categories: undefined,
meta: undefined,
},
"accordion": {
name: "accordion",
description: "",

View File

@@ -1,4 +1,4 @@
import { registryItemSchema, type Registry } from "shadcn/registry"
import { registryItemSchema, type Registry } from "shadcn/schema"
import { z } from "zod"
import { blocks } from "@/registry/registry-blocks"
@@ -19,6 +19,16 @@ const DEPRECATED_ITEMS = [
"toast-with-title",
]
// Shared between index and style for backward compatibility.
const NEW_YORK_V4_STYLE = {
type: "registry:style",
dependencies: ["class-variance-authority", "lucide-react"],
devDependencies: ["tw-animate-css"],
registryDependencies: ["utils"],
cssVars: {},
files: [],
}
export const registry = {
name: "shadcn/ui",
homepage: "https://ui.shadcn.com",
@@ -26,12 +36,11 @@ export const registry = {
[
{
name: "index",
type: "registry:style",
dependencies: ["class-variance-authority", "lucide-react"],
devDependencies: ["tw-animate-css"],
registryDependencies: ["utils"],
cssVars: {},
files: [],
...NEW_YORK_V4_STYLE,
},
{
name: "style",
...NEW_YORK_V4_STYLE,
},
...ui,
...blocks,

View File

@@ -257,7 +257,6 @@ export function ChartAreaInteractive() {
/>
<ChartTooltip
cursor={false}
defaultIndex={isMobile ? -1 : 10}
content={
<ChartTooltipContent
labelFormatter={(value) => {

View File

@@ -233,7 +233,7 @@ export default function DataTableDemo() {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const blocks: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const charts: Registry["items"] = [
// Area Charts

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const examples: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const hooks: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const internal: Registry["items"] = [
// Do not move this. They are intentionally here for registry capture.

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const lib: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const themes: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const ui: Registry["items"] = [
{

View File

@@ -61,7 +61,7 @@ export const Index: Record<string, any> = {`
index += `
}`
console.log(`#️⃣ ${Object.keys(registry.items).length} components found`)
console.log(`#️⃣ ${Object.keys(registry.items).length} items found`)
// Write style index.
rimraf.sync(path.join(process.cwd(), "registry/__index__.tsx"))
@@ -93,6 +93,16 @@ async function buildRegistryJsonFile() {
path.join(process.cwd(), `registry.json`),
JSON.stringify(fixedRegistry, null, 2)
)
// 3. Copy the registry.json to the www/public/r/styles/new-york-v4 directory.
await fs.cp(
path.join(process.cwd(), "registry.json"),
path.join(
process.cwd(),
"../www/public/r/styles/new-york-v4/registry.json"
),
{ recursive: true }
)
}
async function buildRegistry() {

View File

@@ -33,7 +33,6 @@ export default defineConfig({
export const docs = defineDocs({
dir: "content/docs",
docs: {
// @ts-expect-error - TODO: fix the type.
schema: frontmatterSchema.extend({
links: z
.object({

View File

@@ -5,6 +5,28 @@ import * as React from "react"
export const Index: Record<string, any> = {
"new-york": {
"index": {
name: "index",
description: "",
type: "registry:style",
registryDependencies: ["utils"],
files: [],
categories: undefined,
component: React.lazy(() => import("@/registry/new-york/style/index")),
source: "",
meta: undefined,
},
"style": {
name: "style",
description: "",
type: "registry:style",
registryDependencies: ["utils"],
files: [],
categories: undefined,
component: React.lazy(() => import("@/registry/new-york/style/style")),
source: "",
meta: undefined,
},
"accordion": {
name: "accordion",
description: "",
@@ -5838,6 +5860,28 @@ export const Index: Record<string, any> = {
meta: undefined,
},
}, "default": {
"index": {
name: "index",
description: "",
type: "registry:style",
registryDependencies: ["utils"],
files: [],
categories: undefined,
component: React.lazy(() => import("@/registry/default/style/index")),
source: "",
meta: undefined,
},
"style": {
name: "style",
description: "",
type: "registry:style",
registryDependencies: ["utils"],
files: [],
categories: undefined,
component: React.lazy(() => import("@/registry/default/style/style")),
source: "",
meta: undefined,
},
"accordion": {
name: "accordion",
description: "",

View File

@@ -70,7 +70,7 @@ export function DataTable<TData, TValue>({
return (
<div className="space-y-4">
<DataTableToolbar table={table} />
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,5 +1,5 @@
import * as React from "react"
import { registryItemFileSchema } from "shadcn/registry"
import { registryItemFileSchema } from "shadcn/schema"
import { z } from "zod"
import { highlightCode } from "@/lib/highlight-code"

View File

@@ -16,7 +16,7 @@ import {
Terminal,
} from "lucide-react"
import { ImperativePanelHandle } from "react-resizable-panels"
import { registryItemFileSchema, registryItemSchema } from "shadcn/registry"
import { registryItemFileSchema, registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { trackEvent } from "@/lib/events"

View File

@@ -239,7 +239,7 @@ export function CardsDataTable() {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,5 +1,5 @@
import * as React from "react"
import { registryItemSchema } from "shadcn/registry"
import { registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { highlightCode } from "@/lib/highlight-code"

View File

@@ -185,7 +185,7 @@ export function DataTable<TData, TValue>({
})
return (
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
@@ -425,7 +425,7 @@ export function DataTable<TData, TValue>({
return (
<div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
{ // .... }
</Table>
@@ -499,7 +499,7 @@ export function DataTable<TData, TValue>({
return (
<div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
@@ -602,7 +602,7 @@ export function DataTable<TData, TValue>({
className="max-w-sm"
/>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
@@ -715,7 +715,7 @@ export function DataTable<TData, TValue>({
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
@@ -805,7 +805,7 @@ export function DataTable<TData, TValue>({
return (
<div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table />
</div>
</div>

View File

@@ -1,6 +1,6 @@
"use server"
import { registryItemSchema } from "shadcn/registry"
import { registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { Style } from "@/registry/registry-styles"

View File

@@ -2,7 +2,7 @@ import { promises as fs } from "fs"
import { tmpdir } from "os"
import path from "path"
import { Index } from "@/__registry__"
import { registryItemFileSchema, registryItemSchema } from "shadcn/registry"
import { registryItemFileSchema, registryItemSchema } from "shadcn/schema"
import { Project, ScriptKind, SourceFile, SyntaxKind } from "ts-morph"
import { z } from "zod"

View File

@@ -88,7 +88,7 @@
"react-resizable-panels": "^2.0.22",
"react-wrap-balancer": "^0.4.1",
"recharts": "2.12.7",
"shadcn": "2.9.3",
"shadcn": "3.0.0",
"sharp": "^0.32.6",
"sonner": "^1.2.3",
"swr": "2.2.6-beta.3",

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,8 @@
{
"name": "default",
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "index",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
@@ -9,6 +11,7 @@
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
@@ -16,6 +19,5 @@
]
}
},
"cssVars": {},
"files": []
"cssVars": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "style",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react"
],
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
"require(\"tailwindcss-animate\")"
]
}
},
"cssVars": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "style",
"type": "registry:style",
"dependencies": [
"class-variance-authority",
"lucide-react"
],
"devDependencies": [
"tw-animate-css"
],
"registryDependencies": [
"utils"
],
"files": [],
"cssVars": {}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,8 @@
{
"name": "new-york",
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "index",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
@@ -9,6 +11,7 @@
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
@@ -16,6 +19,5 @@
]
}
},
"cssVars": {},
"files": []
"cssVars": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "style",
"type": "registry:style",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react"
],
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
"require(\"tailwindcss-animate\")"
]
}
},
"cssVars": {}
}

View File

@@ -56,6 +56,42 @@
}
},
"required": ["utils", "components"]
},
"registries": {
"type": "object",
"patternProperties": {
"^@": {
"oneOf": [
{
"type": "string",
"pattern": "\\{name\\}"
},
{
"type": "object",
"properties": {
"url": {
"type": "string",
"pattern": "\\{name\\}"
},
"params": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": ["url"]
}
]
}
},
"additionalProperties": false
}
},
"required": ["style", "tailwind", "rsc", "aliases"]

View File

@@ -233,7 +233,7 @@ export default function DataTableDemo() {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
import { z } from "zod"
import { blocks } from "@/registry/registry-blocks"
@@ -14,6 +14,40 @@ export const registry = {
name: "shadcn/ui",
homepage: "https://ui.shadcn.com",
items: [
{
name: "index",
type: "registry:style",
dependencies: [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
],
registryDependencies: ["utils"],
tailwind: {
config: {
plugins: ['require("tailwindcss-animate")'],
},
},
cssVars: {},
files: [],
},
{
name: "style",
type: "registry:style",
dependencies: [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
],
registryDependencies: ["utils"],
tailwind: {
config: {
plugins: ['require("tailwindcss-animate")'],
},
},
cssVars: {},
files: [],
},
...ui,
...blocks,
...charts,

View File

@@ -233,7 +233,7 @@ export default function DataTableDemo() {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const blocks: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const charts: Registry["items"] = [
// Area Charts

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const examples: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const hooks: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const internal: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const lib: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const themes: Registry["items"] = [
{

View File

@@ -1,4 +1,4 @@
import { type Registry } from "shadcn/registry"
import { type Registry } from "shadcn/schema"
export const ui: Registry["items"] = [
{

View File

@@ -9,7 +9,7 @@ import {
registryItemSchema,
registryItemTypeSchema,
registrySchema,
} from "shadcn/registry"
} from "shadcn/schema"
import { Project, ScriptKind } from "ts-morph"
import { z } from "zod"
@@ -31,6 +31,7 @@ const REGISTRY_INDEX_WHITELIST: z.infer<typeof registryItemTypeSchema>[] = [
"registry:block",
"registry:example",
"registry:internal",
"registry:style",
]
const project = new Project({
@@ -254,7 +255,7 @@ export const Index: Record<string, any> = {
`
// ----------------------------------------------------------------------------
// Build registry/index.json.
// Build registry/index.json (central index for all styles).
// ----------------------------------------------------------------------------
const items = registry.items
.filter((item) => ["registry:ui"].includes(item.type))
@@ -299,6 +300,9 @@ async function buildStyles(registry: Registry) {
await fs.mkdir(targetPath, { recursive: true })
}
// Build registry.json for this style
const styleRegistryItems = []
for (const item of registry.items) {
if (!REGISTRY_INDEX_WHITELIST.includes(item.type)) {
continue
@@ -390,8 +394,47 @@ async function buildStyles(registry: Registry) {
JSON.stringify(payload.data, null, 2),
"utf8"
)
// Add item to style registry with fixed paths (without content)
styleRegistryItems.push({
...item,
files: files
?.map((file: any) => {
if (!file) return null
// Exclude content from registry.json
const { content, ...fileWithoutContent } = file
return {
...fileWithoutContent,
path: `registry/${style.name}/${file.path}`,
}
})
.filter(Boolean),
})
}
}
// Build registry.json for this style following registrySchema format
const styleRegistry = {
name: registry.name,
homepage: registry.homepage,
items: styleRegistryItems,
}
// Validate against registrySchema
const validatedRegistry = registrySchema.safeParse(styleRegistry)
if (validatedRegistry.success) {
const styleRegistryJson = JSON.stringify(validatedRegistry.data, null, 2)
await fs.writeFile(
path.join(targetPath, "registry.json"),
styleRegistryJson,
"utf8"
)
} else {
console.error(
`Failed to validate registry for style ${style.name}:`,
validatedRegistry.error
)
}
}
// ----------------------------------------------------------------------------
@@ -831,7 +874,7 @@ try {
await syncStyles()
await buildRegistry(result.data)
await buildStyles(result.data)
await buildStylesIndex()
// await buildStylesIndex()
await buildThemes()
await buildRegistryIcons()

View File

@@ -18,7 +18,7 @@
"build:registry": "pnpm --filter=www,v4 build:registry && pnpm --filter=www,v4 lint:fix && pnpm format:write -- --loglevel silent",
"registry:build": "pnpm --filter=v4 registry:build && pnpm lint:fix && pnpm format:write -- --loglevel silent",
"registry:capture": "pnpm --filter=www registry:capture",
"dev": "turbo run dev --parallel",
"dev": "turbo run dev --filter=!www --parallel",
"shadcn-ui:dev": "turbo --filter=shadcn-ui dev",
"shadcn-ui": "pnpm --filter=shadcn-ui start:dev",
"shadcn-ui:test": "pnpm --filter=shadcn-ui test",
@@ -38,7 +38,7 @@
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"preview": "turbo run preview",
"typecheck": "turbo run typecheck",
"typecheck": "turbo run typecheck --filter=!www",
"format:write": "turbo run format:write",
"format:check": "turbo run format:check",
"sync:templates": "./scripts/sync-templates.sh \"templates/*\"",
@@ -46,7 +46,8 @@
"release": "changeset version",
"pub:beta": "cd packages/shadcn && pnpm pub:beta",
"pub:release": "cd packages/shadcn && pnpm pub:release",
"test": "turbo run test --filter=!shadcn-ui --force"
"test:dev": "turbo run test --filter=!shadcn-ui --force",
"test": "start-server-and-test v4:dev http://localhost:4000 test:dev"
},
"packageManager": "pnpm@9.0.6",
"dependencies": {
@@ -85,6 +86,7 @@
"@types/node": "^20.11.27",
"@types/react": "^18.2.65",
"@types/react-dom": "^18.2.22",
"start-server-and-test": "^2.0.12",
"typescript": "^5.5.3"
}
}

View File

@@ -1,5 +1,55 @@
# @shadcn/ui
## 3.0.0
### Major Changes
- [#8004](https://github.com/shadcn-ui/ui/pull/8004) [`a941287411e4cf4eca29779a8220d59bfca2a25c`](https://github.com/shadcn-ui/ui/commit/a941287411e4cf4eca29779a8220d59bfca2a25c) Thanks [@shadcn](https://github.com/shadcn)! - bump all dependencies
- [#8012](https://github.com/shadcn-ui/ui/pull/8012) [`296feb28a220627958f3a558dcb2384875c2d7a1`](https://github.com/shadcn-ui/ui/commit/296feb28a220627958f3a558dcb2384875c2d7a1) Thanks [@shadcn](https://github.com/shadcn)! - add new mcp server and command
- [#7994](https://github.com/shadcn-ui/ui/pull/7994) [`4f5333ea7ae8a2197fb84472addaa9fd66d984ef`](https://github.com/shadcn-ui/ui/commit/4f5333ea7ae8a2197fb84472addaa9fd66d984ef) Thanks [@shadcn](https://github.com/shadcn)! - add view and search commands
- [#7983](https://github.com/shadcn-ui/ui/pull/7983) [`a426fea9410ade04c26bb4edfa5da813ecaddc17`](https://github.com/shadcn-ui/ui/commit/a426fea9410ade04c26bb4edfa5da813ecaddc17) Thanks [@shadcn](https://github.com/shadcn)! - update getRegistry, getRegistryItems and resolveRegistryItems apis
- [#7990](https://github.com/shadcn-ui/ui/pull/7990) [`b3639227d0737538e98923ab335b6dd03f2c993f`](https://github.com/shadcn-ui/ui/commit/b3639227d0737538e98923ab335b6dd03f2c993f) Thanks [@shadcn](https://github.com/shadcn)! - deprecate fetchRegistry and resolveRegistryTree
- [#7972](https://github.com/shadcn-ui/ui/pull/7972) [`6e870c399350adf17554c35cdf732421ac8a6ab2`](https://github.com/shadcn-ui/ui/commit/6e870c399350adf17554c35cdf732421ac8a6ab2) Thanks [@shadcn](https://github.com/shadcn)! - copy registry.json on build
- [#7989](https://github.com/shadcn-ui/ui/pull/7989) [`a4a36007578a6847c0b1f02c1b46350de6ebe2cc`](https://github.com/shadcn-ui/ui/commit/a4a36007578a6847c0b1f02c1b46350de6ebe2cc) Thanks [@shadcn](https://github.com/shadcn)! - move schema to shadcn/schema
- [#8001](https://github.com/shadcn-ui/ui/pull/8001) [`fed7e3bfdc2696b8bc1fb8e65134e96330347aa0`](https://github.com/shadcn-ui/ui/commit/fed7e3bfdc2696b8bc1fb8e65134e96330347aa0) Thanks [@shadcn](https://github.com/shadcn)! - update signatures for getRegistry, getRegistryItems, resolveRegistryItems and searchRegistries
- [#7992](https://github.com/shadcn-ui/ui/pull/7992) [`7d71b02fb1c69b649fd143d0eeab1ddc7995f5ca`](https://github.com/shadcn-ui/ui/commit/7d71b02fb1c69b649fd143d0eeab1ddc7995f5ca) Thanks [@shadcn](https://github.com/shadcn)! - add getRegistry
- [#7948](https://github.com/shadcn-ui/ui/pull/7948) [`2c164b0f221fac0367a0eda3ce8502b38b25ce3e`](https://github.com/shadcn-ui/ui/commit/2c164b0f221fac0367a0eda3ce8502b38b25ce3e) Thanks [@shadcn](https://github.com/shadcn)! - update registry dependencies resolution algorithm
- [#8086](https://github.com/shadcn-ui/ui/pull/8086) [`17422714f65a36a9fc81eeb227366a2e44171be9`](https://github.com/shadcn-ui/ui/commit/17422714f65a36a9fc81eeb227366a2e44171be9) Thanks [@shadcn](https://github.com/shadcn)! - add mcp init command
- [#7940](https://github.com/shadcn-ui/ui/pull/7940) [`578f83cbefe1b3d70b8179a58c70c5729fd1982f`](https://github.com/shadcn-ui/ui/commit/578f83cbefe1b3d70b8179a58c70c5729fd1982f) Thanks [@shadcn](https://github.com/shadcn)! - add support for namespaced registries
### Minor Changes
- [#7955](https://github.com/shadcn-ui/ui/pull/7955) [`a80ab374830fa414bf3672fefc66b03ea4b8da78`](https://github.com/shadcn-ui/ui/commit/a80ab374830fa414bf3672fefc66b03ea4b8da78) Thanks [@shadcn](https://github.com/shadcn)! - update file handling for monorepo
### Patch Changes
- [#8081](https://github.com/shadcn-ui/ui/pull/8081) [`fc27ba269217ade8ae3a87724259874da799cd03`](https://github.com/shadcn-ui/ui/commit/fc27ba269217ade8ae3a87724259874da799cd03) Thanks [@shadcn](https://github.com/shadcn)! - fix --defaults option
- [#7962](https://github.com/shadcn-ui/ui/pull/7962) [`fc8927a1f9d7d1be3338e3d984de20355af61083`](https://github.com/shadcn-ui/ui/commit/fc8927a1f9d7d1be3338e3d984de20355af61083) Thanks [@shadcn](https://github.com/shadcn)! - fix monorepo init on nix system
## 2.10.0
### Minor Changes
- [#7902](https://github.com/shadcn-ui/ui/pull/7902) [`e6778dee87de1a183843f233b3f27fbfb1a700ec`](https://github.com/shadcn-ui/ui/commit/e6778dee87de1a183843f233b3f27fbfb1a700ec) Thanks [@shadcn](https://github.com/shadcn)! - add support for envVars in schema
- [#7896](https://github.com/shadcn-ui/ui/pull/7896) [`97a8de1c1b2ae590cc9dbe17970a882990c35a59`](https://github.com/shadcn-ui/ui/commit/97a8de1c1b2ae590cc9dbe17970a882990c35a59) Thanks [@shadcn](https://github.com/shadcn)! - add support for env vars in registry
### Patch Changes
- [#7908](https://github.com/shadcn-ui/ui/pull/7908) [`d891132f2a0121e12c92839e19f5d90252f9a640`](https://github.com/shadcn-ui/ui/commit/d891132f2a0121e12c92839e19f5d90252f9a640) Thanks [@shadcn](https://github.com/shadcn)! - remove init tests
## 2.9.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "shadcn",
"version": "2.9.3",
"version": "3.0.0",
"description": "Add components to your apps.",
"publishConfig": {
"access": "public"
@@ -35,6 +35,10 @@
"types": "./dist/registry/index.d.ts",
"default": "./dist/registry/index.js"
},
"./schema": {
"types": "./dist/schema/index.d.ts",
"default": "./dist/schema/index.js"
},
"./mcp": {
"types": "./dist/mcp/index.d.ts",
"default": "./dist/mcp/index.js"
@@ -56,44 +60,47 @@
"pub:next": "pnpm build && pnpm publish --no-git-checks --access public --tag next",
"pub:release": "pnpm build && pnpm publish --access public",
"test": "vitest run",
"test:dev": "REGISTRY_URL=http://localhost:4000/r vitest run"
"test:dev": "REGISTRY_URL=http://localhost:4000/r vitest run",
"mcp:inspect": "pnpm dlx @modelcontextprotocol/inspector node dist/index.js mcp"
},
"dependencies": {
"@antfu/ni": "^23.2.0",
"@babel/core": "^7.22.1",
"@babel/parser": "^7.22.6",
"@babel/plugin-transform-typescript": "^7.22.5",
"@modelcontextprotocol/sdk": "^1.10.2",
"commander": "^10.0.0",
"cosmiconfig": "^8.1.3",
"@antfu/ni": "^25.0.0",
"@babel/core": "^7.28.0",
"@babel/parser": "^7.28.0",
"@babel/plugin-transform-typescript": "^7.28.0",
"@dotenvx/dotenvx": "^1.48.4",
"@modelcontextprotocol/sdk": "^1.17.2",
"commander": "^14.0.0",
"cosmiconfig": "^9.0.0",
"dedent": "^1.6.0",
"deepmerge": "^4.3.1",
"diff": "^5.1.0",
"execa": "^7.0.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.1.0",
"https-proxy-agent": "^6.2.0",
"diff": "^8.0.2",
"execa": "^9.6.0",
"fast-glob": "^3.3.3",
"fs-extra": "^11.3.1",
"fuzzysort": "^3.1.0",
"https-proxy-agent": "^7.0.6",
"kleur": "^4.1.5",
"msw": "^2.7.1",
"node-fetch": "^3.3.0",
"ora": "^6.1.2",
"postcss": "^8.4.24",
"msw": "^2.10.4",
"node-fetch": "^3.3.2",
"ora": "^8.2.0",
"postcss": "^8.5.6",
"prompts": "^2.4.2",
"recast": "^0.23.2",
"recast": "^0.23.11",
"stringify-object": "^5.0.0",
"ts-morph": "^18.0.0",
"ts-morph": "^26.0.0",
"tsconfig-paths": "^4.2.0",
"zod": "^3.20.2",
"zod-to-json-schema": "^3.24.5"
"zod": "^3.24.1",
"zod-to-json-schema": "^3.24.6"
},
"devDependencies": {
"@types/babel__core": "^7.20.1",
"@types/diff": "^5.0.3",
"@types/fs-extra": "^11.0.1",
"@types/prompts": "^2.4.2",
"@types/babel__core": "^7.20.5",
"@types/fs-extra": "^11.0.4",
"@types/prompts": "^2.4.9",
"@types/stringify-object": "^4.0.5",
"rimraf": "^6.0.1",
"tsup": "^6.6.3",
"type-fest": "^3.8.0",
"typescript": "^4.9.3"
"tsup": "^8.5.0",
"type-fest": "^4.41.0",
"typescript": "^5.9.2"
}
}

View File

@@ -1,16 +1,13 @@
import fs from "fs"
import path from "path"
import { runInit } from "@/src/commands/init"
import { preFlightAdd } from "@/src/preflights/preflight-add"
import { getRegistryIndex, getRegistryItem } from "@/src/registry/api"
import { registryItemTypeSchema } from "@/src/registry/schema"
import {
isLocalFile,
isUniversalRegistryItem,
isUrl,
} from "@/src/registry/utils"
import { getRegistryItems, getShadcnRegistryIndex } from "@/src/registry/api"
import { DEPRECATED_COMPONENTS } from "@/src/registry/constants"
import { clearRegistryContext } from "@/src/registry/context"
import { isUniversalRegistryItem } from "@/src/registry/utils"
import { addComponents } from "@/src/utils/add-components"
import { createProject } from "@/src/utils/create-project"
import { loadEnvFiles } from "@/src/utils/env-loader"
import * as ERRORS from "@/src/utils/errors"
import { createConfig, getConfig } from "@/src/utils/get-config"
import { getProjectInfo } from "@/src/utils/get-project-info"
@@ -22,21 +19,6 @@ import { Command } from "commander"
import prompts from "prompts"
import { z } from "zod"
const DEPRECATED_COMPONENTS = [
{
name: "toast",
deprecatedBy: "sonner",
message:
"The toast component is deprecated. Use the sonner component instead.",
},
{
name: "toaster",
deprecatedBy: "sonner",
message:
"The toaster component is deprecated. Use the sonner component instead.",
},
]
export const addOptionsSchema = z.object({
components: z.array(z.string()).optional(),
yes: z.boolean(),
@@ -82,37 +64,50 @@ export const add = new Command()
...opts,
})
let itemType: z.infer<typeof registryItemTypeSchema> | undefined
let registryItem: any = null
await loadEnvFiles(options.cwd)
if (
components.length > 0 &&
(isUrl(components[0]) || isLocalFile(components[0]))
) {
registryItem = await getRegistryItem(components[0], "")
itemType = registryItem?.type
let initialConfig = await getConfig(options.cwd)
if (!initialConfig) {
initialConfig = createConfig({
style: "new-york",
resolvedPaths: {
cwd: options.cwd,
},
})
}
if (
!options.yes &&
(itemType === "registry:style" || itemType === "registry:theme")
) {
logger.break()
const { confirm } = await prompts({
type: "confirm",
name: "confirm",
message: highlighter.warn(
`You are about to install a new ${itemType.replace(
"registry:",
""
)}. \nExisting CSS variables and components will be overwritten. Continue?`
),
if (components.length > 0) {
const [registryItem] = await getRegistryItems([components[0]], {
config: initialConfig,
})
if (!confirm) {
const itemType = registryItem?.type
if (isUniversalRegistryItem(registryItem)) {
await addComponents(components, initialConfig, options)
return
}
if (
!options.yes &&
(itemType === "registry:style" || itemType === "registry:theme")
) {
logger.break()
logger.log(`Installation cancelled.`)
logger.break()
process.exit(1)
const { confirm } = await prompts({
type: "confirm",
name: "confirm",
message: highlighter.warn(
`You are about to install a new ${itemType.replace(
"registry:",
""
)}. \nExisting CSS variables and components will be overwritten. Continue?`
),
})
if (!confirm) {
logger.break()
logger.log(`Installation cancelled.`)
logger.break()
process.exit(1)
}
}
}
@@ -136,22 +131,6 @@ export const add = new Command()
}
}
if (isUniversalRegistryItem(registryItem)) {
// Universal items only cares about the cwd.
if (!fs.existsSync(options.cwd)) {
throw new Error(`Directory ${options.cwd} does not exist`)
}
const minimalConfig = createConfig({
resolvedPaths: {
cwd: options.cwd,
},
})
await addComponents(options.components, minimalConfig, options)
return
}
let { errors, config } = await preFlightAdd(options)
// No components.json file. Prompt the user to run init.
@@ -180,7 +159,7 @@ export const add = new Command()
isNewProject: false,
srcDir: options.srcDir,
cssVariables: options.cssVariables,
style: "index",
baseStyle: true,
})
}
@@ -212,7 +191,7 @@ export const add = new Command()
isNewProject: true,
srcDir: options.srcDir,
cssVariables: options.cssVariables,
style: "index",
baseStyle: true,
})
shouldUpdateAppIndex =
@@ -237,13 +216,15 @@ export const add = new Command()
} catch (error) {
logger.break()
handleError(error)
} finally {
clearRegistryContext()
}
})
async function promptForRegistryComponents(
options: z.infer<typeof addOptionsSchema>
) {
const registryIndex = await getRegistryIndex()
const registryIndex = await getShadcnRegistryIndex()
if (!registryIndex) {
logger.break()
handleError(new Error("Failed to fetch registry index."))

View File

@@ -1,7 +1,7 @@
import * as fs from "fs/promises"
import * as path from "path"
import { preFlightBuild } from "@/src/preflights/preflight-build"
import { registryItemSchema, registrySchema } from "@/src/registry"
import { registryItemSchema, registrySchema } from "@/src/schema"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
@@ -89,6 +89,12 @@ export const build = new Command()
)
}
// Copy registry.json to the output directory.
await fs.copyFile(
resolvePaths.registryFile,
path.resolve(resolvePaths.outputDir, "registry.json")
)
buildSpinner.succeed("Building registry.")
} catch (error) {
logger.break()

View File

@@ -4,9 +4,9 @@ import {
fetchTree,
getItemTargetPath,
getRegistryBaseColor,
getRegistryIndex,
getShadcnRegistryIndex,
} from "@/src/registry/api"
import { registryIndexSchema } from "@/src/registry/schema"
import { registryIndexSchema } from "@/src/schema"
import { Config, getConfig } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
@@ -57,7 +57,7 @@ export const diff = new Command()
process.exit(1)
}
const registryIndex = await getRegistryIndex()
const registryIndex = await getShadcnRegistryIndex()
if (!registryIndex) {
handleError(new Error("Failed to fetch registry index."))

View File

@@ -1,5 +1,6 @@
import { getConfig } from "@/src/utils/get-config"
import { getProjectInfo } from "@/src/utils/get-project-info"
import { handleError } from "@/src/utils/handle-error"
import { logger } from "@/src/utils/logger"
import { Command } from "commander"
@@ -12,9 +13,13 @@ export const info = new Command()
process.cwd()
)
.action(async (opts) => {
logger.info("> project info")
console.log(await getProjectInfo(opts.cwd))
logger.break()
logger.info("> components.json")
console.log(await getConfig(opts.cwd))
try {
logger.info("> project info")
console.log(await getProjectInfo(opts.cwd))
logger.break()
logger.info("> components.json")
console.log(await getConfig(opts.cwd))
} catch (error) {
handleError(error)
}
})

View File

@@ -2,22 +2,31 @@ import { promises as fs } from "fs"
import path from "path"
import { preFlightInit } from "@/src/preflights/preflight-init"
import {
BASE_COLORS,
getRegistryBaseColors,
getRegistryItem,
getRegistryItems,
getRegistryStyles,
} from "@/src/registry/api"
import { isLocalFile, isUrl } from "@/src/registry/utils"
import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
import { configWithDefaults } from "@/src/registry/config"
import { BASE_COLORS } from "@/src/registry/constants"
import { clearRegistryContext } from "@/src/registry/context"
import { rawConfigSchema } from "@/src/schema"
import { addComponents } from "@/src/utils/add-components"
import { TEMPLATES, createProject } from "@/src/utils/create-project"
import { loadEnvFiles } from "@/src/utils/env-loader"
import * as ERRORS from "@/src/utils/errors"
import {
FILE_BACKUP_SUFFIX,
createFileBackup,
deleteFileBackup,
restoreFileBackup,
} from "@/src/utils/file-helper"
import {
DEFAULT_COMPONENTS,
DEFAULT_TAILWIND_CONFIG,
DEFAULT_TAILWIND_CSS,
DEFAULT_UTILS,
getConfig,
rawConfigSchema,
resolveConfigPaths,
type Config,
} from "@/src/utils/get-config"
@@ -32,9 +41,23 @@ import { logger } from "@/src/utils/logger"
import { spinner } from "@/src/utils/spinner"
import { updateTailwindContent } from "@/src/utils/updaters/update-tailwind-content"
import { Command } from "commander"
import deepmerge from "deepmerge"
import fsExtra from "fs-extra"
import prompts from "prompts"
import { z } from "zod"
process.on("exit", (code) => {
const filePath = path.resolve(process.cwd(), "components.json")
// Delete backup if successful.
if (code === 0) {
return deleteFileBackup(filePath)
}
// Restore backup if error.
return restoreFileBackup(filePath)
})
export const initOptionsSchema = z.object({
cwd: z.string(),
components: z.array(z.string()).optional(),
@@ -76,7 +99,7 @@ export const initOptionsSchema = z.object({
).join("', '")}'`,
}
),
style: z.string(),
baseStyle: z.boolean(),
})
export const init = new Command()
@@ -112,31 +135,67 @@ export const init = new Command()
)
.option("--css-variables", "use css variables for theming.", true)
.option("--no-css-variables", "do not use css variables for theming.")
.option("--no-base-style", "do not install the base shadcn style.")
.action(async (components, opts) => {
try {
// Apply defaults when --defaults flag is set.
if (opts.defaults) {
opts.template = opts.template || "next"
opts.baseColor = opts.baseColor || "neutral"
}
const options = initOptionsSchema.parse({
cwd: path.resolve(opts.cwd),
isNewProject: false,
components,
style: "index",
...opts,
})
// We need to check if we're initializing with a new style.
// We fetch the payload of the first item.
// This is okay since the request is cached and deduped.
if (
components.length > 0 &&
(isUrl(components[0]) || isLocalFile(components[0]))
) {
const item = await getRegistryItem(components[0], "")
await loadEnvFiles(options.cwd)
// Skip base color if style.
// We set a default and let the style override it.
if (item?.type === "registry:style") {
options.baseColor = "neutral"
options.style = item.extends ?? "index"
// We need to check if we're initializing with a new style.
// This will allow us to determine if we need to install the base style.
// And if we should prompt the user for a base color.
if (components.length > 0) {
// We don't know the full config at this point.
// So we'll use a shadow config to fetch the first item.
let shadowConfig = configWithDefaults({})
// Check if there's a components.json file.
// If so, we'll merge with our shadow config.
const componentsJsonPath = path.resolve(options.cwd, "components.json")
if (fsExtra.existsSync(componentsJsonPath)) {
const existingConfig = await fsExtra.readJson(componentsJsonPath)
const config = rawConfigSchema.partial().parse(existingConfig)
shadowConfig = configWithDefaults(config)
// Since components.json might not be valid at this point.
// Temporarily rename components.json to allow preflight to run.
// We'll rename it back after preflight.
createFileBackup(componentsJsonPath)
}
// This forces a shadowConfig validation early in the process.
buildUrlAndHeadersForRegistryItem(components[0], shadowConfig)
const [item] = await getRegistryItems([components[0]], {
config: shadowConfig,
})
if (item?.type === "registry:style") {
// Set a default base color so we're not prompted.
// The style will extend or override it.
options.baseColor = "neutral"
// If the style extends none, we don't want to install the base style.
options.baseStyle =
item.extends === "none" ? false : options.baseStyle
}
}
// If --no-base-style, we don't want to prompt for a base color either.
// The style will extend or override it.
if (!options.baseStyle) {
options.baseColor = "neutral"
}
await runInit(options)
@@ -146,10 +205,15 @@ export const init = new Command()
"Success!"
)} Project initialization completed.\nYou may now add components.`
)
// We need when runninng with custom cwd.
deleteFileBackup(path.resolve(options.cwd, "components.json"))
logger.break()
} catch (error) {
logger.break()
handleError(error)
} finally {
clearRegistryContext()
}
})
@@ -182,7 +246,8 @@ export async function runInit(
}
const projectConfig = await getProjectConfig(options.cwd, projectInfo)
const config = projectConfig
let config = projectConfig
? await promptForMinimalConfig(projectConfig, options)
: await promptForConfig(await getConfig(options.cwd))
@@ -201,23 +266,38 @@ export async function runInit(
}
}
// Write components.json.
const componentSpinner = spinner(`Writing components.json.`).start()
const targetPath = path.resolve(options.cwd, "components.json")
await fs.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8")
const backupPath = `${targetPath}${FILE_BACKUP_SUFFIX}`
// Merge with backup config if it exists and not using --force
if (!options.force && fsExtra.existsSync(backupPath)) {
const existingConfig = await fsExtra.readJson(backupPath)
// Move registries at the end of the config.
const { registries, ...merged } = deepmerge(existingConfig, config)
config = { ...merged, registries }
}
// Write components.json.
await fs.writeFile(targetPath, `${JSON.stringify(config, null, 2)}\n`, "utf8")
componentSpinner.succeed()
// Add components.
const fullConfig = await resolveConfigPaths(options.cwd, config)
const components = [
...(options.style === "none" ? [] : [options.style]),
// "index" is the default shadcn style.
// Why index? Because when style is true, we read style from components.json and fetch that.
// i.e new-york from components.json then fetch /styles/new-york/index.
// TODO: Fix this so that we can extend any style i.e --style=new-york.
...(options.baseStyle ? ["index"] : []),
...(options.components ?? []),
]
await addComponents(components, fullConfig, {
// Init will always overwrite files.
overwrite: true,
silent: options.silent,
style: options.style,
baseStyle: options.baseStyle,
isNewProject:
options.isNewProject || projectInfo?.framework.name === "next-app",
})
@@ -408,7 +488,7 @@ async function promptForMinimalConfig(
},
rsc: defaultConfig?.rsc,
tsx: defaultConfig?.tsx,
aliases: defaultConfig?.aliases,
iconLibrary: defaultConfig?.iconLibrary,
aliases: defaultConfig?.aliases,
})
}

View File

@@ -0,0 +1,198 @@
import { promises as fs } from "fs"
import path from "path"
import { server } from "@/src/mcp"
import { loadEnvFiles } from "@/src/utils/env-loader"
import { getConfig } from "@/src/utils/get-config"
import { getPackageManager } from "@/src/utils/get-package-manager"
import { handleError } from "@/src/utils/handle-error"
import { logger } from "@/src/utils/logger"
import { spinner } from "@/src/utils/spinner"
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { Command } from "commander"
import deepmerge from "deepmerge"
import { execa } from "execa"
import fsExtra from "fs-extra"
import prompts from "prompts"
import z from "zod"
const SHADCN_MCP_VERSION = "latest"
const CLIENTS = [
{
name: "claude",
label: "Claude Code",
configPath: ".mcp.json",
config: {
mcpServers: {
shadcn: {
command: "npx",
args: [`shadcn@${SHADCN_MCP_VERSION}`, "mcp"],
},
},
},
},
{
name: "cursor",
label: "Cursor",
configPath: ".cursor/mcp.json",
config: {
mcpServers: {
shadcn: {
command: "npx",
args: [`shadcn@${SHADCN_MCP_VERSION}`, "mcp"],
},
},
},
},
{
name: "vscode",
label: "VS Code",
configPath: ".vscode/mcp.json",
config: {
servers: {
shadcn: {
command: "npx",
args: [`shadcn@${SHADCN_MCP_VERSION}`, "mcp"],
},
},
},
},
] as const
const DEPENDENCIES = [`shadcn@${SHADCN_MCP_VERSION}`]
export const mcp = new Command()
.name("mcp")
.description("MCP server and configuration commands")
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.action(async (options) => {
try {
await loadEnvFiles(options.cwd)
const transport = new StdioServerTransport()
await server.connect(transport)
} catch (error) {
logger.break()
handleError(error)
}
})
const mcpInitOptionsSchema = z.object({
client: z.enum(["claude", "cursor", "vscode"]),
cwd: z.string(),
})
mcp
.command("init")
.description("Initialize MCP configuration for your client")
.option(
"--client <client>",
`MCP client (${CLIENTS.map((c) => c.name).join(", ")})`
)
.action(async (opts, command) => {
try {
// Get the cwd from parent command.
const parentOpts = command.parent?.opts() || {}
const cwd = parentOpts.cwd || process.cwd()
let client = opts.client
if (!client) {
const response = await prompts({
type: "select",
name: "client",
message: "Which MCP client are you using?",
choices: CLIENTS.map((c) => ({
title: c.label,
value: c.name,
})),
})
if (!response.client) {
logger.break()
process.exit(1)
}
client = response.client
}
const options = mcpInitOptionsSchema.parse({
client,
cwd,
})
const configSpinner = spinner("Configuring MCP server...").start()
const configPath = await runMcpInit(options)
configSpinner.succeed("Configuring MCP server.")
const config = await getConfig(options.cwd)
if (config) {
await updateDependencies([], DEPENDENCIES, config, {
silent: false,
})
} else {
const packageManager = await getPackageManager(options.cwd)
const installCommand = packageManager === "npm" ? "install" : "add"
const devFlag = packageManager === "npm" ? "--save-dev" : "-D"
const installSpinner = spinner("Installing dependencies...").start()
await execa(
packageManager,
[installCommand, devFlag, ...DEPENDENCIES],
{
cwd: options.cwd,
}
)
installSpinner.succeed("Installing dependencies.")
}
logger.break()
logger.success(`Configuration saved to ${configPath}.`)
logger.break()
} catch (error) {
handleError(error)
}
})
const overwriteMerge = (_: any[], sourceArray: any[]) => sourceArray
async function runMcpInit(options: z.infer<typeof mcpInitOptionsSchema>) {
const { client, cwd } = options
const clientInfo = CLIENTS.find((c) => c.name === client)
if (!clientInfo) {
throw new Error(
`Unknown client: ${client}. Available clients: ${CLIENTS.map(
(c) => c.name
).join(", ")}`
)
}
const configPath = path.join(cwd, clientInfo.configPath)
let existingConfig = {}
try {
const content = await fs.readFile(configPath, "utf-8")
existingConfig = JSON.parse(content)
} catch {}
const mergedConfig = deepmerge(
existingConfig,
clientInfo.config as Record<string, unknown>,
{ arrayMerge: overwriteMerge }
)
const dir = path.dirname(configPath)
await fsExtra.ensureDir(dir)
await fs.writeFile(
configPath,
JSON.stringify(mergedConfig, null, 2) + "\n",
"utf-8"
)
return clientInfo.configPath
}

View File

@@ -1,10 +1,9 @@
import * as fs from "fs/promises"
import * as path from "path"
import { preFlightRegistryBuild } from "@/src/preflights/preflight-registry"
import { registryItemSchema, registrySchema } from "@/src/registry"
import { recursivelyResolveFileImports } from "@/src/registry/utils"
import { configSchema, registryItemSchema, registrySchema } from "@/src/schema"
import * as ERRORS from "@/src/utils/errors"
import { configSchema } from "@/src/utils/get-config"
import { ProjectInfo, getProjectInfo } from "@/src/utils/get-project-info"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"

View File

@@ -1,23 +1,22 @@
import { server } from "@/src/mcp"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { Command } from "commander"
export const mcp = new Command()
.name("registry:mcp")
.description("starts the registry MCP server [EXPERIMENTAL]")
.description("starts the registry MCP server [DEPRECATED]")
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.action(async () => {
try {
const transport = new StdioServerTransport()
await server.connect(transport)
} catch (error) {
logger.break()
handleError(error)
}
logger.warn(
`The ${highlighter.info(
"shadcn registry:mcp"
)} command is deprecated. Use the ${highlighter.info(
"shadcn mcp"
)} command instead.`
)
logger.break()
})

View File

@@ -0,0 +1,105 @@
import path from "path"
import { configWithDefaults } from "@/src/registry/config"
import { clearRegistryContext } from "@/src/registry/context"
import { searchRegistries } from "@/src/registry/search"
import { validateRegistryConfigForItems } from "@/src/registry/validator"
import { rawConfigSchema } from "@/src/schema"
import { loadEnvFiles } from "@/src/utils/env-loader"
import { createConfig, getConfig } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { Command } from "commander"
import fsExtra from "fs-extra"
import { z } from "zod"
const searchOptionsSchema = z.object({
cwd: z.string(),
query: z.string().optional(),
limit: z.number().optional(),
offset: z.number().optional(),
})
// TODO: We're duplicating logic for shadowConfig here.
// Revisit and properly abstract this.
export const search = new Command()
.name("search")
.alias("list")
.description("search items from registries")
.argument(
"<registries...>",
"the registry names or urls to search items from. Names must be prefixed with @."
)
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.option("-q, --query <query>", "query string")
.option(
"-l, --limit <number>",
"maximum number of items to display per registry",
"100"
)
.option("-o, --offset <number>", "number of items to skip", "0")
.action(async (registries: string[], opts) => {
try {
const options = searchOptionsSchema.parse({
cwd: path.resolve(opts.cwd),
query: opts.query,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
})
await loadEnvFiles(options.cwd)
// Start with a shadow config to support partial components.json.
// Use createConfig to get proper default paths
const defaultConfig = createConfig({
style: "new-york",
resolvedPaths: {
cwd: options.cwd,
},
})
let shadowConfig = configWithDefaults(defaultConfig)
// Check if there's a components.json file (partial or complete).
const componentsJsonPath = path.resolve(options.cwd, "components.json")
if (fsExtra.existsSync(componentsJsonPath)) {
const existingConfig = await fsExtra.readJson(componentsJsonPath)
const partialConfig = rawConfigSchema.partial().parse(existingConfig)
shadowConfig = configWithDefaults({
...defaultConfig,
...partialConfig,
})
}
// Try to get the full config, but fall back to shadow config if it fails.
let config = shadowConfig
try {
const fullConfig = await getConfig(options.cwd)
if (fullConfig) {
config = configWithDefaults(fullConfig)
}
} catch {
// Use shadow config if getConfig fails (partial components.json).
}
// Validate registries early for better error messages.
validateRegistryConfigForItems(registries, config)
// Use searchRegistries for both search and non-search cases
const results = await searchRegistries(registries as `@${string}`[], {
query: options.query,
limit: options.limit,
offset: options.offset,
config,
})
console.log(JSON.stringify(results, null, 2))
process.exit(0)
} catch (error) {
handleError(error)
} finally {
clearRegistryContext()
}
})

View File

@@ -0,0 +1,68 @@
import path from "path"
import { getRegistryItems } from "@/src/registry/api"
import { configWithDefaults } from "@/src/registry/config"
import { clearRegistryContext } from "@/src/registry/context"
import { validateRegistryConfigForItems } from "@/src/registry/validator"
import { rawConfigSchema } from "@/src/schema"
import { loadEnvFiles } from "@/src/utils/env-loader"
import { getConfig } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { Command } from "commander"
import fsExtra from "fs-extra"
import { z } from "zod"
const viewOptionsSchema = z.object({
cwd: z.string(),
})
export const view = new Command()
.name("view")
.description("view items from the registry")
.argument("<items...>", "the item names or URLs to view")
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.action(async (items: string[], opts) => {
try {
const options = viewOptionsSchema.parse({
cwd: path.resolve(opts.cwd),
})
await loadEnvFiles(options.cwd)
// Start with a shadow config to support partial components.json.
let shadowConfig = configWithDefaults({})
// Check if there's a components.json file (partial or complete).
const componentsJsonPath = path.resolve(options.cwd, "components.json")
if (fsExtra.existsSync(componentsJsonPath)) {
const existingConfig = await fsExtra.readJson(componentsJsonPath)
const partialConfig = rawConfigSchema.partial().parse(existingConfig)
shadowConfig = configWithDefaults(partialConfig)
}
// Try to get the full config, but fall back to shadow config if it fails.
let config = shadowConfig
try {
const fullConfig = await getConfig(options.cwd)
if (fullConfig) {
config = configWithDefaults(fullConfig)
}
} catch {
// Use shadow config if getConfig fails (partial components.json).
}
// Validate registries early for better error messages.
validateRegistryConfigForItems(items, config)
const payload = await getRegistryItems(items, { config })
console.log(JSON.stringify(payload, null, 2))
process.exit(0)
} catch (error) {
handleError(error)
} finally {
clearRegistryContext()
}
})

View File

@@ -4,9 +4,12 @@ import { build } from "@/src/commands/build"
import { diff } from "@/src/commands/diff"
import { info } from "@/src/commands/info"
import { init } from "@/src/commands/init"
import { mcp } from "@/src/commands/mcp"
import { migrate } from "@/src/commands/migrate"
import { build as registryBuild } from "@/src/commands/registry/build"
import { mcp as registryMcp } from "@/src/commands/registry/mcp"
import { search } from "@/src/commands/search"
import { view } from "@/src/commands/view"
import { Command } from "commander"
import packageJson from "../package.json"
@@ -17,7 +20,7 @@ process.on("SIGTERM", () => process.exit(0))
async function main() {
const program = new Command()
.name("shadcn")
.description("add components and dependencies to your project")
.description("add items from registries to your project")
.version(
packageJson.version || "1.0.0",
"-v, --version",
@@ -28,10 +31,12 @@ async function main() {
.addCommand(init)
.addCommand(add)
.addCommand(diff)
.addCommand(view)
.addCommand(search)
.addCommand(migrate)
.addCommand(info)
.addCommand(build)
.addCommand(mcp)
// Registry commands
program.addCommand(registryBuild).addCommand(registryMcp)

View File

@@ -1,17 +1,26 @@
import { registrySchema } from "@/src/registry"
import { fetchRegistry, getRegistryItem } from "@/src/registry/api"
import { getRegistryItems, searchRegistries } from "@/src/registry"
import { RegistryError } from "@/src/registry/errors"
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
import dedent from "dedent"
import { z } from "zod"
import { zodToJsonSchema } from "zod-to-json-schema"
import {
formatItemExamples,
formatRegistryItems,
formatSearchResultsWithPagination,
getMcpConfig,
npxShadcn,
} from "./utils"
export const server = new Server(
{
name: "shadcn",
version: "0.0.1",
version: "1.0.0",
},
{
capabilities: {
@@ -25,42 +34,113 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "init",
name: "get_project_registries",
description:
"Initialize a new project using a registry style project structure.",
"Get configured registry names from components.json - Returns error if no components.json exists (use init_project to create one)",
inputSchema: zodToJsonSchema(z.object({})),
},
{
name: "get_items",
description: "List all the available items in the registry",
inputSchema: zodToJsonSchema(z.object({})),
},
{
name: "get_item",
description: "Get an item from the registry",
name: "list_items_in_registries",
description:
"List items from registries (requires components.json - use init_project if missing)",
inputSchema: zodToJsonSchema(
z.object({
name: z
registries: z
.array(z.string())
.describe(
"Array of registry names to search (e.g., ['@shadcn', '@acme'])"
),
limit: z
.number()
.optional()
.describe("Maximum number of items to return"),
offset: z
.number()
.optional()
.describe("Number of items to skip for pagination"),
})
),
},
{
name: "search_items_in_registries",
description:
"Search for components in registries using fuzzy matching (requires components.json). After finding an item, use get_item_examples_from_registries to see usage examples.",
inputSchema: zodToJsonSchema(
z.object({
registries: z
.array(z.string())
.describe(
"Array of registry names to search (e.g., ['@shadcn', '@acme'])"
),
query: z
.string()
.describe(
"The name of the item to get from the registry. This is required."
"Search query string for fuzzy matching against item names and descriptions"
),
limit: z
.number()
.optional()
.describe("Maximum number of items to return"),
offset: z
.number()
.optional()
.describe("Number of items to skip for pagination"),
})
),
},
{
name: "view_items_in_registries",
description:
"View detailed information about specific registry items including the name, description, type and files content. For usage examples, use get_item_examples_from_registries instead.",
inputSchema: zodToJsonSchema(
z.object({
items: z
.array(z.string())
.describe(
"Array of item names with registry prefix (e.g., ['@shadcn/button', '@shadcn/card'])"
),
})
),
},
{
name: "add_item",
description: "Add an item from the registry",
name: "get_item_examples_from_registries",
description:
"Find usage examples and demos with their complete code. Search for patterns like 'accordion-demo', 'button example', 'card-demo', etc. Returns full implementation code with dependencies.",
inputSchema: zodToJsonSchema(
z.object({
name: z
registries: z
.array(z.string())
.describe(
"Array of registry names to search (e.g., ['@shadcn', '@acme'])"
),
query: z
.string()
.describe(
"The name of the item to add to the registry. This is required."
"Search query for examples (e.g., 'accordion-demo', 'button demo', 'card example', 'tooltip-demo', 'example-booking-form', 'example-hero'). Common patterns: '{item-name}-demo', '{item-name} example', 'example {item-name}'"
),
})
),
},
{
name: "get_add_command_for_items",
description:
"Get the shadcn CLI add command for specific items in a registry. This is useful for adding one or more components to your project.",
inputSchema: zodToJsonSchema(
z.object({
items: z
.array(z.string())
.describe(
"Array of items to get the add command for prefixed with the registry name (e.g., ['@shadcn/button', '@shadcn/card'])"
),
})
),
},
{
name: "get_audit_checklist",
description:
"After creating new components or generating new code files, use this tool for a quick checklist to verify that everything is working as expected. Make sure to run the tool after all required steps have been completed.",
inputSchema: zodToJsonSchema(z.object({})),
},
],
}
})
@@ -68,52 +148,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required")
}
const REGISTRY_URL = process.env.REGISTRY_URL
if (!REGISTRY_URL) {
throw new Error("REGISTRY_URL is not set")
throw new Error("No tool arguments provided.")
}
switch (request.params.name) {
case "init": {
const registry = await getRegistry(REGISTRY_URL)
const style = registry.items.find(
(item) => item.type === "registry:style"
)
case "get_project_registries": {
const config = await getMcpConfig(process.cwd())
let text = `To initialize a new project, run the following command:
\`\`\`bash
npx shadcn@canary init
\`\`\`
- This will install all the dependencies and theme for the project.
- If running the init command installs a rules i.e registry.mdc file, you should follow the instructions in the file to configure the project.
`
const rules = registry.items.find(
(item) => item.type === "registry:file" && item.name === "rules"
)
if (rules) {
text += `
You should also install the rules for the project.
\`\`\`bash
npx shadcn@canary add ${getRegistryItemUrl(
rules.name,
REGISTRY_URL
)}
\`\`\`
`
}
if (!style) {
if (!config?.registries) {
return {
content: [
{
type: "text",
text,
text: dedent`No components.json found or no registries configured.
To fix this:
1. Use the \`init\` command to create a components.json file
2. Or manually create components.json with a registries section`,
},
],
}
@@ -123,29 +174,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
content: [
{
type: "text",
text: `To initialize a new project using the ${
style.name
} style, run the following command:
\`\`\`bash
npx shadcn@canary init ${getRegistryItemUrl(
style.name,
REGISTRY_URL
)}
\`\`\`
`,
text: dedent`The following registries are configured in the current project:
${Object.keys(config.registries)
.map((registry) => `- ${registry}`)
.join("\n")}
You can view the items in a registry by running:
\`${await npxShadcn("view @name-of-registry")}\`
For example: \`${await npxShadcn(
"view @shadcn"
)}\` or \`${await npxShadcn(
"view @shadcn @acme"
)}\` to view multiple registries.
`,
},
],
}
}
case "get_items": {
const registry = await getRegistry(REGISTRY_URL)
if (!registry.items) {
case "search_items_in_registries": {
const inputSchema = z.object({
registries: z.array(z.string()),
query: z.string(),
limit: z.number().optional(),
offset: z.number().optional(),
})
const args = inputSchema.parse(request.params.arguments)
const results = await searchRegistries(args.registries, {
query: args.query,
limit: args.limit,
offset: args.offset,
config: await getMcpConfig(process.cwd()),
useCache: false,
})
if (results.items.length === 0) {
return {
content: [
{
type: "text",
text: "No items found in the registry",
text: dedent`No items found matching "${
args.query
}" in registries ${args.registries.join(
", "
)}, Try searching with a different query or registry.`,
},
],
}
@@ -155,108 +230,234 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
content: [
{
type: "text",
text: `The following items are available in the registry:
${JSON.stringify(
registry.items.map(
(item) => `- ${item.name} (${item.type}): ${item.description}`
),
null,
2
)}.
- To install and use an item in your project, you run the following command:
\`\`\`bash
npx shadcn@canary add ${getRegistryItemUrl(
"NAME_OF_THE_ITEM",
REGISTRY_URL
)}
\`\`\`
- Example: npx shadcn@canary add ${getRegistryItemUrl(
registry.items[0].name,
REGISTRY_URL
)} to install the ${registry.items[0].name} item.
- To install multiple registry.items, you can do the following:
\`\`\`bash
npx shadcn@canary add ${getRegistryItemUrl(
"NAME_OF_THE_ITEM_1",
REGISTRY_URL
)} ${getRegistryItemUrl("NAME_OF_THE_ITEM_2", REGISTRY_URL)}
\`\`\`
- Before using any item, you need to add it first.
- Adding the items will install all dependencies for the item and format the code as per the project.
- Example components should not be installed directly unless asked. These components should be used as a reference to build other components.
text: formatSearchResultsWithPagination(results, {
query: args.query,
registries: args.registries,
}),
},
],
}
}
case "list_items_in_registries": {
const inputSchema = z.object({
registries: z.array(z.string()),
limit: z.number().optional(),
offset: z.number().optional(),
cwd: z.string().optional(),
})
const args = inputSchema.parse(request.params.arguments)
const results = await searchRegistries(args.registries, {
limit: args.limit,
offset: args.offset,
config: await getMcpConfig(process.cwd()),
useCache: false,
})
if (results.items.length === 0) {
return {
content: [
{
type: "text",
text: dedent`No items found in registries ${args.registries.join(
", "
)}.`,
},
],
}
}
return {
content: [
{
type: "text",
text: formatSearchResultsWithPagination(results, {
registries: args.registries,
}),
},
],
}
}
case "view_items_in_registries": {
const inputSchema = z.object({
items: z.array(z.string()),
})
const args = inputSchema.parse(request.params.arguments)
const registryItems = await getRegistryItems(args.items, {
config: await getMcpConfig(process.cwd()),
useCache: false,
})
if (registryItems?.length === 0) {
return {
content: [
{
type: "text",
text: dedent`No items found for: ${args.items.join(", ")}
Make sure the item names are correct and include the registry prefix (e.g., @shadcn/button).`,
},
],
}
}
const formattedItems = formatRegistryItems(registryItems)
return {
content: [
{
type: "text",
text: dedent`Item Details:
${formattedItems.join("\n\n---\n\n")}`,
},
],
}
}
case "get_item_examples_from_registries": {
const inputSchema = z.object({
query: z.string(),
registries: z.array(z.string()),
})
const args = inputSchema.parse(request.params.arguments)
const config = await getMcpConfig()
const results = await searchRegistries(args.registries, {
query: args.query,
config,
useCache: false,
})
if (results.items.length === 0) {
return {
content: [
{
type: "text",
text: dedent`No examples found for query "${args.query}".
Try searching with patterns like:
- "accordion-demo" for accordion examples
- "button demo" or "button example"
- Component name followed by "-demo" or "example"
You can also:
1. Use search_items_in_registries to find all items matching your query
2. View the main component with view_items_in_registries for inline usage documentation`,
},
],
}
}
const itemNames = results.items.map((item) => item.addCommandArgument)
const fullItems = await getRegistryItems(itemNames, {
config,
useCache: false,
})
return {
content: [
{
type: "text",
text: formatItemExamples(fullItems, args.query),
},
],
}
}
case "get_add_command_for_items": {
const args = z
.object({
items: z.array(z.string()),
})
.parse(request.params.arguments)
return {
content: [
{
type: "text",
text: await npxShadcn(`add ${args.items.join(" ")}`),
},
],
}
}
case "get_audit_checklist": {
return {
content: [
{
type: "text",
text: dedent`## Component Audit Checklist
After adding or generating components, check the following common issues:
- [ ] Ensure imports are correct i.e named vs default imports
- [ ] If using next/image, ensure images.remotePatterns next.config.js is configured correctly.
- [ ] Ensure all dependencies are installed.
- [ ] Check for linting errors or warnings
- [ ] Check for TypeScript errors
- [ ] Use the Playwright MCP if available.
`,
},
],
}
}
case "get_item": {
const name = z.string().parse(request.params.arguments.name)
if (!name) {
throw new Error("Name is required")
}
const itemUrl = getRegistryItemUrl(name, REGISTRY_URL)
const item = await getRegistryItem(itemUrl, "")
return {
content: [{ type: "text", text: JSON.stringify(item, null, 2) }],
}
}
case "add_item": {
const name = z.string().parse(request.params.arguments.name)
if (!name) {
throw new Error("Name is required")
}
const itemUrl = getRegistryItemUrl(name, REGISTRY_URL)
const item = await getRegistryItem(itemUrl, "")
if (!item) {
return {
content: [
{
type: "text",
text: `Item ${name} not found in the registry.`,
},
],
}
}
return {
content: [
{
type: "text",
text: `To install the ${name} item, run the following command:
\`\`\`bash
npx shadcn@canary add ${itemUrl}
\`\`\``,
},
],
}
}
default:
throw new Error(`Tool ${request.params.name} not found`)
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`)
return {
content: [
{
type: "text",
text: dedent`Invalid input parameters:
${error.errors
.map((e) => `- ${e.path.join(".")}: ${e.message}`)
.join("\n")}
`,
},
],
isError: true,
}
}
throw error
if (error instanceof RegistryError) {
let errorMessage = error.message
if (error.suggestion) {
errorMessage += `\n\n💡 ${error.suggestion}`
}
if (error.context) {
errorMessage += `\n\nContext: ${JSON.stringify(error.context, null, 2)}`
}
return {
content: [
{
type: "text",
text: dedent`Error (${error.code}): ${errorMessage}`,
},
],
isError: true,
}
}
const errorMessage = error instanceof Error ? error.message : String(error)
return {
content: [
{
type: "text",
text: dedent`Error: ${errorMessage}`,
},
],
isError: true,
}
}
})
async function getRegistry(registryUrl: string) {
const [registryJson] = await fetchRegistry([registryUrl], {
useCache: false,
})
return registrySchema.parse(registryJson)
}
function getRegistryItemUrl(itemName: string, registryUrl: string) {
const registryBaseUrl = registryUrl.replace(/\/registry\.json$/, "")
return `${registryBaseUrl}/${itemName}.json`
}

View File

@@ -0,0 +1,132 @@
import { getRegistriesConfig } from "@/src/registry/api"
import { registryItemSchema, searchResultsSchema } from "@/src/schema"
import { getPackageRunner } from "@/src/utils/get-package-manager"
import { z } from "zod"
const SHADCN_CLI_COMMAND = "shadcn@latest"
export async function npxShadcn(command: string) {
const packageRunner = await getPackageRunner(process.cwd())
return `${packageRunner} ${SHADCN_CLI_COMMAND} ${command}`
}
export async function getMcpConfig(cwd = process.cwd()) {
const config = await getRegistriesConfig(cwd, {
useCache: false,
})
return {
registries: config.registries,
}
}
export function formatSearchResultsWithPagination(
results: z.infer<typeof searchResultsSchema>,
options?: {
query?: string
registries?: string[]
}
) {
const { query, registries } = options || {}
const formattedItems = results.items.map((item) => {
const parts: string[] = [`- ${item.name}`]
if (item.type) {
parts.push(`(${item.type})`)
}
if (item.description) {
parts.push(`- ${item.description}`)
}
if (item.registry) {
parts.push(`[${item.registry}]`)
}
parts.push(
`\n Add command: \`${npxShadcn(`add ${item.addCommandArgument}`)}\``
)
return parts.join(" ")
})
let header = `Found ${results.pagination.total} items`
if (query) {
header += ` matching "${query}"`
}
if (registries && registries.length > 0) {
header += ` in registries ${registries.join(", ")}`
}
header += ":"
const showingRange = `Showing items ${
results.pagination.offset + 1
}-${Math.min(
results.pagination.offset + results.pagination.limit,
results.pagination.total
)} of ${results.pagination.total}:`
let output = `${header}\n\n${showingRange}\n\n${formattedItems.join("\n\n")}`
if (results.pagination.hasMore) {
output += `\n\nMore items available. Use offset: ${
results.pagination.offset + results.pagination.limit
} to see the next page.`
}
return output
}
export function formatRegistryItems(
items: z.infer<typeof registryItemSchema>[]
) {
return items.map((item) => {
const parts: string[] = [
`## ${item.name}`,
item.description ? `\n${item.description}\n` : "",
item.type ? `**Type:** ${item.type}` : "",
item.files && item.files.length > 0
? `**Files:** ${item.files.length} file(s)`
: "",
item.dependencies && item.dependencies.length > 0
? `**Dependencies:** ${item.dependencies.join(", ")}`
: "",
item.devDependencies && item.devDependencies.length > 0
? `**Dev Dependencies:** ${item.devDependencies.join(", ")}`
: "",
]
return parts.filter(Boolean).join("\n")
})
}
export function formatItemExamples(
items: z.infer<typeof registryItemSchema>[],
query: string
) {
const sections = items.map((item) => {
const parts: string[] = [
`## Example: ${item.name}`,
item.description ? `\n${item.description}\n` : "",
]
if (item.files?.length) {
item.files.forEach((file) => {
if (file.content) {
parts.push(`### Code (${file.path}):\n`)
parts.push("```tsx")
parts.push(file.content)
parts.push("```")
}
})
}
return parts.filter(Boolean).join("\n")
})
const header = `# Usage Examples\n\nFound ${items.length} example${
items.length > 1 ? "s" : ""
} matching "${query}":\n`
return header + sections.join("\n\n---\n\n")
}

Some files were not shown because too many files have changed in this diff Show More