mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-19 05:41:33 +00:00
Compare commits
44 Commits
shadcn@2.9
...
shadcn@3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b34f3fdc4f | ||
|
|
2ecf876fa1 | ||
|
|
dcd2c3ef14 | ||
|
|
17422714f6 | ||
|
|
fc27ba2692 | ||
|
|
f854190b53 | ||
|
|
396275e46a | ||
|
|
296feb28a2 | ||
|
|
a941287411 | ||
|
|
2e34c95c4e | ||
|
|
fed7e3bfdc | ||
|
|
4f5333ea7a | ||
|
|
b5b8deedde | ||
|
|
7d71b02fb1 | ||
|
|
b3639227d0 | ||
|
|
a4a3600757 | ||
|
|
a426fea941 | ||
|
|
6e870c3993 | ||
|
|
68aa3389de | ||
|
|
2e9ccede8f | ||
|
|
fc8927a1f9 | ||
|
|
ccfd14946b | ||
|
|
01c02b289a | ||
|
|
a80ab37483 | ||
|
|
469250115f | ||
|
|
2c164b0f22 | ||
|
|
578f83cbef | ||
|
|
07eda36b13 | ||
|
|
0eccdc9c5f | ||
|
|
0940c6aec7 | ||
|
|
e244952500 | ||
|
|
0e3d6b24d3 | ||
|
|
cef5af9ed3 | ||
|
|
6deb0fdbb6 | ||
|
|
e9ae79f874 | ||
|
|
d891132f2a | ||
|
|
873f7f2773 | ||
|
|
e6778dee87 | ||
|
|
97a8de1c1b | ||
|
|
19d7fbb731 | ||
|
|
a9ab05ad83 | ||
|
|
6ac114ae68 | ||
|
|
d5770e4350 | ||
|
|
4730276256 |
@@ -7,5 +7,5 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["www", "v4"]
|
||||
"ignore": ["www", "v4", "tests"]
|
||||
}
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -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"
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import { registryItemSchema } from "shadcn/registry"
|
||||
import { registryItemSchema } from "shadcn/schema"
|
||||
import { z } from "zod"
|
||||
|
||||
export async function getAllBlockIds(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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*",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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
@@ -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": {}
|
||||
}
|
||||
5793
apps/v4/public/r/styles/default/registry.json
Normal file
5793
apps/v4/public/r/styles/default/registry.json
Normal file
File diff suppressed because it is too large
Load Diff
23
apps/v4/public/r/styles/default/style.json
Normal file
23
apps/v4/public/r/styles/default/style.json
Normal 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
5777
apps/v4/public/r/styles/new-york-v4/registry.json
Normal file
5777
apps/v4/public/r/styles/new-york-v4/registry.json
Normal file
File diff suppressed because it is too large
Load Diff
17
apps/v4/public/r/styles/new-york-v4/style.json
Normal file
17
apps/v4/public/r/styles/new-york-v4/style.json
Normal 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
@@ -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": {}
|
||||
}
|
||||
5793
apps/v4/public/r/styles/new-york/registry.json
Normal file
5793
apps/v4/public/r/styles/new-york/registry.json
Normal file
File diff suppressed because it is too large
Load Diff
23
apps/v4/public/r/styles/new-york/style.json
Normal file
23
apps/v4/public/r/styles/new-york/style.json
Normal 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": {}
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -257,7 +257,6 @@ export function ChartAreaInteractive() {
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
defaultIndex={isMobile ? -1 : 10}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(value) => {
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const blocks: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const charts: Registry["items"] = [
|
||||
// Area Charts
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const examples: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const hooks: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const lib: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const themes: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const ui: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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": {}
|
||||
}
|
||||
6942
apps/www/public/r/styles/default/registry.json
Normal file
6942
apps/www/public/r/styles/default/registry.json
Normal file
File diff suppressed because it is too large
Load Diff
23
apps/www/public/r/styles/default/style.json
Normal file
23
apps/www/public/r/styles/default/style.json
Normal 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
5777
apps/www/public/r/styles/new-york-v4/registry.json
Normal file
5777
apps/www/public/r/styles/new-york-v4/registry.json
Normal file
File diff suppressed because it is too large
Load Diff
17
apps/www/public/r/styles/new-york-v4/style.json
Normal file
17
apps/www/public/r/styles/new-york-v4/style.json
Normal 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
@@ -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": {}
|
||||
}
|
||||
6942
apps/www/public/r/styles/new-york/registry.json
Normal file
6942
apps/www/public/r/styles/new-york/registry.json
Normal file
File diff suppressed because it is too large
Load Diff
23
apps/www/public/r/styles/new-york/style.json
Normal file
23
apps/www/public/r/styles/new-york/style.json
Normal 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": {}
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const blocks: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const charts: Registry["items"] = [
|
||||
// Area Charts
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const examples: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const hooks: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const internal: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const lib: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const themes: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Registry } from "shadcn/registry"
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const ui: Registry["items"] = [
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
198
packages/shadcn/src/commands/mcp.ts
Normal file
198
packages/shadcn/src/commands/mcp.ts
Normal 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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
105
packages/shadcn/src/commands/search.ts
Normal file
105
packages/shadcn/src/commands/search.ts
Normal 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()
|
||||
}
|
||||
})
|
||||
68
packages/shadcn/src/commands/view.ts
Normal file
68
packages/shadcn/src/commands/view.ts
Normal 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()
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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`
|
||||
}
|
||||
|
||||
132
packages/shadcn/src/mcp/utils.ts
Normal file
132
packages/shadcn/src/mcp/utils.ts
Normal 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
Reference in New Issue
Block a user