setup-next-project
Version:
CLI to quickly create a pre-configured Next.js project with modular components and features
529 lines (481 loc) • 14.3 kB
JavaScript
import { getAvailableComponents, getComponentContent } from './components.js';
export const AVAILABLE_FEATURES = [
{
id: 'prisma',
name: 'Prisma ORM',
description: 'Add Prisma for database management with PostgreSQL',
dependencies: ['@prisma/client'],
devDependencies: ['prisma'],
packageJsonUpdates: {
scripts: {
'db:generate': 'prisma generate',
'db:push': 'prisma db push',
'db:migrate': 'prisma migrate dev',
'db:studio': 'prisma studio'
}
},
files: [
{
path: 'prisma/schema.prisma',
content: `// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
`
},
{
path: '.env.example',
content: `# Database
DATABASE_URL="postgresql://username:password@localhost:5432/mydb"
`
},
{
path: 'src/lib/prisma.ts',
content: `import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
`
}
]
},
{
id: 'auth',
name: 'NextAuth.js',
description: 'Add authentication with NextAuth.js',
dependencies: ['next-auth', '@auth/prisma-adapter'],
files: [
{
path: 'src/lib/auth.ts',
content: `import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "./prisma"
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],
})
`
},
{
path: 'app/api/auth/[...nextauth]/route.ts',
content: `import { handlers } from "@/lib/auth"
export const { GET, POST } = handlers
`
}
]
},
{
id: 'form-components',
name: 'Form Components',
description: 'Add advanced form components (Input, Select, Textarea, etc.)',
dependencies: ['@hookform/resolvers', 'react-hook-form', 'zod'],
files: [
{
path: 'src/components/ui/input.tsx',
content: `import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
`
},
{
path: 'src/components/ui/form.tsx',
content: `import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: \`\${id}-form-item\`,
formDescriptionId: \`\${id}-form-item-description\`,
formMessageId: \`\${id}-form-item-message\`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<LabelPrimitive.Root
ref={ref}
className={cn(
error && "text-destructive",
className
)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? \`\${formDescriptionId}\`
: \`\${formDescriptionId} \${formMessageId}\`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
`
}
]
},
{
id: 'data-table',
name: 'Data Table',
description: 'Add advanced data table with sorting, filtering, and pagination',
dependencies: ['@tanstack/react-table'],
files: [
{
path: 'src/components/ui/table.tsx',
content: `import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
`
}
]
},
{
id: 'api-routes',
name: 'API Routes Example',
description: 'Add example API routes with error handling',
files: [
{
path: 'app/api/users/route.ts',
content: `import { NextRequest, NextResponse } from 'next/server'
export async function GET() {
try {
// Example: fetch users from database
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
]
return NextResponse.json(users)
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch users' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
// Example: create user
const newUser = {
id: Date.now(),
...body
}
return NextResponse.json(newUser, { status: 201 })
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create user' },
{ status: 500 }
)
}
}
`
}
]
}
];
export async function getFeature(featureId) {
// First try to find in base features
const baseFeature = AVAILABLE_FEATURES.find(feature => feature.id === featureId);
if (baseFeature) {
return baseFeature;
}
// Then try to find in component features
const componentFeatures = await getComponentFeatures();
return componentFeatures.find(feature => feature.id === featureId);
}
export function getAvailableFeatures() {
return AVAILABLE_FEATURES;
}
// Create dynamic component features from the components directory
export async function getComponentFeatures() {
const components = getAvailableComponents();
const componentFeatures = [];
for (const component of components) {
const content = await getComponentContent(component.id);
if (content) {
componentFeatures.push({
id: `component-${component.id}`,
name: component.name,
description: component.description,
dependencies: component.dependencies,
devDependencies: component.devDependencies,
files: [{
path: component.targetPath,
content: content,
mode: 'create'
}]
});
}
}
return componentFeatures;
}
// Get all available features including components
export async function getAllAvailableFeatures() {
const baseFeatures = AVAILABLE_FEATURES;
const componentFeatures = await getComponentFeatures();
return [...baseFeatures, ...componentFeatures];
}
//# sourceMappingURL=features.js.map