mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
feat: add @shadcn/ui cli (#112)
This commit is contained in:
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Changesets
|
||||||
|
|
||||||
|
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||||
|
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||||
|
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||||
|
|
||||||
|
We have a quick list of common questions to get you started engaging with this project in
|
||||||
|
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||||
|
"changelog": ["@changesets/changelog-github", { "repo": "shadcn/ui" }],
|
||||||
|
"commit": false,
|
||||||
|
"fixed": [],
|
||||||
|
"linked": [],
|
||||||
|
"access": "public",
|
||||||
|
"baseBranch": "main",
|
||||||
|
"updateInternalDependencies": "patch",
|
||||||
|
"ignore": ["www", "playground", "**-template"]
|
||||||
|
}
|
||||||
5
.changeset/khaki-roses-rule.md
Normal file
5
.changeset/khaki-roses-rule.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@shadcn/ui": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Initial commit.
|
||||||
45
.github/workflows/publish.yml
vendored
Normal file
45
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Adapted from create-t3-app.
|
||||||
|
|
||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: ${{ github.repository_owner == 'shadcn' }}
|
||||||
|
name: Create a PR for release workflow
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Use PNPM
|
||||||
|
uses: pnpm/action-setup@v2.2.4
|
||||||
|
|
||||||
|
- name: Use Node.js 18
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install NPM Dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Create Version PR or Publish to NPM
|
||||||
|
id: changesets
|
||||||
|
uses: changesets/action@v1.4.1
|
||||||
|
with:
|
||||||
|
commit: "chore(release): version packages"
|
||||||
|
title: "chore(release): version packages"
|
||||||
|
publish: pnpm build:cli
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||||
|
NODE_ENV: "production"
|
||||||
@@ -2,4 +2,5 @@ dist
|
|||||||
node_modules
|
node_modules
|
||||||
.next
|
.next
|
||||||
build
|
build
|
||||||
.contentlayer
|
.contentlayer
|
||||||
|
apps/www/pages/api/components.json
|
||||||
@@ -7,6 +7,7 @@ import { allDocs } from "contentlayer/generated"
|
|||||||
import { siteConfig } from "@/config/site"
|
import { siteConfig } from "@/config/site"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Icons } from "@/components/icons"
|
import { Icons } from "@/components/icons"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuContent,
|
NavigationMenuContent,
|
||||||
@@ -16,8 +17,7 @@ import {
|
|||||||
NavigationMenuTrigger,
|
NavigationMenuTrigger,
|
||||||
navigationMenuTriggerStyle,
|
navigationMenuTriggerStyle,
|
||||||
} from "@/components/ui/navigation-menu"
|
} from "@/components/ui/navigation-menu"
|
||||||
import { buttonVariants } from "./ui/button"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Separator } from "./ui/separator"
|
|
||||||
|
|
||||||
export function MainNav() {
|
export function MainNav() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { DialogProps } from "@radix-ui/react-dialog"
|
import { DialogProps } from "@radix-ui/react-dialog"
|
||||||
import { Command as CommandPrimitive, useCommandState } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
import { ChevronsUpDown, Search } from "lucide-react"
|
import { Search } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
|||||||
295
apps/www/config/components.ts
Normal file
295
apps/www/config/components.ts
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
export const components = [
|
||||||
|
{
|
||||||
|
name: "Accordion",
|
||||||
|
dependencies: ["@radix-ui/react-accordion"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "accordion.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alert Dialog",
|
||||||
|
dependencies: ["@radix-ui/react-alert-dialog"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "alert-dialog.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Aspect Ratio",
|
||||||
|
dependencies: ["@radix-ui/react-aspect-ratio"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "aspect-ratio.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Avatar",
|
||||||
|
dependencies: ["@radix-ui/react-avatar"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "avatar.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Button",
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "button.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Checkbox",
|
||||||
|
dependencies: ["@radix-ui/react-checkbox"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "checkbox.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Collapsible",
|
||||||
|
dependencies: ["@radix-ui/react-collapsible"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "collapsible.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Command",
|
||||||
|
dependencies: ["cmdk"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "command.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Context Menu",
|
||||||
|
dependencies: ["@radix-ui/react-context-menu"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "context-menu.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dialog",
|
||||||
|
dependencies: ["@radix-ui/react-dialog"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "dialog.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dropdown Menu",
|
||||||
|
dependencies: ["@radix-ui/react-dropdown-menu"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "dropdown-menu.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Hover Card",
|
||||||
|
dependencies: ["@radix-ui/react-hover-card"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "hover-card.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: "Input", files: [{ name: "input.tsx", dir: "components/ui" }] },
|
||||||
|
{
|
||||||
|
name: "Label",
|
||||||
|
dependencies: ["@radix-ui/react-label"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "label.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Menubar",
|
||||||
|
dependencies: ["@radix-ui/react-menubar"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "menubar.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Navigation Menu",
|
||||||
|
dependencies: ["@radix-ui/react-navigation-menu"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "navigation-menu.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Popover",
|
||||||
|
dependencies: ["@radix-ui/react-popover"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "popover.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Progress",
|
||||||
|
dependencies: ["@radix-ui/react-progress"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "progress.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Radio Group",
|
||||||
|
dependencies: ["@radix-ui/react-radio-group"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "radio-group.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scroll-area",
|
||||||
|
dependencies: ["@radix-ui/react-scroll-area"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "scroll-area.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Select",
|
||||||
|
dependencies: ["@radix-ui/react-select"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "select.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Separator",
|
||||||
|
dependencies: ["@radix-ui/react-separator"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "separator.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sheet",
|
||||||
|
dependencies: ["@radix-ui/react-dialog"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "sheet.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Slider",
|
||||||
|
dependencies: ["@radix-ui/react-slider"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "slider.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Switch",
|
||||||
|
dependencies: ["@radix-ui/react-switch"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "switch.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tabs",
|
||||||
|
dependencies: ["@radix-ui/react-tabs"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "tabs.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Textarea",
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "textarea.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Toast",
|
||||||
|
dependencies: ["@radix-ui/react-toast"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "toast.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "use-toast.ts",
|
||||||
|
dir: "hooks",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Toggle",
|
||||||
|
dependencies: ["@radix-ui/react-toggle"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "toggle.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tooltip",
|
||||||
|
dependencies: ["@radix-ui/react-tooltip"],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "tooltip.tsx",
|
||||||
|
dir: "components/ui",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Context Menu
|
title: Context Menu
|
||||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||||
|
component: true
|
||||||
radix:
|
radix:
|
||||||
link: https://www.radix-ui.com/docs/primitives/components/context-menu
|
link: https://www.radix-ui.com/docs/primitives/components/context-menu
|
||||||
api: https://www.radix-ui.com/docs/primitives/components/context-menu#api-reference
|
api: https://www.radix-ui.com/docs/primitives/components/context-menu#api-reference
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Switch
|
title: Switch
|
||||||
description: A control that allows the user to toggle between checked and not checked.
|
description: A control that allows the user to toggle between checked and not checked.
|
||||||
|
component: true
|
||||||
radix:
|
radix:
|
||||||
link: https://www.radix-ui.com/docs/primitives/components/switch
|
link: https://www.radix-ui.com/docs/primitives/components/switch
|
||||||
api: https://www.radix-ui.com/docs/primitives/components/switch#api-reference
|
api: https://www.radix-ui.com/docs/primitives/components/switch#api-reference
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "pnpm build:components && next build",
|
||||||
|
"build:components": "ts-node --esm --project ./tsconfig.scripts.json ./scripts/build-components.ts",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"preview": "next build && next start",
|
"preview": "next build && next start",
|
||||||
|
|||||||
334
apps/www/pages/api/components.json
Normal file
334
apps/www/pages/api/components.json
Normal file
File diff suppressed because one or more lines are too long
14
apps/www/pages/api/components.ts
Normal file
14
apps/www/pages/api/components.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
|
import components from "./components.json"
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
if (req.method !== "GET") {
|
||||||
|
return res.status(405).end()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(components)
|
||||||
|
}
|
||||||
38
apps/www/scripts/build-components.ts
Normal file
38
apps/www/scripts/build-components.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import fs from "fs"
|
||||||
|
import path, { basename, dirname } from "path"
|
||||||
|
|
||||||
|
import { components } from "../config/components"
|
||||||
|
|
||||||
|
const payload = components
|
||||||
|
.map((component) => {
|
||||||
|
const files = component.files?.map((file) => {
|
||||||
|
const content = fs.readFileSync(
|
||||||
|
path.join(process.cwd(), file.dir, file.name),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
content,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...component,
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if (a.name > b.name) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(process.cwd(), "pages/api/components.json"),
|
||||||
|
JSON.stringify(payload, null, 2)
|
||||||
|
)
|
||||||
@@ -33,5 +33,5 @@
|
|||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
".contentlayer/generated"
|
".contentlayer/generated"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "./scripts/build-components.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
12
apps/www/tsconfig.scripts.json
Normal file
12
apps/www/tsconfig.scripts.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"isolatedModules": false
|
||||||
|
},
|
||||||
|
"include": [".contentlayer/generated", "scripts/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
|
"build:cli": "turbo --filter=@shadcn/ui build",
|
||||||
"dev": "turbo run dev --parallel",
|
"dev": "turbo run dev --parallel",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"preview": "turbo run preview",
|
"preview": "turbo run preview",
|
||||||
@@ -22,11 +23,13 @@
|
|||||||
"format:write": "turbo run format",
|
"format:write": "turbo run format",
|
||||||
"format:check": "turbo run format:check",
|
"format:check": "turbo run format:check",
|
||||||
"sync:templates": "./scripts/sync-templates.sh \"templates/*\"",
|
"sync:templates": "./scripts/sync-templates.sh \"templates/*\"",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install",
|
||||||
|
"release": "changeset version"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@7.13.5",
|
"packageManager": "pnpm@7.13.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.20.7",
|
"@babel/core": "^7.20.7",
|
||||||
|
"@changesets/cli": "^2.26.0",
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^3.7.1",
|
"@ianvs/prettier-plugin-sort-imports": "^3.7.1",
|
||||||
"@tailwindcss/line-clamp": "^0.4.2",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
@@ -36,6 +39,7 @@
|
|||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-react": "^7.31.11",
|
||||||
"eslint-plugin-tailwindcss": "^3.8.0",
|
"eslint-plugin-tailwindcss": "^3.8.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
"turbo": "^1.6.3"
|
"turbo": "^1.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
2
packages/cli/.gitignore
vendored
Normal file
2
packages/cli/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
components
|
||||||
|
dist
|
||||||
58
packages/cli/package.json
Normal file
58
packages/cli/package.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "@shadcn/ui",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Add @shadcn/ui components to your app.",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": {
|
||||||
|
"name": "shadcn",
|
||||||
|
"url": "https://twitter.com/shadcn"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/shadcn/ui.git",
|
||||||
|
"directory": "packages/cli"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"components",
|
||||||
|
"ui",
|
||||||
|
"tailwind",
|
||||||
|
"radix-ui",
|
||||||
|
"shadcn"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"exports": "./dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"@shadcn/cli": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"build": "tsup",
|
||||||
|
"typecheck": "tsc",
|
||||||
|
"clean": "rimraf dist && rimraf components",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
|
||||||
|
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
|
||||||
|
"release": "changeset version",
|
||||||
|
"pub:beta": "pnpm build && npm publish --tag beta",
|
||||||
|
"pub:next": "pnpm build && npm publish --tag next",
|
||||||
|
"pub:release": "pnpm build && npm publish"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "5.2.0",
|
||||||
|
"commander": "^10.0.0",
|
||||||
|
"execa": "^7.0.0",
|
||||||
|
"fs-extra": "^11.1.0",
|
||||||
|
"node-fetch": "^3.3.0",
|
||||||
|
"ora": "^6.1.2",
|
||||||
|
"prompts": "^2.4.2",
|
||||||
|
"zod": "^3.20.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.1",
|
||||||
|
"@types/prompts": "^2.4.2",
|
||||||
|
"rimraf": "^4.1.3",
|
||||||
|
"tsup": "^6.6.3",
|
||||||
|
"type-fest": "^3.6.1",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
113
packages/cli/src/index.ts
Normal file
113
packages/cli/src/index.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { existsSync, promises as fs } from "fs"
|
||||||
|
import path from "path"
|
||||||
|
import { Command } from "commander"
|
||||||
|
import { execa } from "execa"
|
||||||
|
import ora from "ora"
|
||||||
|
import prompts from "prompts"
|
||||||
|
|
||||||
|
import { Component, getAvailableComponents } from "./utils/get-components"
|
||||||
|
import { getPackageInfo } from "./utils/get-package-info"
|
||||||
|
import { getPackageManager } from "./utils/get-package-manager"
|
||||||
|
import { logger } from "./utils/logger"
|
||||||
|
|
||||||
|
process.on("SIGINT", () => process.exit(0))
|
||||||
|
process.on("SIGTERM", () => process.exit(0))
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const packageInfo = await getPackageInfo()
|
||||||
|
|
||||||
|
const program = new Command()
|
||||||
|
.name("@shadcn/ui")
|
||||||
|
.description("Add @shadcn/ui components to your project")
|
||||||
|
.version(
|
||||||
|
packageInfo.version || "1.0.0",
|
||||||
|
"-v, --version",
|
||||||
|
"display the version number"
|
||||||
|
)
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("add")
|
||||||
|
.description("add components to your project")
|
||||||
|
.action(async () => {
|
||||||
|
const { components, dir } = await promptForAddOptions()
|
||||||
|
if (!components?.length) {
|
||||||
|
logger.warn("No components selected. Nothing to install.")
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create componentPath directory if it doesn't exist.
|
||||||
|
const destinationDir = path.resolve(dir)
|
||||||
|
if (!existsSync(destinationDir)) {
|
||||||
|
const spinner = ora(`Creating ${dir}...`).start()
|
||||||
|
await fs.mkdir(destinationDir, { recursive: true })
|
||||||
|
spinner.succeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageManager = getPackageManager()
|
||||||
|
|
||||||
|
logger.success(`Installing components...`)
|
||||||
|
for (const component of components) {
|
||||||
|
const componentSpinner = ora(`${component.name}...`).start()
|
||||||
|
|
||||||
|
// Write the files.
|
||||||
|
for (const file of component.files) {
|
||||||
|
const filePath = path.resolve(dir, file.name)
|
||||||
|
await fs.writeFile(filePath, file.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install dependencies.
|
||||||
|
if (component.dependencies?.length) {
|
||||||
|
const dependencies = component.dependencies.join(" ")
|
||||||
|
await execa(packageManager, [
|
||||||
|
packageManager === "npm" ? "install" : "add",
|
||||||
|
dependencies,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
componentSpinner.succeed(component.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
program.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddOptions = {
|
||||||
|
components: Component[]
|
||||||
|
dir: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptForAddOptions() {
|
||||||
|
const availableComponents = await getAvailableComponents()
|
||||||
|
|
||||||
|
if (!availableComponents?.length) {
|
||||||
|
logger.error(
|
||||||
|
"An error occurred while fetching components. Please try again."
|
||||||
|
)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = await prompts([
|
||||||
|
{
|
||||||
|
type: "multiselect",
|
||||||
|
name: "components",
|
||||||
|
message: "Which component(s) would you like to add?",
|
||||||
|
hint: "Space to select. A to select all. I to invert selection.",
|
||||||
|
instructions: false,
|
||||||
|
|
||||||
|
choices: availableComponents.map((component) => ({
|
||||||
|
title: component.name,
|
||||||
|
value: component,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
name: "dir",
|
||||||
|
message: "Where would you like to install the component(s)?",
|
||||||
|
initial: "./components/ui",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
return options as AddOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
34
packages/cli/src/utils/get-components.ts
Normal file
34
packages/cli/src/utils/get-components.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import fetch from "node-fetch"
|
||||||
|
import * as z from "zod"
|
||||||
|
|
||||||
|
const baseUrl =
|
||||||
|
process.env.NODE_ENV === "production"
|
||||||
|
? "https://ui.shadcn.com"
|
||||||
|
: "http://localhost:3000"
|
||||||
|
|
||||||
|
const componentSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
dependencies: z.array(z.string()).optional(),
|
||||||
|
files: z.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
dir: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type Component = z.infer<typeof componentSchema>
|
||||||
|
|
||||||
|
const componentsSchema = z.array(componentSchema)
|
||||||
|
|
||||||
|
export async function getAvailableComponents() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/api/components`)
|
||||||
|
const components = await response.json()
|
||||||
|
|
||||||
|
return componentsSchema.parse(components)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to fetch components")
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/cli/src/utils/get-package-info.ts
Normal file
9
packages/cli/src/utils/get-package-info.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import path from "path"
|
||||||
|
import fs from "fs-extra"
|
||||||
|
import { type PackageJson } from "type-fest"
|
||||||
|
|
||||||
|
export function getPackageInfo() {
|
||||||
|
const packageJsonPath = path.join("package.json")
|
||||||
|
|
||||||
|
return fs.readJSONSync(packageJsonPath) as PackageJson
|
||||||
|
}
|
||||||
17
packages/cli/src/utils/get-package-manager.ts
Normal file
17
packages/cli/src/utils/get-package-manager.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export function getPackageManager() {
|
||||||
|
const userAgent = process.env.npm_config_user_agent
|
||||||
|
|
||||||
|
if (!userAgent) {
|
||||||
|
return "npm"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userAgent.startsWith("yarn")) {
|
||||||
|
return "yarn"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userAgent.startsWith("pnpm")) {
|
||||||
|
return "pnpm"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "npm"
|
||||||
|
}
|
||||||
16
packages/cli/src/utils/logger.ts
Normal file
16
packages/cli/src/utils/logger.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import chalk from "chalk"
|
||||||
|
|
||||||
|
export const logger = {
|
||||||
|
error(...args: unknown[]) {
|
||||||
|
console.log(chalk.red(...args))
|
||||||
|
},
|
||||||
|
warn(...args: unknown[]) {
|
||||||
|
console.log(chalk.yellow(...args))
|
||||||
|
},
|
||||||
|
info(...args: unknown[]) {
|
||||||
|
console.log(chalk.cyan(...args))
|
||||||
|
},
|
||||||
|
success(...args: unknown[]) {
|
||||||
|
console.log(chalk.green(...args))
|
||||||
|
},
|
||||||
|
}
|
||||||
13
packages/cli/tsconfig.json
Normal file
13
packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"isolatedModules": false,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
11
packages/cli/tsup.config.ts
Normal file
11
packages/cli/tsup.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from "tsup"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
clean: true,
|
||||||
|
dts: true,
|
||||||
|
entry: ["src/index.ts"],
|
||||||
|
format: ["esm"],
|
||||||
|
sourcemap: true,
|
||||||
|
target: "esnext",
|
||||||
|
outDir: "dist",
|
||||||
|
})
|
||||||
1841
pnpm-lock.yaml
generated
1841
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,8 @@
|
|||||||
"env": [
|
"env": [
|
||||||
"NEXT_PUBLIC_APP_URL",
|
"NEXT_PUBLIC_APP_URL",
|
||||||
"UPSTASH_REDIS_REST_URL",
|
"UPSTASH_REDIS_REST_URL",
|
||||||
"UPSTASH_REDIS_REST_TOKEN"
|
"UPSTASH_REDIS_REST_TOKEN",
|
||||||
|
"npm_config_user_agent"
|
||||||
],
|
],
|
||||||
"outputs": ["dist/**", ".next/**"]
|
"outputs": ["dist/**", ".next/**"]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user