feat: add new monorepo templates

This commit is contained in:
shadcn
2026-02-26 13:17:43 +04:00
parent d24d2e6fd0
commit 5ef76dece1
100 changed files with 18415 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -21,6 +21,18 @@ import { STYLES } from "@/registry/styles"
// This is used by the v4 site.
const WHITELISTED_STYLES = ["new-york-v4"]
// Template directories to archive during build.
const TEMPLATE_NAMES = [
"next-app",
"vite-app",
"react-router-app",
"start-app",
"next-monorepo",
"vite-monorepo",
"react-router-monorepo",
"start-monorepo",
]
// Collect paths for batch prettier formatting at the end.
const prettierPaths: string[] = []
@@ -77,6 +89,9 @@ try {
console.log("\n⚙ Building public/r/config.json...")
await buildConfig()
console.log("\n📦 Building public/templates...")
await buildTemplates()
// Copy UI to examples before cleanup.
console.log("\n📋 Copying UI to examples...")
await copyUIToExamples()
@@ -714,3 +729,53 @@ async function batchPrettier(paths: string[]) {
proc.on("error", reject)
})
}
async function buildTemplates() {
const templatesDir = path.resolve(process.cwd(), "../../templates")
const outputDir = path.join(process.cwd(), "public/templates")
await fs.mkdir(outputDir, { recursive: true })
await Promise.all(
TEMPLATE_NAMES.map(async (name) => {
const templatePath = path.join(templatesDir, name)
// Verify the template directory exists.
try {
await fs.access(templatePath)
} catch {
console.log(` ⚠️ templates/${name} not found, skipping`)
return
}
const outputPath = path.join(outputDir, `${name}.tar.gz`)
await new Promise<void>((resolve, reject) => {
const proc = spawn(
"tar",
[
"-czf",
outputPath,
"--exclude",
"node_modules",
"-C",
templatesDir,
name,
],
{ cwd: process.cwd(), stdio: "pipe" }
)
let stderr = ""
proc.stderr?.on("data", (data) => (stderr += data))
proc.on("close", (code) => {
if (code !== 0) {
reject(new Error(`tar exited with code ${code}: ${stderr}`))
} else {
resolve()
}
})
proc.on("error", reject)
})
console.log(`${name}.tar.gz`)
})
)
}

View File

@@ -0,0 +1,34 @@
# 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
build
dist
# React Router
.react-router/
# Debug
npm-debug.log*
# Misc
.DS_Store
*.pem

View File

View File

@@ -0,0 +1,8 @@
dist/
build/
node_modules/
.turbo/
.react-router/
coverage/
pnpm-lock.yaml
.pnpm-store/

View File

@@ -0,0 +1,11 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "packages/ui/src/styles/globals.css",
"tailwindFunctions": ["cn", "cva"]
}

View File

@@ -0,0 +1,21 @@
# shadcn/ui monorepo template
This is a React Router monorepo template with shadcn/ui.
## 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.
## Using components
To use the components in your app, import them from the `ui` package.
```tsx
import { Button } from "@workspace/ui/components/button";
```

View File

@@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md

View File

@@ -0,0 +1,22 @@
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci
FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev
FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build
FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]

View File

@@ -0,0 +1,62 @@
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
isRouteErrorResponse,
} from "react-router"
import type { Route } from "./+types/root"
import "@workspace/ui/globals.css"
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
export default function App() {
return <Outlet />
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!"
let details = "An unexpected error occurred."
let stack: string | undefined
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error"
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message
stack = error.stack
}
return (
<main className="container mx-auto p-4 pt-16">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full overflow-x-auto p-4">
<code>{stack}</code>
</pre>
)}
</main>
)
}

View File

@@ -0,0 +1,3 @@
import { type RouteConfig, index } from "@react-router/dev/routes"
export default [index("routes/home.tsx")] satisfies RouteConfig

View File

@@ -0,0 +1,18 @@
export default function Home() {
return (
<div className="flex min-h-svh p-6">
<div className="max-w-md text-sm leading-loose">
<h1 className="font-medium">Project ready!</h1>
<p>You may now add components and start building.</p>
<a
href="https://ui.shadcn.com/docs/new"
target="_blank"
rel="noopener noreferrer"
className="mt-2 inline-flex text-primary underline underline-offset-4"
>
Read the docs
</a>
</div>
</div>
)
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "remixicon",
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@workspace/ui/lib/utils",
"ui": "@workspace/ui/components"
},
"rtl": false,
"menuColor": "inverted",
"menuAccent": "subtle"
}

View File

@@ -0,0 +1,34 @@
{
"name": "web",
"version": "0.0.1",
"type": "module",
"private": true,
"scripts": {
"dev": "react-router dev",
"build": "react-router build",
"start": "react-router-serve ./build/server/index.js",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"typecheck": "react-router typegen && tsc"
},
"dependencies": {
"@react-router/node": "7.12.0",
"@react-router/serve": "7.12.0",
"@remixicon/react": "^4.9.0",
"@workspace/ui": "workspace:*",
"isbot": "^5.1.31",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "7.12.0"
},
"devDependencies": {
"@react-router/dev": "7.12.0",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite": "^7.2.4",
"vite-tsconfig-paths": "^5.1.4"
}
}

View File

@@ -0,0 +1,7 @@
import type { Config } from "@react-router/dev/config"
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true,
} satisfies Config

View File

@@ -0,0 +1,28 @@
{
"include": [
"**/*",
"**/.server/**/*",
"**/.client/**/*",
".react-router/types/**/*"
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"@/*": ["./app/*"],
"@workspace/ui/*": ["../../packages/ui/src/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}

View File

@@ -0,0 +1,8 @@
import { reactRouter } from "@react-router/dev/vite"
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "vite"
import tsconfigPaths from "vite-tsconfig-paths"
export default defineConfig({
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
})

View File

@@ -0,0 +1,21 @@
{
"name": "react-router-monorepo",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"format": "turbo format",
"typecheck": "turbo typecheck"
},
"devDependencies": {
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"turbo": "^2.8.8",
"typescript": "5.9.3"
},
"packageManager": "pnpm@9.0.6",
"engines": {
"node": ">=20"
}
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "remixicon",
"aliases": {
"components": "@workspace/ui/components",
"utils": "@workspace/ui/lib/utils",
"hooks": "@workspace/ui/hooks",
"lib": "@workspace/ui/lib",
"ui": "@workspace/ui/components"
},
"rtl": false,
"menuColor": "inverted",
"menuAccent": "subtle"
}

View File

@@ -0,0 +1,38 @@
{
"name": "@workspace/ui",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"format": "prettier --write \"**/*.{ts,tsx}\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@fontsource-variable/outfit": "^5.2.8",
"@remixicon/react": "^4.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"shadcn": "^3.8.5",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"@turbo/gen": "^2.8.1",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3"
},
"exports": {
"./globals.css": "./src/styles/globals.css",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
"./hooks/*": "./src/hooks/*.ts"
}
}

View 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))
}

View File

@@ -0,0 +1,129 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "@fontsource-variable/outfit";
@custom-variant dark (&:is(.dark *));
@source "../../../apps/**/*.{ts,tsx}";
@source "../../../components/**/*.{ts,tsx}";
@source "../**/*.{ts,tsx}";
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.648 0.2 131.684);
--primary-foreground: oklch(0.986 0.031 120.757);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.871 0.15 154.449);
--chart-2: oklch(0.723 0.219 149.579);
--chart-3: oklch(0.627 0.194 149.214);
--chart-4: oklch(0.527 0.154 150.069);
--chart-5: oklch(0.448 0.119 151.328);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.648 0.2 131.684);
--sidebar-primary-foreground: oklch(0.986 0.031 120.757);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.648 0.2 131.684);
--primary-foreground: oklch(0.986 0.031 120.757);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.871 0.15 154.449);
--chart-2: oklch(0.723 0.219 149.579);
--chart-3: oklch(0.627 0.194 149.214);
--chart-4: oklch(0.527 0.154 150.069);
--chart-5: oklch(0.448 0.119 151.328);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.768 0.233 130.85);
--sidebar-primary-foreground: oklch(0.986 0.031 120.757);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--font-sans: 'Outfit', sans-serif;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@workspace/ui/*": ["./src/*"]
}
},
"include": ["."],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"outDir": "dist"
},
"include": ["src", "turbo"],
"exclude": ["node_modules", "dist"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
packages:
- "apps/*"
- "packages/*"

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"strict": true
}
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["build/**"]
},
"format": {
"dependsOn": ["^format"]
},
"typecheck": {
"dependsOn": ["^typecheck"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}

34
templates/start-monorepo/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# 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
dist
.output
.nitro
.tanstack
.vinxi
# Debug
npm-debug.log*
# Misc
.DS_Store
*.pem

View File

View File

@@ -0,0 +1,10 @@
dist/
node_modules/
.turbo/
.output/
.nitro/
.tanstack/
.vinxi/
coverage/
pnpm-lock.yaml
.pnpm-store/

View File

@@ -0,0 +1,11 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "packages/ui/src/styles/globals.css",
"tailwindFunctions": ["cn", "cva"]
}

View File

@@ -0,0 +1,21 @@
# shadcn/ui monorepo template
This is a TanStack Start monorepo template with shadcn/ui.
## 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.
## Using components
To use the components in your app, import them from the `ui` package.
```tsx
import { Button } from "@workspace/ui/components/button";
```

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@workspace/ui/lib/utils",
"ui": "@workspace/ui/components"
}
}

View File

@@ -0,0 +1,5 @@
// @ts-check
import { tanstackConfig } from "@tanstack/eslint-config"
export default [...tanstackConfig]

View File

@@ -0,0 +1,36 @@
{
"name": "web",
"version": "0.0.1",
"type": "module",
"private": true,
"scripts": {
"dev": "vite dev --port 3000",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-router": "^1.132.0",
"@tanstack/react-start": "^1.132.0",
"@tanstack/router-plugin": "^1.132.0",
"@workspace/ui": "workspace:*",
"nitro": "latest",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwindcss": "^4.1.18",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@tanstack/eslint-config": "^0.3.0",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.2",
"typescript": "^5.9.3",
"vite": "^7.2.4"
}
}

View File

@@ -0,0 +1,68 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from "./routes/__root"
import { Route as IndexRouteImport } from "./routes/index"
const IndexRoute = IndexRouteImport.update({
id: "/",
path: "/",
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath {
"/": typeof IndexRoute
}
export interface FileRoutesByTo {
"/": typeof IndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
"/": typeof IndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: "/"
fileRoutesByTo: FileRoutesByTo
to: "/"
id: "__root__" | "/"
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
}
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
"/": {
id: "/"
path: "/"
fullPath: "/"
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
import type { getRouter } from "./router.tsx"
import type { createStart } from "@tanstack/react-start"
declare module "@tanstack/react-start" {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}

View File

@@ -0,0 +1,20 @@
import { createRouter as createTanStackRouter } from "@tanstack/react-router"
import { routeTree } from "./routeTree.gen"
export function getRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
})
return router
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof getRouter>
}
}

View File

@@ -0,0 +1,41 @@
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"
import appCss from "@workspace/ui/globals.css?url"
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
{
title: "TanStack Start Starter",
},
],
links: [
{
rel: "stylesheet",
href: appCss,
},
],
}),
shellComponent: RootDocument,
})
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}

View File

@@ -0,0 +1,22 @@
import { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/")({ component: App })
function App() {
return (
<div className="flex min-h-svh p-6">
<div className="max-w-md text-sm leading-loose">
<h1 className="font-medium">Project ready!</h1>
<p>You may now add components and start building.</p>
<a
href="https://ui.shadcn.com/docs/new"
target="_blank"
rel="noopener noreferrer"
className="mt-2 inline-flex text-primary underline underline-offset-4"
>
Read the docs
</a>
</div>
</div>
)
}

View File

@@ -0,0 +1,30 @@
{
"include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"],
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@workspace/ui/*": ["../../packages/ui/src/*"]
}
}
}

View File

@@ -0,0 +1,20 @@
import { defineConfig } from "vite"
import { tanstackStart } from "@tanstack/react-start/plugin/vite"
import viteReact from "@vitejs/plugin-react"
import viteTsConfigPaths from "vite-tsconfig-paths"
import tailwindcss from "@tailwindcss/vite"
import { nitro } from "nitro/vite"
const config = defineConfig({
plugins: [
nitro(),
viteTsConfigPaths({
projects: ["./tsconfig.json"],
}),
tailwindcss(),
tanstackStart(),
viteReact(),
],
})
export default config

View File

@@ -0,0 +1,22 @@
{
"name": "start-monorepo",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"format": "turbo format",
"typecheck": "turbo typecheck"
},
"devDependencies": {
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"turbo": "^2.8.8",
"typescript": "5.9.3"
},
"packageManager": "pnpm@9.0.6",
"engines": {
"node": ">=20"
}
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"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"
}
}

View File

@@ -0,0 +1,5 @@
// @ts-check
import { tanstackConfig } from "@tanstack/eslint-config"
export default [...tanstackConfig]

View File

@@ -0,0 +1,33 @@
{
"name": "@workspace/ui",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"lint": "eslint",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"react": "^19.2.4",
"react-dom": "^19.2.4",
"zod": "^3.25.76"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"@turbo/gen": "^2.8.1",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@tanstack/eslint-config": "^0.3.0",
"eslint": "^9.39.2",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3"
},
"exports": {
"./globals.css": "./src/styles/globals.css",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
"./hooks/*": "./src/hooks/*.ts"
}
}

View File

@@ -0,0 +1,4 @@
@import "tailwindcss";
@source "../../../apps/**/*.{ts,tsx}";
@source "../../../components/**/*.{ts,tsx}";
@source "../**/*.{ts,tsx}";

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@workspace/ui/*": ["./src/*"]
}
},
"include": ["."],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"outDir": "dist"
},
"include": ["src", "turbo"],
"exclude": ["node_modules", "dist"]
}

5609
templates/start-monorepo/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
packages:
- "apps/*"
- "packages/*"

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"strict": true
}
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".output/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"format": {
"dependsOn": ["^format"]
},
"typecheck": {
"dependsOn": ["^typecheck"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}

30
templates/vite-monorepo/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# 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
dist
# Debug
npm-debug.log*
# Misc
.DS_Store
*.pem

View File

View File

@@ -0,0 +1,6 @@
dist/
node_modules/
.turbo/
coverage/
pnpm-lock.yaml
.pnpm-store/

View File

@@ -0,0 +1,11 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "packages/ui/src/styles/globals.css",
"tailwindFunctions": ["cn", "cva"]
}

View File

@@ -0,0 +1,21 @@
# shadcn/ui monorepo template
This is a Vite monorepo template with shadcn/ui.
## 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.
## Using components
To use the components in your app, import them from the `ui` package.
```tsx
import { Button } from "@workspace/ui/components/button";
```

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@workspace/ui/lib/utils",
"ui": "@workspace/ui/components"
}
}

View File

@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vite-monorepo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,34 @@
{
"name": "web",
"version": "0.0.1",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"typecheck": "tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"@workspace/ui": "workspace:*",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"@eslint/js": "^9.39.2",
"eslint": "^9.39.2",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^17.2.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0",
"vite": "^7.2.4"
}
}

View File

@@ -0,0 +1,18 @@
export function App() {
return (
<div className="flex min-h-svh p-6">
<div className="max-w-md text-sm leading-loose">
<h1 className="font-medium">Project ready!</h1>
<p>You may now add components and start building.</p>
<a
href="https://ui.shadcn.com/docs/new"
target="_blank"
rel="noopener noreferrer"
className="mt-2 inline-flex text-primary underline underline-offset-4"
>
Read the docs
</a>
</div>
</div>
)
}

View File

@@ -0,0 +1,230 @@
/* eslint-disable react-refresh/only-export-components */
import * as React from "react"
type Theme = "dark" | "light" | "system"
type ResolvedTheme = "dark" | "light"
type ThemeProviderProps = {
children: React.ReactNode
defaultTheme?: Theme
storageKey?: string
disableTransitionOnChange?: boolean
}
type ThemeProviderState = {
theme: Theme
setTheme: (theme: Theme) => void
}
const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"
const THEME_VALUES: Theme[] = ["dark", "light", "system"]
const ThemeProviderContext = React.createContext<
ThemeProviderState | undefined
>(undefined)
function isTheme(value: string | null): value is Theme {
if (value === null) {
return false
}
return THEME_VALUES.includes(value as Theme)
}
function getSystemTheme(): ResolvedTheme {
if (window.matchMedia(COLOR_SCHEME_QUERY).matches) {
return "dark"
}
return "light"
}
function disableTransitionsTemporarily() {
const style = document.createElement("style")
style.appendChild(
document.createTextNode(
"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}"
)
)
document.head.appendChild(style)
return () => {
window.getComputedStyle(document.body)
requestAnimationFrame(() => {
requestAnimationFrame(() => {
style.remove()
})
})
}
}
function isEditableTarget(target: EventTarget | null) {
if (!(target instanceof HTMLElement)) {
return false
}
if (target.isContentEditable) {
return true
}
const editableParent = target.closest(
"input, textarea, select, [contenteditable='true']"
)
if (editableParent) {
return true
}
return false
}
export function ThemeProvider({
children,
defaultTheme = "system",
storageKey = "theme",
disableTransitionOnChange = true,
...props
}: ThemeProviderProps) {
const [theme, setThemeState] = React.useState<Theme>(() => {
const storedTheme = localStorage.getItem(storageKey)
if (isTheme(storedTheme)) {
return storedTheme
}
return defaultTheme
})
const setTheme = React.useCallback(
(nextTheme: Theme) => {
localStorage.setItem(storageKey, nextTheme)
setThemeState(nextTheme)
},
[storageKey]
)
const applyTheme = React.useCallback(
(nextTheme: Theme) => {
const root = document.documentElement
const resolvedTheme =
nextTheme === "system" ? getSystemTheme() : nextTheme
const restoreTransitions = disableTransitionOnChange
? disableTransitionsTemporarily()
: null
root.classList.remove("light", "dark")
root.classList.add(resolvedTheme)
if (restoreTransitions) {
restoreTransitions()
}
},
[disableTransitionOnChange]
)
React.useEffect(() => {
applyTheme(theme)
if (theme !== "system") {
return undefined
}
const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY)
const handleChange = () => {
applyTheme("system")
}
mediaQuery.addEventListener("change", handleChange)
return () => {
mediaQuery.removeEventListener("change", handleChange)
}
}, [theme, applyTheme])
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.repeat) {
return
}
if (event.metaKey || event.ctrlKey || event.altKey) {
return
}
if (isEditableTarget(event.target)) {
return
}
if (event.key.toLowerCase() !== "d") {
return
}
setThemeState((currentTheme) => {
const nextTheme =
currentTheme === "dark"
? "light"
: currentTheme === "light"
? "dark"
: getSystemTheme() === "dark"
? "light"
: "dark"
localStorage.setItem(storageKey, nextTheme)
return nextTheme
})
}
window.addEventListener("keydown", handleKeyDown)
return () => {
window.removeEventListener("keydown", handleKeyDown)
}
}, [storageKey])
React.useEffect(() => {
const handleStorageChange = (event: StorageEvent) => {
if (event.storageArea !== localStorage) {
return
}
if (event.key !== storageKey) {
return
}
if (isTheme(event.newValue)) {
setThemeState(event.newValue)
return
}
setThemeState(defaultTheme)
}
window.addEventListener("storage", handleStorageChange)
return () => {
window.removeEventListener("storage", handleStorageChange)
}
}, [defaultTheme, storageKey])
const value = React.useMemo(
() => ({
theme,
setTheme,
}),
[theme, setTheme]
)
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
)
}
export const useTheme = () => {
const context = React.useContext(ThemeProviderContext)
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider")
}
return context
}

View File

@@ -0,0 +1,14 @@
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import "@workspace/ui/globals.css"
import { App } from "./App.tsx"
import { ThemeProvider } from "@/components/theme-provider.tsx"
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</StrictMode>
)

View File

@@ -0,0 +1,33 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@workspace/ui/*": ["../../packages/ui/src/*"]
}
},
"include": ["src"]
}

View File

@@ -0,0 +1,14 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@workspace/ui/*": ["../../packages/ui/src/*"]
}
}
}

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,14 @@
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})

View File

@@ -0,0 +1,22 @@
{
"name": "vite-monorepo",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"format": "turbo format",
"typecheck": "turbo typecheck"
},
"devDependencies": {
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"turbo": "^2.8.8",
"typescript": "5.9.3"
},
"packageManager": "pnpm@9.0.6",
"engines": {
"node": ">=20"
}
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"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"
}
}

View File

@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

View File

@@ -0,0 +1,37 @@
{
"name": "@workspace/ui",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"lint": "eslint",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"react": "^19.2.4",
"react-dom": "^19.2.4",
"zod": "^3.25.76"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"@turbo/gen": "^2.8.1",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@eslint/js": "^9.39.2",
"eslint": "^9.39.2",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^17.2.0",
"typescript-eslint": "^8.54.0",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3"
},
"exports": {
"./globals.css": "./src/styles/globals.css",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
"./hooks/*": "./src/hooks/*.ts"
}
}

View File

@@ -0,0 +1,4 @@
@import "tailwindcss";
@source "../../../apps/**/*.{ts,tsx}";
@source "../../../components/**/*.{ts,tsx}";
@source "../**/*.{ts,tsx}";

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@workspace/ui/*": ["./src/*"]
}
},
"include": ["."],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"outDir": "dist"
},
"include": ["src", "turbo"],
"exclude": ["node_modules", "dist"]
}

3749
templates/vite-monorepo/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
packages:
- "apps/*"
- "packages/*"

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"strict": true
}
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"format": {
"dependsOn": ["^format"]
},
"typecheck": {
"dependsOn": ["^typecheck"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}