UNPKG

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
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