mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
feat(shadcn): monorepo support (#6104)
This commit is contained in:
8
.eslintignore
Normal file
8
.eslintignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
target/
|
||||||
|
.next/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
/templates/
|
||||||
|
/fixtures/
|
||||||
2
.github/workflows/prerelease-comment.yml
vendored
2
.github/workflows/prerelease-comment.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
A new prerelease is available for testing:
|
A new prerelease is available for testing:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||||
```
|
```
|
||||||
|
|
||||||
- name: "Remove the autorelease label once published"
|
- name: "Remove the autorelease label once published"
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { ArrowRight } from "lucide-react"
|
|||||||
export function Announcement() {
|
export function Announcement() {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href="/docs/components/sidebar"
|
href="/docs/monorepo"
|
||||||
className="group mb-2 inline-flex items-center px-0.5 text-sm font-medium"
|
className="group mb-2 inline-flex items-center px-0.5 text-sm font-medium"
|
||||||
>
|
>
|
||||||
<span className="underline-offset-4 group-hover:underline">
|
<span className="underline-offset-4 group-hover:underline">
|
||||||
New sidebar component
|
Monorepo support
|
||||||
</span>
|
</span>
|
||||||
<ArrowRight className="ml-1 h-4 w-4" />
|
<ArrowRight className="ml-1 h-4 w-4" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ export const docsConfig: DocsConfig = {
|
|||||||
href: "/docs/cli",
|
href: "/docs/cli",
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Monorepo",
|
||||||
|
href: "/docs/monorepo",
|
||||||
|
items: [],
|
||||||
|
label: "New",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Next.js 15 + React 19",
|
title: "Next.js 15 + React 19",
|
||||||
href: "/docs/react-19",
|
href: "/docs/react-19",
|
||||||
@@ -141,12 +147,6 @@ export const docsConfig: DocsConfig = {
|
|||||||
{
|
{
|
||||||
title: "Components",
|
title: "Components",
|
||||||
items: [
|
items: [
|
||||||
{
|
|
||||||
title: "Sidebar",
|
|
||||||
href: "/docs/components/sidebar",
|
|
||||||
items: [],
|
|
||||||
label: "New",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Accordion",
|
title: "Accordion",
|
||||||
href: "/docs/components/accordion",
|
href: "/docs/components/accordion",
|
||||||
@@ -337,6 +337,11 @@ export const docsConfig: DocsConfig = {
|
|||||||
href: "/docs/components/sheet",
|
href: "/docs/components/sheet",
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Sidebar",
|
||||||
|
href: "/docs/components/sidebar",
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Skeleton",
|
title: "Skeleton",
|
||||||
href: "/docs/components/skeleton",
|
href: "/docs/components/skeleton",
|
||||||
|
|||||||
175
apps/www/content/docs/monorepo.mdx
Normal file
175
apps/www/content/docs/monorepo.mdx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
title: Monorepo
|
||||||
|
description: Using shadcn/ui components and CLI in a monorepo.
|
||||||
|
---
|
||||||
|
|
||||||
|
<Callout>
|
||||||
|
**Note:** We're releasing monorepo support in the CLI as __experimental__.
|
||||||
|
Help us improve it by testing it out and sending feedback. If you have any
|
||||||
|
questions, please [reach out to
|
||||||
|
us](https://github.com/shadcn-ui/ui/discussions).
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
Until now, using shadcn/ui in a monorepo was a bit of a pain. You could add
|
||||||
|
components using the CLI, but you had to manage where the components
|
||||||
|
were installed and manually fix import paths.
|
||||||
|
|
||||||
|
With the new monorepo support in the CLI, we've made it a lot easier to use
|
||||||
|
shadcn/ui in a monorepo.
|
||||||
|
|
||||||
|
The CLI now understands the monorepo structure and will install the components,
|
||||||
|
dependencies and registry dependencies to the correct paths and handle imports
|
||||||
|
for you.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
### Create a new monorepo project
|
||||||
|
|
||||||
|
To create a new monorepo project, run the `init` command. You will be prompted
|
||||||
|
to select the type of project you are creating.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@canary init
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the `Next.js (Monorepo)` option.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
? Would you like to start a new project?
|
||||||
|
Next.js
|
||||||
|
❯ Next.js (Monorepo)
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a new monorepo project with two workspaces: `web` and `ui`,
|
||||||
|
and [Turborepo](https://turbo.build/repo/docs) as the build system.
|
||||||
|
|
||||||
|
Everything is set up for you, so you can start adding components to your project.
|
||||||
|
|
||||||
|
### Add components to your project
|
||||||
|
|
||||||
|
To add components to your project, run the `add` command **in the path of your app**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/web
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@canary add [COMPONENT]
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI will figure out what type of component you are adding and install the
|
||||||
|
correct files to the correct path.
|
||||||
|
|
||||||
|
For example, if you run `npx shadcn@canary add button`, the CLI will install the button component under `packages/ui` and update the import path for components in `apps/web`.
|
||||||
|
|
||||||
|
If you run `npx shadcn@canary add login-01`, the CLI will install the `button`, `label`, `input` and `card` components under `packages/ui` and the `login-form` component under `apps/web/components`.
|
||||||
|
|
||||||
|
### Importing components
|
||||||
|
|
||||||
|
You can import components from the `@workspace/ui` package as follows:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Button } from "@workspace/ui/components/button"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also import hooks and utilities from the `@workspace/ui` package.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useTheme } from "@workspace/ui/hooks/use-theme"
|
||||||
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
When you create a new monorepo project, the CLI will create the following file structure:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
apps
|
||||||
|
└── web # Your app goes here.
|
||||||
|
├── app
|
||||||
|
│ └── page.tsx
|
||||||
|
├── components
|
||||||
|
│ └── login-form.tsx
|
||||||
|
├── components.json
|
||||||
|
└── package.json
|
||||||
|
packages
|
||||||
|
└── ui # Your components and dependencies are installed here.
|
||||||
|
├── src
|
||||||
|
│ ├── components
|
||||||
|
│ │ └── button.tsx
|
||||||
|
│ ├── hooks
|
||||||
|
│ ├── lib
|
||||||
|
│ │ └── utils.ts
|
||||||
|
│ └── styles
|
||||||
|
│ └── globals.css
|
||||||
|
├── components.json
|
||||||
|
└── package.json
|
||||||
|
package.json
|
||||||
|
turbo.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Every workspace must have a `components.json` file. A `package.json` file tells npm how to install the dependencies. A `components.json` file tells the CLI how and where to install components.
|
||||||
|
|
||||||
|
2. The `components.json` file must properly define aliases for the workspace. This tells the CLI how to import components, hooks, utilities, etc.
|
||||||
|
|
||||||
|
```json title="apps/web/components.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "../../packages/ui/tailwind.config.ts",
|
||||||
|
"css": "../../packages/ui/src/styles/globals.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"hooks": "@/hooks",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"utils": "@workspace/ui/lib/utils",
|
||||||
|
"ui": "@workspace/ui/components"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json title="packages/ui/components.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/styles/globals.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@workspace/ui/components",
|
||||||
|
"utils": "@workspace/ui/lib/utils",
|
||||||
|
"hooks": "@workspace/ui/hooks",
|
||||||
|
"lib": "@workspace/ui/lib",
|
||||||
|
"ui": "@workspace/ui/components"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Ensure you have the same `style`, `iconLibrary` and `baseColor` in both `components.json` files.
|
||||||
|
|
||||||
|
By following these requirements, the CLI will be able to install ui components, blocks, libs and hooks to the correct paths and handle imports for you.
|
||||||
|
|
||||||
|
## Help us improve monorepo support
|
||||||
|
|
||||||
|
We're releasing monorepo support in the CLI as **experimental**. Help us improve it by testing it out and sending feedback.
|
||||||
|
|
||||||
|
If you have any questions, please reach out to us on [GitHub Discussions](https://github.com/shadcn-ui/ui/discussions).
|
||||||
@@ -10,8 +10,7 @@
|
|||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*",
|
"packages/*"
|
||||||
"templates/*"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { preFlightAdd } from "@/src/preflights/preflight-add"
|
|||||||
import { addComponents } from "@/src/utils/add-components"
|
import { addComponents } from "@/src/utils/add-components"
|
||||||
import { createProject } from "@/src/utils/create-project"
|
import { createProject } from "@/src/utils/create-project"
|
||||||
import * as ERRORS from "@/src/utils/errors"
|
import * as ERRORS from "@/src/utils/errors"
|
||||||
|
import { getConfig } from "@/src/utils/get-config"
|
||||||
import { handleError } from "@/src/utils/handle-error"
|
import { handleError } from "@/src/utils/handle-error"
|
||||||
import { highlighter } from "@/src/utils/highlighter"
|
import { highlighter } from "@/src/utils/highlighter"
|
||||||
import { logger } from "@/src/utils/logger"
|
import { logger } from "@/src/utils/logger"
|
||||||
@@ -112,7 +113,7 @@ export const add = new Command()
|
|||||||
|
|
||||||
let shouldUpdateAppIndex = false
|
let shouldUpdateAppIndex = false
|
||||||
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
||||||
const { projectPath } = await createProject({
|
const { projectPath, projectType } = await createProject({
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
force: options.overwrite,
|
force: options.overwrite,
|
||||||
srcDir: options.srcDir,
|
srcDir: options.srcDir,
|
||||||
@@ -124,20 +125,25 @@ export const add = new Command()
|
|||||||
}
|
}
|
||||||
options.cwd = projectPath
|
options.cwd = projectPath
|
||||||
|
|
||||||
config = await runInit({
|
if (projectType === "monorepo") {
|
||||||
cwd: options.cwd,
|
options.cwd = path.resolve(options.cwd, "apps/web")
|
||||||
yes: true,
|
config = await getConfig(options.cwd)
|
||||||
force: true,
|
} else {
|
||||||
defaults: false,
|
config = await runInit({
|
||||||
skipPreflight: true,
|
cwd: options.cwd,
|
||||||
silent: true,
|
yes: true,
|
||||||
isNewProject: true,
|
force: true,
|
||||||
srcDir: options.srcDir,
|
defaults: false,
|
||||||
})
|
skipPreflight: true,
|
||||||
|
silent: true,
|
||||||
|
isNewProject: true,
|
||||||
|
srcDir: options.srcDir,
|
||||||
|
})
|
||||||
|
|
||||||
shouldUpdateAppIndex =
|
shouldUpdateAppIndex =
|
||||||
options.components?.length === 1 &&
|
options.components?.length === 1 &&
|
||||||
!!options.components[0].match(/\/chat\/b\//)
|
!!options.components[0].match(/\/chat\/b\//)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
|||||||
@@ -86,21 +86,28 @@ export async function runInit(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
let projectInfo
|
let projectInfo
|
||||||
|
let newProjectType
|
||||||
if (!options.skipPreflight) {
|
if (!options.skipPreflight) {
|
||||||
const preflight = await preFlightInit(options)
|
const preflight = await preFlightInit(options)
|
||||||
if (preflight.errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
if (preflight.errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
||||||
const { projectPath } = await createProject(options)
|
const { projectPath, projectType } = await createProject(options)
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
options.cwd = projectPath
|
options.cwd = projectPath
|
||||||
options.isNewProject = true
|
options.isNewProject = true
|
||||||
|
newProjectType = projectType
|
||||||
}
|
}
|
||||||
projectInfo = preflight.projectInfo
|
projectInfo = preflight.projectInfo
|
||||||
} else {
|
} else {
|
||||||
projectInfo = await getProjectInfo(options.cwd)
|
projectInfo = await getProjectInfo(options.cwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newProjectType === "monorepo") {
|
||||||
|
options.cwd = path.resolve(options.cwd, "apps/web")
|
||||||
|
return await getConfig(options.cwd)
|
||||||
|
}
|
||||||
|
|
||||||
const projectConfig = await getProjectConfig(options.cwd, projectInfo)
|
const projectConfig = await getProjectConfig(options.cwd, projectInfo)
|
||||||
const config = projectConfig
|
const config = projectConfig
|
||||||
? await promptForMinimalConfig(projectConfig, options)
|
? await promptForMinimalConfig(projectConfig, options)
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
import { type Config } from "@/src/utils/get-config"
|
import path from "path"
|
||||||
|
import {
|
||||||
|
configSchema,
|
||||||
|
findCommonRoot,
|
||||||
|
findPackageRoot,
|
||||||
|
getWorkspaceConfig,
|
||||||
|
workspaceConfigSchema,
|
||||||
|
type Config,
|
||||||
|
} from "@/src/utils/get-config"
|
||||||
import { handleError } from "@/src/utils/handle-error"
|
import { handleError } from "@/src/utils/handle-error"
|
||||||
import { logger } from "@/src/utils/logger"
|
import { logger } from "@/src/utils/logger"
|
||||||
import { registryResolveItemsTree } from "@/src/utils/registry"
|
import {
|
||||||
|
fetchRegistry,
|
||||||
|
getRegistryParentMap,
|
||||||
|
getRegistryTypeAliasMap,
|
||||||
|
registryResolveItemsTree,
|
||||||
|
resolveRegistryItems,
|
||||||
|
} from "@/src/utils/registry"
|
||||||
|
import { registryItemSchema } from "@/src/utils/registry/schema"
|
||||||
import { spinner } from "@/src/utils/spinner"
|
import { spinner } from "@/src/utils/spinner"
|
||||||
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
|
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
|
||||||
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
|
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
|
||||||
import { updateFiles } from "@/src/utils/updaters/update-files"
|
import { updateFiles } from "@/src/utils/updaters/update-files"
|
||||||
import { updateTailwindConfig } from "@/src/utils/updaters/update-tailwind-config"
|
import { updateTailwindConfig } from "@/src/utils/updaters/update-tailwind-config"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
export async function addComponents(
|
export async function addComponents(
|
||||||
components: string[],
|
components: string[],
|
||||||
@@ -24,6 +40,30 @@ export async function addComponents(
|
|||||||
...options,
|
...options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const workspaceConfig = await getWorkspaceConfig(config)
|
||||||
|
if (
|
||||||
|
workspaceConfig &&
|
||||||
|
workspaceConfig?.ui.resolvedPaths.cwd !== config.resolvedPaths.cwd
|
||||||
|
) {
|
||||||
|
return await addWorkspaceComponents(components, config, workspaceConfig, {
|
||||||
|
...options,
|
||||||
|
isRemote:
|
||||||
|
components?.length === 1 && !!components[0].match(/\/chat\/b\//),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await addProjectComponents(components, config, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addProjectComponents(
|
||||||
|
components: string[],
|
||||||
|
config: z.infer<typeof configSchema>,
|
||||||
|
options: {
|
||||||
|
overwrite?: boolean
|
||||||
|
silent?: boolean
|
||||||
|
isNewProject?: boolean
|
||||||
|
}
|
||||||
|
) {
|
||||||
const registrySpinner = spinner(`Checking registry.`, {
|
const registrySpinner = spinner(`Checking registry.`, {
|
||||||
silent: options.silent,
|
silent: options.silent,
|
||||||
})?.start()
|
})?.start()
|
||||||
@@ -54,3 +94,166 @@ export async function addComponents(
|
|||||||
logger.info(tree.docs)
|
logger.info(tree.docs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addWorkspaceComponents(
|
||||||
|
components: string[],
|
||||||
|
config: z.infer<typeof configSchema>,
|
||||||
|
workspaceConfig: z.infer<typeof workspaceConfigSchema>,
|
||||||
|
options: {
|
||||||
|
overwrite?: boolean
|
||||||
|
silent?: boolean
|
||||||
|
isNewProject?: boolean
|
||||||
|
isRemote?: boolean
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const registrySpinner = spinner(`Checking registry.`, {
|
||||||
|
silent: options.silent,
|
||||||
|
})?.start()
|
||||||
|
let registryItems = await resolveRegistryItems(components, config)
|
||||||
|
let result = await fetchRegistry(registryItems)
|
||||||
|
const payload = z.array(registryItemSchema).parse(result)
|
||||||
|
if (!payload) {
|
||||||
|
registrySpinner?.fail()
|
||||||
|
return handleError(new Error("Failed to fetch components from registry."))
|
||||||
|
}
|
||||||
|
registrySpinner?.succeed()
|
||||||
|
|
||||||
|
const registryParentMap = getRegistryParentMap(payload)
|
||||||
|
const registryTypeAliasMap = getRegistryTypeAliasMap()
|
||||||
|
|
||||||
|
const filesCreated: string[] = []
|
||||||
|
const filesUpdated: string[] = []
|
||||||
|
const filesSkipped: string[] = []
|
||||||
|
|
||||||
|
const rootSpinner = spinner(`Installing components.`)?.start()
|
||||||
|
|
||||||
|
for (const component of payload) {
|
||||||
|
const alias = registryTypeAliasMap.get(component.type)
|
||||||
|
const registryParent = registryParentMap.get(component.name)
|
||||||
|
|
||||||
|
// We don't support this type of component.
|
||||||
|
if (!alias) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A good start is ui for now.
|
||||||
|
// TODO: Add support for other types.
|
||||||
|
let targetConfig =
|
||||||
|
component.type === "registry:ui" || registryParent?.type === "registry:ui"
|
||||||
|
? workspaceConfig.ui
|
||||||
|
: config
|
||||||
|
|
||||||
|
const workspaceRoot = findCommonRoot(
|
||||||
|
config.resolvedPaths.cwd,
|
||||||
|
targetConfig.resolvedPaths.ui
|
||||||
|
)
|
||||||
|
const packageRoot =
|
||||||
|
(await findPackageRoot(workspaceRoot, targetConfig.resolvedPaths.cwd)) ??
|
||||||
|
targetConfig.resolvedPaths.cwd
|
||||||
|
|
||||||
|
// 1. Update tailwind config.
|
||||||
|
if (component.tailwind?.config) {
|
||||||
|
await updateTailwindConfig(component.tailwind?.config, targetConfig, {
|
||||||
|
silent: true,
|
||||||
|
})
|
||||||
|
filesUpdated.push(
|
||||||
|
path.relative(workspaceRoot, targetConfig.resolvedPaths.tailwindConfig)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Update css vars.
|
||||||
|
if (component.cssVars) {
|
||||||
|
await updateCssVars(component.cssVars, targetConfig, {
|
||||||
|
silent: true,
|
||||||
|
})
|
||||||
|
filesUpdated.push(
|
||||||
|
path.relative(workspaceRoot, targetConfig.resolvedPaths.tailwindCss)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Update dependencies.
|
||||||
|
await updateDependencies(component.dependencies, targetConfig, {
|
||||||
|
silent: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. Update files.
|
||||||
|
const files = await updateFiles(component.files, targetConfig, {
|
||||||
|
overwrite: options.overwrite,
|
||||||
|
silent: true,
|
||||||
|
rootSpinner,
|
||||||
|
isRemote: options.isRemote,
|
||||||
|
})
|
||||||
|
|
||||||
|
filesCreated.push(
|
||||||
|
...files.filesCreated.map((file) =>
|
||||||
|
path.relative(workspaceRoot, path.join(packageRoot, file))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
filesUpdated.push(
|
||||||
|
...files.filesUpdated.map((file) =>
|
||||||
|
path.relative(workspaceRoot, path.join(packageRoot, file))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
filesSkipped.push(
|
||||||
|
...files.filesSkipped.map((file) =>
|
||||||
|
path.relative(workspaceRoot, path.join(packageRoot, file))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootSpinner?.succeed()
|
||||||
|
|
||||||
|
// Sort files.
|
||||||
|
filesCreated.sort()
|
||||||
|
filesUpdated.sort()
|
||||||
|
filesSkipped.sort()
|
||||||
|
|
||||||
|
const hasUpdatedFiles = filesCreated.length || filesUpdated.length
|
||||||
|
if (!hasUpdatedFiles && !filesSkipped.length) {
|
||||||
|
spinner(`No files updated.`, {
|
||||||
|
silent: options.silent,
|
||||||
|
})?.info()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filesCreated.length) {
|
||||||
|
spinner(
|
||||||
|
`Created ${filesCreated.length} ${
|
||||||
|
filesCreated.length === 1 ? "file" : "files"
|
||||||
|
}:`,
|
||||||
|
{
|
||||||
|
silent: options.silent,
|
||||||
|
}
|
||||||
|
)?.succeed()
|
||||||
|
for (const file of filesCreated) {
|
||||||
|
logger.log(` - ${file}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filesUpdated.length) {
|
||||||
|
spinner(
|
||||||
|
`Updated ${filesUpdated.length} ${
|
||||||
|
filesUpdated.length === 1 ? "file" : "files"
|
||||||
|
}:`,
|
||||||
|
{
|
||||||
|
silent: options.silent,
|
||||||
|
}
|
||||||
|
)?.info()
|
||||||
|
for (const file of filesUpdated) {
|
||||||
|
logger.log(` - ${file}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filesSkipped.length) {
|
||||||
|
spinner(
|
||||||
|
`Skipped ${filesSkipped.length} ${
|
||||||
|
filesUpdated.length === 1 ? "file" : "files"
|
||||||
|
}: (use --overwrite to overwrite)`,
|
||||||
|
{
|
||||||
|
silent: options.silent,
|
||||||
|
}
|
||||||
|
)?.info()
|
||||||
|
for (const file of filesSkipped) {
|
||||||
|
logger.log(` - ${file}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { initOptionsSchema } from "@/src/commands/init"
|
import { initOptionsSchema } from "@/src/commands/init"
|
||||||
import { getPackageManager } from "@/src/utils/get-package-manager"
|
import { getPackageManager } from "@/src/utils/get-package-manager"
|
||||||
@@ -11,6 +12,9 @@ import fs from "fs-extra"
|
|||||||
import prompts from "prompts"
|
import prompts from "prompts"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
const MONOREPO_TEMPLATE_URL =
|
||||||
|
"https://codeload.github.com/shadcn-ui/ui/tar.gz/main"
|
||||||
|
|
||||||
export async function createProject(
|
export async function createProject(
|
||||||
options: Pick<
|
options: Pick<
|
||||||
z.infer<typeof initOptionsSchema>,
|
z.infer<typeof initOptionsSchema>,
|
||||||
@@ -22,11 +26,14 @@ export async function createProject(
|
|||||||
...options,
|
...options,
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextVersion = "14.2.16"
|
let projectType: "next" | "monorepo" = "next"
|
||||||
|
let projectName: string = "my-app"
|
||||||
|
let nextVersion = "15.1.0"
|
||||||
|
|
||||||
const isRemoteComponent =
|
const isRemoteComponent =
|
||||||
options.components?.length === 1 &&
|
options.components?.length === 1 &&
|
||||||
!!options.components[0].match(/\/chat\/b\//)
|
!!options.components[0].match(/\/chat\/b\//)
|
||||||
|
|
||||||
if (options.components && isRemoteComponent) {
|
if (options.components && isRemoteComponent) {
|
||||||
try {
|
try {
|
||||||
const [result] = await fetchRegistry(options.components)
|
const [result] = await fetchRegistry(options.components)
|
||||||
@@ -45,40 +52,41 @@ export async function createProject(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!options.force) {
|
if (!options.force) {
|
||||||
const { proceed } = await prompts({
|
const { type, name } = await prompts([
|
||||||
type: "confirm",
|
{
|
||||||
name: "proceed",
|
type: "select",
|
||||||
message: `The path ${highlighter.info(
|
name: "type",
|
||||||
options.cwd
|
message: `The path ${highlighter.info(
|
||||||
)} does not contain a package.json file. Would you like to start a new ${highlighter.info(
|
options.cwd
|
||||||
"Next.js"
|
)} does not contain a package.json file.\n Would you like to start a new project?`,
|
||||||
)} project?`,
|
choices: [
|
||||||
initial: true,
|
{ title: "Next.js", value: "next" },
|
||||||
})
|
{ title: "Next.js (Monorepo)", value: "monorepo" },
|
||||||
|
],
|
||||||
|
initial: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
name: "name",
|
||||||
|
message: "What is your project named?",
|
||||||
|
initial: projectName,
|
||||||
|
format: (value: string) => value.trim(),
|
||||||
|
validate: (value: string) =>
|
||||||
|
value.length > 128
|
||||||
|
? `Name should be less than 128 characters.`
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
if (!proceed) {
|
projectType = type
|
||||||
return {
|
projectName = name
|
||||||
projectPath: null,
|
|
||||||
projectName: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageManager = await getPackageManager(options.cwd, {
|
const packageManager = await getPackageManager(options.cwd, {
|
||||||
withFallback: true,
|
withFallback: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { name } = await prompts({
|
const projectPath = `${options.cwd}/${projectName}`
|
||||||
type: "text",
|
|
||||||
name: "name",
|
|
||||||
message: `What is your project named?`,
|
|
||||||
initial: "my-app",
|
|
||||||
format: (value: string) => value.trim(),
|
|
||||||
validate: (value: string) =>
|
|
||||||
value.length > 128 ? `Name should be less than 128 characters.` : true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const projectPath = `${options.cwd}/${name}`
|
|
||||||
|
|
||||||
// Check if path is writable.
|
// Check if path is writable.
|
||||||
try {
|
try {
|
||||||
@@ -95,16 +103,47 @@ export async function createProject(
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(path.resolve(options.cwd, name, "package.json"))) {
|
if (fs.existsSync(path.resolve(options.cwd, projectName, "package.json"))) {
|
||||||
logger.break()
|
logger.break()
|
||||||
logger.error(
|
logger.error(
|
||||||
`A project with the name ${highlighter.info(name)} already exists.`
|
`A project with the name ${highlighter.info(projectName)} already exists.`
|
||||||
)
|
)
|
||||||
logger.error(`Please choose a different name and try again.`)
|
logger.error(`Please choose a different name and try again.`)
|
||||||
logger.break()
|
logger.break()
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (projectType === "next") {
|
||||||
|
await createNextProject(projectPath, {
|
||||||
|
version: nextVersion,
|
||||||
|
cwd: options.cwd,
|
||||||
|
packageManager,
|
||||||
|
srcDir: !!options.srcDir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectType === "monorepo") {
|
||||||
|
await createMonorepoProject(projectPath, {
|
||||||
|
packageManager,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
projectPath,
|
||||||
|
projectName,
|
||||||
|
projectType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createNextProject(
|
||||||
|
projectPath: string,
|
||||||
|
options: {
|
||||||
|
version: string
|
||||||
|
cwd: string
|
||||||
|
packageManager: string
|
||||||
|
srcDir: boolean
|
||||||
|
}
|
||||||
|
) {
|
||||||
const createSpinner = spinner(
|
const createSpinner = spinner(
|
||||||
`Creating a new Next.js project. This may take a few minutes.`
|
`Creating a new Next.js project. This may take a few minutes.`
|
||||||
).start()
|
).start()
|
||||||
@@ -117,17 +156,17 @@ export async function createProject(
|
|||||||
"--app",
|
"--app",
|
||||||
options.srcDir ? "--src-dir" : "--no-src-dir",
|
options.srcDir ? "--src-dir" : "--no-src-dir",
|
||||||
"--no-import-alias",
|
"--no-import-alias",
|
||||||
`--use-${packageManager}`,
|
`--use-${options.packageManager}`,
|
||||||
]
|
]
|
||||||
|
|
||||||
if (nextVersion.startsWith("15")) {
|
if (options.version.startsWith("15")) {
|
||||||
args.push("--turbopack")
|
args.push("--turbopack")
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await execa(
|
await execa(
|
||||||
"npx",
|
"npx",
|
||||||
[`create-next-app@${nextVersion}`, projectPath, "--silent", ...args],
|
[`create-next-app@${options.version}`, projectPath, "--silent", ...args],
|
||||||
{
|
{
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
}
|
}
|
||||||
@@ -141,9 +180,60 @@ export async function createProject(
|
|||||||
}
|
}
|
||||||
|
|
||||||
createSpinner?.succeed("Creating a new Next.js project.")
|
createSpinner?.succeed("Creating a new Next.js project.")
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
async function createMonorepoProject(
|
||||||
projectPath,
|
projectPath: string,
|
||||||
projectName: name,
|
options: {
|
||||||
|
packageManager: string
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const createSpinner = spinner(
|
||||||
|
`Creating a new Next.js monorepo. This may take a few minutes.`
|
||||||
|
).start()
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the template.
|
||||||
|
const templatePath = path.join(os.tmpdir(), `shadcn-template-${Date.now()}`)
|
||||||
|
await fs.ensureDir(templatePath)
|
||||||
|
const response = await fetch(MONOREPO_TEMPLATE_URL)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to download template: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the tar file
|
||||||
|
const tarPath = path.resolve(templatePath, "template.tar.gz")
|
||||||
|
await fs.writeFile(tarPath, Buffer.from(await response.arrayBuffer()))
|
||||||
|
await execa("tar", [
|
||||||
|
"-xzf",
|
||||||
|
tarPath,
|
||||||
|
"-C",
|
||||||
|
templatePath,
|
||||||
|
"--strip-components=2",
|
||||||
|
"ui-shadcn-cli-monorepo/templates/monorepo-next",
|
||||||
|
])
|
||||||
|
const extractedPath = path.resolve(templatePath, "monorepo-next")
|
||||||
|
await fs.move(extractedPath, projectPath)
|
||||||
|
await fs.remove(templatePath)
|
||||||
|
|
||||||
|
// Run install.
|
||||||
|
await execa(options.packageManager, ["install"], {
|
||||||
|
cwd: projectPath,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Try git init.
|
||||||
|
const cwd = process.cwd()
|
||||||
|
await execa("git", ["--version"], { cwd: projectPath })
|
||||||
|
await execa("git", ["init"], { cwd: projectPath })
|
||||||
|
await execa("git", ["add", "-A"], { cwd: projectPath })
|
||||||
|
await execa("git", ["commit", "-m", "Initial commit"], {
|
||||||
|
cwd: projectPath,
|
||||||
|
})
|
||||||
|
await execa("cd", [cwd])
|
||||||
|
|
||||||
|
createSpinner?.succeed("Creating a new Next.js monorepo.")
|
||||||
|
} catch (error) {
|
||||||
|
createSpinner?.fail("Something went wrong creating a new Next.js monorepo.")
|
||||||
|
handleError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import path from "path"
|
|||||||
import { highlighter } from "@/src/utils/highlighter"
|
import { highlighter } from "@/src/utils/highlighter"
|
||||||
import { resolveImport } from "@/src/utils/resolve-import"
|
import { resolveImport } from "@/src/utils/resolve-import"
|
||||||
import { cosmiconfig } from "cosmiconfig"
|
import { cosmiconfig } from "cosmiconfig"
|
||||||
|
import fg from "fast-glob"
|
||||||
|
import fs from "fs-extra"
|
||||||
import { loadConfig } from "tsconfig-paths"
|
import { loadConfig } from "tsconfig-paths"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
@@ -59,6 +61,10 @@ export const configSchema = rawConfigSchema.extend({
|
|||||||
|
|
||||||
export type Config = z.infer<typeof configSchema>
|
export type Config = z.infer<typeof configSchema>
|
||||||
|
|
||||||
|
// TODO: type the key.
|
||||||
|
// Okay for now since I don't want a breaking change.
|
||||||
|
export const workspaceConfigSchema = z.record(configSchema)
|
||||||
|
|
||||||
export async function getConfig(cwd: string) {
|
export async function getConfig(cwd: string) {
|
||||||
const config = await getRawConfig(cwd)
|
const config = await getRawConfig(cwd)
|
||||||
|
|
||||||
@@ -137,3 +143,77 @@ export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: we can check for -workspace.yaml or "workspace" in package.json.
|
||||||
|
// Since cwd is not necessarily the root of the project.
|
||||||
|
// We'll instead check if ui aliases resolve to a different root.
|
||||||
|
export async function getWorkspaceConfig(config: Config) {
|
||||||
|
let resolvedAliases: any = {}
|
||||||
|
|
||||||
|
for (const key of Object.keys(config.aliases)) {
|
||||||
|
if (!isAliasKey(key, config)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPath = config.resolvedPaths[key]
|
||||||
|
const packageRoot = await findPackageRoot(
|
||||||
|
config.resolvedPaths.cwd,
|
||||||
|
resolvedPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!packageRoot) {
|
||||||
|
resolvedAliases[key] = config
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedAliases[key] = await getConfig(packageRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = workspaceConfigSchema.safeParse(resolvedAliases)
|
||||||
|
if (!result.success) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findPackageRoot(cwd: string, resolvedPath: string) {
|
||||||
|
const commonRoot = findCommonRoot(cwd, resolvedPath)
|
||||||
|
const relativePath = path.relative(commonRoot, resolvedPath)
|
||||||
|
|
||||||
|
const packageRoots = await fg.glob("**/package.json", {
|
||||||
|
cwd: commonRoot,
|
||||||
|
deep: 3,
|
||||||
|
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/public/**"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const matchingPackageRoot = packageRoots
|
||||||
|
.map((pkgPath) => path.dirname(pkgPath))
|
||||||
|
.find((pkgDir) => relativePath.startsWith(pkgDir))
|
||||||
|
|
||||||
|
return matchingPackageRoot ? path.join(commonRoot, matchingPackageRoot) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAliasKey(
|
||||||
|
key: string,
|
||||||
|
config: Config
|
||||||
|
): key is keyof Config["aliases"] {
|
||||||
|
return Object.keys(config.resolvedPaths)
|
||||||
|
.filter((key) => key !== "utils")
|
||||||
|
.includes(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findCommonRoot(cwd: string, resolvedPath: string) {
|
||||||
|
const parts1 = cwd.split(path.sep)
|
||||||
|
const parts2 = resolvedPath.split(path.sep)
|
||||||
|
const commonParts = []
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) {
|
||||||
|
if (parts1[i] !== parts2[i]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
commonParts.push(parts1[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return commonParts.join(path.sep)
|
||||||
|
}
|
||||||
|
|||||||
@@ -281,17 +281,8 @@ export async function registryResolveItemsTree(
|
|||||||
names.unshift("index")
|
names.unshift("index")
|
||||||
}
|
}
|
||||||
|
|
||||||
let registryDependencies: string[] = []
|
let registryItems = await resolveRegistryItems(names, config)
|
||||||
for (const name of names) {
|
let result = await fetchRegistry(registryItems)
|
||||||
const itemRegistryDependencies = await resolveRegistryDependencies(
|
|
||||||
name,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
registryDependencies.push(...itemRegistryDependencies)
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniqueRegistryDependencies = Array.from(new Set(registryDependencies))
|
|
||||||
let result = await fetchRegistry(uniqueRegistryDependencies)
|
|
||||||
const payload = z.array(registryItemSchema).parse(result)
|
const payload = z.array(registryItemSchema).parse(result)
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
@@ -461,3 +452,44 @@ function isUrl(path: string) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We're double-fetching here. Use a cache.
|
||||||
|
export async function resolveRegistryItems(names: string[], config: Config) {
|
||||||
|
let registryDependencies: string[] = []
|
||||||
|
for (const name of names) {
|
||||||
|
const itemRegistryDependencies = await resolveRegistryDependencies(
|
||||||
|
name,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
registryDependencies.push(...itemRegistryDependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(new Set(registryDependencies))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRegistryTypeAliasMap() {
|
||||||
|
return new Map<string, string>([
|
||||||
|
["registry:ui", "ui"],
|
||||||
|
["registry:lib", "lib"],
|
||||||
|
["registry:hook", "hooks"],
|
||||||
|
["registry:block", "components"],
|
||||||
|
["registry:component", "components"],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track a dependency and its parent.
|
||||||
|
export function getRegistryParentMap(
|
||||||
|
registryItems: z.infer<typeof registryItemSchema>[]
|
||||||
|
) {
|
||||||
|
const map = new Map<string, z.infer<typeof registryItemSchema>>()
|
||||||
|
registryItems.forEach((item) => {
|
||||||
|
if (!item.registryDependencies) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.registryDependencies.forEach((dependency) => {
|
||||||
|
map.set(dependency, item)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type TransformOpts = {
|
|||||||
config: Config
|
config: Config
|
||||||
baseColor?: z.infer<typeof registryBaseColorSchema>
|
baseColor?: z.infer<typeof registryBaseColorSchema>
|
||||||
transformJsx?: boolean
|
transformJsx?: boolean
|
||||||
|
isRemote?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Transformer<Output = SourceFile> = (
|
export type Transformer<Output = SourceFile> = (
|
||||||
|
|||||||
@@ -1,24 +1,39 @@
|
|||||||
import { Config } from "@/src/utils/get-config"
|
import { Config } from "@/src/utils/get-config"
|
||||||
import { Transformer } from "@/src/utils/transformers"
|
import { Transformer } from "@/src/utils/transformers"
|
||||||
|
|
||||||
export const transformImport: Transformer = async ({ sourceFile, config }) => {
|
const COMMON_CN_IMPORTS = {
|
||||||
|
"@/lib/utils": /^@\/lib\/utils/,
|
||||||
|
"@workspace/lib/utils": /^@workspace\/lib\/utils/,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformImport: Transformer = async ({
|
||||||
|
sourceFile,
|
||||||
|
config,
|
||||||
|
isRemote,
|
||||||
|
}) => {
|
||||||
const importDeclarations = sourceFile.getImportDeclarations()
|
const importDeclarations = sourceFile.getImportDeclarations()
|
||||||
|
|
||||||
for (const importDeclaration of importDeclarations) {
|
for (const importDeclaration of importDeclarations) {
|
||||||
const moduleSpecifier = updateImportAliases(
|
const moduleSpecifier = updateImportAliases(
|
||||||
importDeclaration.getModuleSpecifierValue(),
|
importDeclaration.getModuleSpecifierValue(),
|
||||||
config
|
config,
|
||||||
|
isRemote
|
||||||
)
|
)
|
||||||
|
|
||||||
importDeclaration.setModuleSpecifier(moduleSpecifier)
|
importDeclaration.setModuleSpecifier(moduleSpecifier)
|
||||||
|
|
||||||
// Replace `import { cn } from "@/lib/utils"`
|
// Replace `import { cn } from "@/lib/utils"`
|
||||||
if (moduleSpecifier == "@/lib/utils") {
|
if (COMMON_CN_IMPORTS[moduleSpecifier as keyof typeof COMMON_CN_IMPORTS]) {
|
||||||
const namedImports = importDeclaration.getNamedImports()
|
const namedImports = importDeclaration.getNamedImports()
|
||||||
const cnImport = namedImports.find((i) => i.getName() === "cn")
|
const cnImport = namedImports.find((i) => i.getName() === "cn")
|
||||||
if (cnImport) {
|
if (cnImport) {
|
||||||
importDeclaration.setModuleSpecifier(
|
importDeclaration.setModuleSpecifier(
|
||||||
moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils)
|
moduleSpecifier.replace(
|
||||||
|
COMMON_CN_IMPORTS[
|
||||||
|
moduleSpecifier as keyof typeof COMMON_CN_IMPORTS
|
||||||
|
],
|
||||||
|
config.aliases.utils
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,12 +42,21 @@ export const transformImport: Transformer = async ({ sourceFile, config }) => {
|
|||||||
return sourceFile
|
return sourceFile
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateImportAliases(moduleSpecifier: string, config: Config) {
|
function updateImportAliases(
|
||||||
|
moduleSpecifier: string,
|
||||||
|
config: Config,
|
||||||
|
isRemote: boolean = false
|
||||||
|
) {
|
||||||
// Not a local import.
|
// Not a local import.
|
||||||
if (!moduleSpecifier.startsWith("@/")) {
|
if (!moduleSpecifier.startsWith("@/") && !isRemote) {
|
||||||
return moduleSpecifier
|
return moduleSpecifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This treats the remote as coming from a faux registry.
|
||||||
|
if (isRemote && moduleSpecifier.startsWith("@/")) {
|
||||||
|
moduleSpecifier = moduleSpecifier.replace(/^@\//, `@/registry/new-york/`)
|
||||||
|
}
|
||||||
|
|
||||||
// Not a registry import.
|
// Not a registry import.
|
||||||
if (!moduleSpecifier.startsWith("@/registry/")) {
|
if (!moduleSpecifier.startsWith("@/registry/")) {
|
||||||
// We fix the alias and return.
|
// We fix the alias and return.
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export async function updateDependencies(
|
|||||||
cwd: config.resolvedPaths.cwd,
|
cwd: config.resolvedPaths.cwd,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
dependenciesSpinner?.succeed()
|
dependenciesSpinner?.succeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,15 +38,22 @@ export async function updateFiles(
|
|||||||
overwrite?: boolean
|
overwrite?: boolean
|
||||||
force?: boolean
|
force?: boolean
|
||||||
silent?: boolean
|
silent?: boolean
|
||||||
|
rootSpinner?: ReturnType<typeof spinner>
|
||||||
|
isRemote?: boolean
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (!files?.length) {
|
if (!files?.length) {
|
||||||
return
|
return {
|
||||||
|
filesCreated: [],
|
||||||
|
filesUpdated: [],
|
||||||
|
filesSkipped: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
options = {
|
options = {
|
||||||
overwrite: false,
|
overwrite: false,
|
||||||
force: false,
|
force: false,
|
||||||
silent: false,
|
silent: false,
|
||||||
|
isRemote: false,
|
||||||
...options,
|
...options,
|
||||||
}
|
}
|
||||||
const filesCreatedSpinner = spinner(`Updating files.`, {
|
const filesCreatedSpinner = spinner(`Updating files.`, {
|
||||||
@@ -83,8 +90,12 @@ export async function updateFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existingFile = existsSync(filePath)
|
const existingFile = existsSync(filePath)
|
||||||
|
|
||||||
if (existingFile && !options.overwrite) {
|
if (existingFile && !options.overwrite) {
|
||||||
filesCreatedSpinner.stop()
|
filesCreatedSpinner.stop()
|
||||||
|
if (options.rootSpinner) {
|
||||||
|
options.rootSpinner.stop()
|
||||||
|
}
|
||||||
const { overwrite } = await prompts({
|
const { overwrite } = await prompts({
|
||||||
type: "confirm",
|
type: "confirm",
|
||||||
name: "overwrite",
|
name: "overwrite",
|
||||||
@@ -96,9 +107,15 @@ export async function updateFiles(
|
|||||||
|
|
||||||
if (!overwrite) {
|
if (!overwrite) {
|
||||||
filesSkipped.push(path.relative(config.resolvedPaths.cwd, filePath))
|
filesSkipped.push(path.relative(config.resolvedPaths.cwd, filePath))
|
||||||
|
if (options.rootSpinner) {
|
||||||
|
options.rootSpinner.start()
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filesCreatedSpinner?.start()
|
filesCreatedSpinner?.start()
|
||||||
|
if (options.rootSpinner) {
|
||||||
|
options.rootSpinner.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the target directory if it doesn't exist.
|
// Create the target directory if it doesn't exist.
|
||||||
@@ -114,6 +131,7 @@ export async function updateFiles(
|
|||||||
config,
|
config,
|
||||||
baseColor,
|
baseColor,
|
||||||
transformJsx: !config.tsx,
|
transformJsx: !config.tsx,
|
||||||
|
isRemote: options.isRemote,
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
transformImport,
|
transformImport,
|
||||||
@@ -170,7 +188,7 @@ export async function updateFiles(
|
|||||||
spinner(
|
spinner(
|
||||||
`Skipped ${filesSkipped.length} ${
|
`Skipped ${filesSkipped.length} ${
|
||||||
filesUpdated.length === 1 ? "file" : "files"
|
filesUpdated.length === 1 ? "file" : "files"
|
||||||
}:`,
|
}: (use --overwrite to overwrite)`,
|
||||||
{
|
{
|
||||||
silent: options.silent,
|
silent: options.silent,
|
||||||
}
|
}
|
||||||
@@ -185,4 +203,10 @@ export async function updateFiles(
|
|||||||
if (!options.silent) {
|
if (!options.silent) {
|
||||||
logger.break()
|
logger.break()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filesCreated,
|
||||||
|
filesUpdated,
|
||||||
|
filesSkipped,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
packages:
|
packages:
|
||||||
- "apps/*"
|
- "apps/*"
|
||||||
- "packages/*"
|
- "packages/*"
|
||||||
- "templates/*"
|
|
||||||
- "!**/test/**"
|
- "!**/test/**"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ module.exports = {
|
|||||||
"^(next/(.*)$)|^(next$)",
|
"^(next/(.*)$)|^(next$)",
|
||||||
"<THIRD_PARTY_MODULES>",
|
"<THIRD_PARTY_MODULES>",
|
||||||
"",
|
"",
|
||||||
|
"^@workspace/(.*)$",
|
||||||
|
"",
|
||||||
"^types$",
|
"^types$",
|
||||||
"^@/types/(.*)$",
|
"^@/types/(.*)$",
|
||||||
"^@/config/(.*)$",
|
"^@/config/(.*)$",
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
> [!NOTE]
|
|
||||||
> The `next-template` has been deprecated. Use `npx shadcn@latest init` to create a new project.
|
|
||||||
10
templates/monorepo-next/.eslintrc.js
Normal file
10
templates/monorepo-next/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// This configuration only applies to the package manager root.
|
||||||
|
/** @type {import("eslint").Linter.Config} */
|
||||||
|
module.exports = {
|
||||||
|
ignorePatterns: ["apps/**", "packages/**"],
|
||||||
|
extends: ["@workspace/eslint-config/library.js"],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
36
templates/monorepo-next/.gitignore
vendored
Normal file
36
templates/monorepo-next/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Turbo
|
||||||
|
.turbo
|
||||||
|
|
||||||
|
# Vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# Build Outputs
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
0
templates/monorepo-next/.npmrc
Normal file
0
templates/monorepo-next/.npmrc
Normal file
31
templates/monorepo-next/README.md
Normal file
31
templates/monorepo-next/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# shadcn/ui monorepo template
|
||||||
|
|
||||||
|
This template is for creating a monorepo with shadcn/ui.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dlx shadcn@latest init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding components
|
||||||
|
|
||||||
|
To add components to your app, run the following command at the root of your `web` app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dlx shadcn@latest add button -c apps/web
|
||||||
|
```
|
||||||
|
|
||||||
|
This will place the ui components in the `packages/ui/src/components` directory.
|
||||||
|
|
||||||
|
## Tailwind
|
||||||
|
|
||||||
|
Your `tailwind.config.ts` and `globals.css` are already set up to use the components from the `ui` package.
|
||||||
|
|
||||||
|
## Using components
|
||||||
|
|
||||||
|
To use the components in your app, import them from the `ui` package.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Button } from "@workspace/ui/components/ui/button"
|
||||||
|
```
|
||||||
BIN
templates/monorepo-next/apps/web/app/favicon.ico
Normal file
BIN
templates/monorepo-next/apps/web/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
30
templates/monorepo-next/apps/web/app/layout.tsx
Normal file
30
templates/monorepo-next/apps/web/app/layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Geist, Geist_Mono } from "next/font/google"
|
||||||
|
|
||||||
|
import "@workspace/ui/globals.css"
|
||||||
|
import { Providers } from "@/components/providers"
|
||||||
|
|
||||||
|
const fontSans = Geist({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-sans",
|
||||||
|
})
|
||||||
|
|
||||||
|
const fontMono = Geist_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body
|
||||||
|
className={`${fontSans.variable} ${fontMono.variable} font-sans antialiased `}
|
||||||
|
>
|
||||||
|
<Providers>{children}</Providers>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
templates/monorepo-next/apps/web/app/page.tsx
Normal file
12
templates/monorepo-next/apps/web/app/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Button } from "@workspace/ui/components/button"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-svh">
|
||||||
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
|
<h1 className="text-2xl font-bold">Hello World</h1>
|
||||||
|
<Button size="sm">Button</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
templates/monorepo-next/apps/web/components.json
Normal file
20
templates/monorepo-next/apps/web/components.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "../../packages/ui/tailwind.config.ts",
|
||||||
|
"css": "../../packages/ui/src/styles/globals.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"hooks": "@/hooks",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"utils": "@workspace/ui/lib/utils",
|
||||||
|
"ui": "@workspace/ui/components"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
templates/monorepo-next/apps/web/components/providers.tsx
Normal file
18
templates/monorepo-next/apps/web/components/providers.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
|
|
||||||
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<NextThemesProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="system"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
enableColorScheme
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</NextThemesProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
4
templates/monorepo-next/apps/web/eslint.config.js
Normal file
4
templates/monorepo-next/apps/web/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { nextJsConfig } from "@workspace/eslint-config/next-js"
|
||||||
|
|
||||||
|
/** @type {import("eslint").Linter.Config} */
|
||||||
|
export default nextJsConfig
|
||||||
0
templates/monorepo-next/apps/web/hooks/.gitkeep
Normal file
0
templates/monorepo-next/apps/web/hooks/.gitkeep
Normal file
0
templates/monorepo-next/apps/web/lib/.gitkeep
Normal file
0
templates/monorepo-next/apps/web/lib/.gitkeep
Normal file
5
templates/monorepo-next/apps/web/next-env.d.ts
vendored
Normal file
5
templates/monorepo-next/apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
6
templates/monorepo-next/apps/web/next.config.mjs
Normal file
6
templates/monorepo-next/apps/web/next.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
transpilePackages: ["@workspace/ui"],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default nextConfig
|
||||||
30
templates/monorepo-next/apps/web/package.json
Normal file
30
templates/monorepo-next/apps/web/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@workspace/ui": "workspace:*",
|
||||||
|
"lucide-react": "0.456.0",
|
||||||
|
"next-themes": "^0.4.3",
|
||||||
|
"next": "^15.1.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "18.3.0",
|
||||||
|
"@types/react-dom": "18.3.1",
|
||||||
|
"@workspace/eslint-config": "workspace:^",
|
||||||
|
"@workspace/typescript-config": "workspace:*",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
templates/monorepo-next/apps/web/postcss.config.mjs
Normal file
1
templates/monorepo-next/apps/web/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "@workspace/ui/postcss.config";
|
||||||
1
templates/monorepo-next/apps/web/tailwind.config.ts
Normal file
1
templates/monorepo-next/apps/web/tailwind.config.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "@workspace/ui/tailwind.config";
|
||||||
23
templates/monorepo-next/apps/web/tsconfig.json
Normal file
23
templates/monorepo-next/apps/web/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"extends": "@workspace/typescript-config/nextjs.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"],
|
||||||
|
"@workspace/ui/*": ["../../packages/ui/src/*"]
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"next.config.mjs",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
22
templates/monorepo-next/package.json
Normal file
22
templates/monorepo-next/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "shadcn-ui-monorepo",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo build",
|
||||||
|
"dev": "turbo dev",
|
||||||
|
"lint": "turbo lint",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@workspace/eslint-config": "workspace:*",
|
||||||
|
"@workspace/typescript-config": "workspace:*",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"turbo": "^2.3.0",
|
||||||
|
"typescript": "5.5.4"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@9.12.3",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
templates/monorepo-next/packages/eslint-config/README.md
Normal file
3
templates/monorepo-next/packages/eslint-config/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# `@workspace/eslint-config`
|
||||||
|
|
||||||
|
Shared eslint configuration for the workspace.
|
||||||
32
templates/monorepo-next/packages/eslint-config/base.js
Normal file
32
templates/monorepo-next/packages/eslint-config/base.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import js from "@eslint/js"
|
||||||
|
import eslintConfigPrettier from "eslint-config-prettier"
|
||||||
|
import onlyWarn from "eslint-plugin-only-warn"
|
||||||
|
import turboPlugin from "eslint-plugin-turbo"
|
||||||
|
import tseslint from "typescript-eslint"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shared ESLint configuration for the repository.
|
||||||
|
*
|
||||||
|
* @type {import("eslint").Linter.Config}
|
||||||
|
* */
|
||||||
|
export const config = [
|
||||||
|
js.configs.recommended,
|
||||||
|
eslintConfigPrettier,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
turbo: turboPlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"turbo/no-undeclared-env-vars": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
onlyWarn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ["dist/**"],
|
||||||
|
},
|
||||||
|
]
|
||||||
51
templates/monorepo-next/packages/eslint-config/next.js
Normal file
51
templates/monorepo-next/packages/eslint-config/next.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import js from "@eslint/js"
|
||||||
|
import pluginNext from "@next/eslint-plugin-next"
|
||||||
|
import eslintConfigPrettier from "eslint-config-prettier"
|
||||||
|
import pluginReact from "eslint-plugin-react"
|
||||||
|
import pluginReactHooks from "eslint-plugin-react-hooks"
|
||||||
|
import globals from "globals"
|
||||||
|
import tseslint from "typescript-eslint"
|
||||||
|
|
||||||
|
import { config as baseConfig } from "./base.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom ESLint configuration for libraries that use Next.js.
|
||||||
|
*
|
||||||
|
* @type {import("eslint").Linter.Config}
|
||||||
|
* */
|
||||||
|
export const nextJsConfig = [
|
||||||
|
...baseConfig,
|
||||||
|
js.configs.recommended,
|
||||||
|
eslintConfigPrettier,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
...pluginReact.configs.flat.recommended,
|
||||||
|
languageOptions: {
|
||||||
|
...pluginReact.configs.flat.recommended.languageOptions,
|
||||||
|
globals: {
|
||||||
|
...globals.serviceworker,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
"@next/next": pluginNext,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...pluginNext.configs.recommended.rules,
|
||||||
|
...pluginNext.configs["core-web-vitals"].rules,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
"react-hooks": pluginReactHooks,
|
||||||
|
},
|
||||||
|
settings: { react: { version: "detect" } },
|
||||||
|
rules: {
|
||||||
|
...pluginReactHooks.configs.recommended.rules,
|
||||||
|
// React scope no longer necessary with new JSX transform.
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
25
templates/monorepo-next/packages/eslint-config/package.json
Normal file
25
templates/monorepo-next/packages/eslint-config/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "@workspace/eslint-config",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"exports": {
|
||||||
|
"./base": "./base.js",
|
||||||
|
"./next-js": "./next.js",
|
||||||
|
"./react-internal": "./react-internal.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@next/eslint-plugin-next": "^15.1.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
||||||
|
"@typescript-eslint/parser": "^8.15.0",
|
||||||
|
"eslint": "^9.15.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-only-warn": "^1.1.0",
|
||||||
|
"eslint-plugin-react": "^7.37.2",
|
||||||
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
|
"eslint-plugin-turbo": "^2.3.0",
|
||||||
|
"globals": "^15.12.0",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"typescript-eslint": "^8.15.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
templates/monorepo-next/packages/eslint-config/react-internal.js
vendored
Normal file
41
templates/monorepo-next/packages/eslint-config/react-internal.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import js from "@eslint/js"
|
||||||
|
import eslintConfigPrettier from "eslint-config-prettier"
|
||||||
|
import pluginReact from "eslint-plugin-react"
|
||||||
|
import pluginReactHooks from "eslint-plugin-react-hooks"
|
||||||
|
import globals from "globals"
|
||||||
|
import tseslint from "typescript-eslint"
|
||||||
|
|
||||||
|
import { config as baseConfig } from "./base.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom ESLint configuration for libraries that use React.
|
||||||
|
*
|
||||||
|
* @type {import("eslint").Linter.Config} */
|
||||||
|
export const config = [
|
||||||
|
...baseConfig,
|
||||||
|
js.configs.recommended,
|
||||||
|
eslintConfigPrettier,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
pluginReact.configs.flat.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
...pluginReact.configs.flat.recommended.languageOptions,
|
||||||
|
globals: {
|
||||||
|
...globals.serviceworker,
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
"react-hooks": pluginReactHooks,
|
||||||
|
},
|
||||||
|
settings: { react: { version: "detect" } },
|
||||||
|
rules: {
|
||||||
|
...pluginReactHooks.configs.recommended.rules,
|
||||||
|
// React scope no longer necessary with new JSX transform.
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# `@workspace/typescript-config`
|
||||||
|
|
||||||
|
Shared typescript configuration for the workspace.
|
||||||
20
templates/monorepo-next/packages/typescript-config/base.json
Normal file
20
templates/monorepo-next/packages/typescript-config/base.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "Default",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"incremental": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "ES2022"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "Next.js",
|
||||||
|
"extends": "./base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"plugins": [{ "name": "next" }],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "@workspace/typescript-config",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"license": "PROPRIETARY",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "React Library",
|
||||||
|
"extends": "./base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
}
|
||||||
|
}
|
||||||
20
templates/monorepo-next/packages/ui/components.json
Normal file
20
templates/monorepo-next/packages/ui/components.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/styles/globals.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@workspace/ui/components",
|
||||||
|
"utils": "@workspace/ui/lib/utils",
|
||||||
|
"hooks": "@workspace/ui/hooks",
|
||||||
|
"lib": "@workspace/ui/lib",
|
||||||
|
"ui": "@workspace/ui/components"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
templates/monorepo-next/packages/ui/eslint.config.js
Normal file
4
templates/monorepo-next/packages/ui/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { config } from "@workspace/eslint-config/react-internal"
|
||||||
|
|
||||||
|
/** @type {import("eslint").Linter.Config} */
|
||||||
|
export default config
|
||||||
42
templates/monorepo-next/packages/ui/package.json
Normal file
42
templates/monorepo-next/packages/ui/package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "@workspace/ui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint . --max-warnings 0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "0.456.0",
|
||||||
|
"next-themes": "^0.4.3",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"tailwind-merge": "^2.5.4",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@turbo/gen": "^2.2.3",
|
||||||
|
"@types/node": "^22.9.0",
|
||||||
|
"@types/react": "18.3.0",
|
||||||
|
"@types/react-dom": "18.3.1",
|
||||||
|
"@workspace/eslint-config": "workspace:*",
|
||||||
|
"@workspace/typescript-config": "workspace:*",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"tailwindcss": "^3.4.14",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
"./globals.css": "./src/styles/globals.css",
|
||||||
|
"./postcss.config": "./postcss.config.mjs",
|
||||||
|
"./tailwind.config": "./tailwind.config.ts",
|
||||||
|
"./lib/*": "./src/lib/*.ts",
|
||||||
|
"./components/*": "./src/components/*.tsx",
|
||||||
|
"./hooks/*": "./src/hooks/*.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
templates/monorepo-next/packages/ui/postcss.config.mjs
Normal file
9
templates/monorepo-next/packages/ui/postcss.config.mjs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "h-10 w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
6
templates/monorepo-next/packages/ui/src/lib/utils.ts
Normal file
6
templates/monorepo-next/packages/ui/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
68
templates/monorepo-next/packages/ui/src/styles/globals.css
Normal file
68
templates/monorepo-next/packages/ui/src/styles/globals.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 10% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 10% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 240 10% 3.9%;
|
||||||
|
--primary: 240 5.9% 10%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
--muted: 240 4.8% 95.9%;
|
||||||
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
|
--accent: 240 4.8% 95.9%;
|
||||||
|
--accent-foreground: 240 5.9% 10%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
--input: 240 5.9% 90%;
|
||||||
|
--ring: 240 10% 3.9%;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--background: 240 10% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 240 10% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--popover: 240 10% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 240 3.7% 15.9%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
--accent: 240 3.7% 15.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
--ring: 240 4.9% 83.9%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
templates/monorepo-next/packages/ui/tailwind.config.ts
Normal file
63
templates/monorepo-next/packages/ui/tailwind.config.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type { Config } from "tailwindcss"
|
||||||
|
import tailwindcssAnimate from "tailwindcss-animate"
|
||||||
|
import { fontFamily } from "tailwindcss/defaultTheme"
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: [
|
||||||
|
"app/**/*.{ts,tsx}",
|
||||||
|
"components/**/*.{ts,tsx}",
|
||||||
|
"../../packages/ui/src/components/**/*.{ts,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||||
|
mono: ["var(--font-mono)", ...fontFamily.mono],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [tailwindcssAnimate],
|
||||||
|
} satisfies Config
|
||||||
|
|
||||||
|
export default config
|
||||||
12
templates/monorepo-next/packages/ui/tsconfig.json
Normal file
12
templates/monorepo-next/packages/ui/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "@workspace/typescript-config/react-library.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
//"outDir": "dist"
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@workspace/ui/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
8
templates/monorepo-next/packages/ui/tsconfig.lint.json
Normal file
8
templates/monorepo-next/packages/ui/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@workspace/typescript-config/react-library.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src", "turbo"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
5153
templates/monorepo-next/pnpm-lock.yaml
generated
Normal file
5153
templates/monorepo-next/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
templates/monorepo-next/pnpm-workspace.yaml
Normal file
3
templates/monorepo-next/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
packages:
|
||||||
|
- "apps/*"
|
||||||
|
- "packages/*"
|
||||||
4
templates/monorepo-next/tsconfig.json
Normal file
4
templates/monorepo-next/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "@workspace/typescript-config/base.json"
|
||||||
|
}
|
||||||
|
|
||||||
21
templates/monorepo-next/turbo.json
Normal file
21
templates/monorepo-next/turbo.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"ui": "tui",
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
|
"outputs": [".next/**", "!.next/cache/**"]
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"dependsOn": ["^lint"]
|
||||||
|
},
|
||||||
|
"check-types": {
|
||||||
|
"dependsOn": ["^check-types"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user