UNPKG

create-nextjs-flex

Version:

CLI to create a flexible Next.js boilerplate

388 lines (322 loc) 9.77 kB
#!/usr/bin/env node const { execSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); async function askQuestion(question) { return new Promise((resolve) => { rl.question(question, (answer) => { resolve(answer.toLowerCase().trim()); }); }); } async function setup(name, options = {}) { console.log('Welcome to the Nextjs-flex Boilerplate Setup!'); const projectName = name || await askQuestion('Enter your project name: '); const config = await getConfiguration(options); console.log('\nSetting up your project...'); createProjectStructure(projectName, config); installDependencies(config); createNecessaryFiles(config); console.log('\nSetup complete! You can now start your development server with:'); console.log(`cd ${projectName} && yarn dev`); rl.close(); } async function getConfiguration(options) { if (options.yes) { return { useReactQuery: true, useApollo: true, useZustand: true, aiChoice: 'openai', useClass: false }; } const useReactQuery = await askQuestion('Do you want to use React Query? (y/n): ') === 'y'; const useApollo = !useReactQuery && (await askQuestion('Do you want to use Apollo Client? (y/n): ') === 'y'); const useZustand = !useApollo && (await askQuestion('Do you want to use Zustand for state management? (y/n): ') === 'y'); const aiChoice = await askQuestion('Which AI API do you want to use? (openai/claude/v0/none): '); const useClass = await askQuestion('Do you want to use a class-based service? (y/n): ') === 'y'; return { useReactQuery, useApollo, useZustand, aiChoice, useClass }; } function createProjectStructure(projectName, config) { fs.mkdirSync(projectName); process.chdir(projectName); execSync('npx create-next-app . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-yarn', { stdio: 'inherit' }); const directories = [ config.useApollo ? 'src/lib' : '', config.useClass ? 'src/services/Ai' : 'src/utils', config.useZustand ? 'src/store' : '', 'src/components' ].filter(Boolean); directories.forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); } function installDependencies(config) { const dependencies = []; if (config.useReactQuery) { dependencies.push('@tanstack/react-query', '@tanstack/react-query-devtools'); } if (config.useApollo) { dependencies.push('@apollo/client', 'graphql'); } if (config.useZustand) { dependencies.push('zustand'); } if (config.aiChoice === 'openai') { dependencies.push('openai'); } else if (config.aiChoice === 'claude') { dependencies.push('@anthropic-ai/sdk'); } else if (config.aiChoice === 'v0') { dependencies.push('ai'); dependencies.push('@ai-sdk/openai'); } if (dependencies.length > 0) { execSync(`yarn add ${dependencies.join(' ')}`, { stdio: 'inherit' }); } } function createNecessaryFiles(config) { if (config.useReactQuery) { createReactQueryProvider(); updateLayoutFile(); } if (config.useApollo) { createApolloClientFile(); } if (config.useZustand) { createZustandStore(); } if (config.aiChoice !== 'none') { createAIService(config.aiChoice, config.useClass); } } function updateLayoutFile() { const layoutPath = path.join('src', 'app', 'layout.tsx'); let content = fs.readFileSync(layoutPath, 'utf8'); content = content.replace( "export default function RootLayout({", "import Providers from './providers'\n\nexport default function RootLayout({" ); content = content.replace( "{children}", "<Providers>{children}</Providers>" ); fs.writeFileSync(layoutPath, content); } function createReactQueryProvider() { const content = ` import React, { useState } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; export default function Providers({ children }: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient()); return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); } `.trim(); fs.writeFileSync(path.join('src', 'app', 'providers.tsx'), content); } function createApolloClientFile() { const content = ` import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ uri: process.env.NEXT_PUBLIC_GRAPHQL_API_URL, cache: new InMemoryCache(), }); export default client; `.trim(); fs.writeFileSync(path.join('src', 'lib', 'apollo-client.ts'), content); // Create .env.local file const envContent = ` NEXT_PUBLIC_GRAPHQL_API_URL=https://api.spacex.land/graphql/ `.trim(); fs.writeFileSync('.env.local', envContent); } function createZustandStore() { const content = ` import { create } from 'zustand'; interface AppState { count: number; increment: () => void; } export const useAppStore = create<AppState>((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); `.trim(); fs.writeFileSync(path.join('src', 'store', 'appStore.ts'), content); } function createAIService(aiChoice, useClass) { const content = getAIServiceContent(aiChoice, useClass); const filePath = useClass ? path.join('src', 'services', 'Ai', 'ai.ts') : path.join('src', 'utils', 'ai.ts'); fs.writeFileSync(filePath, content); } function getAIServiceContent(aiChoice, useClass) { if (useClass) { return getClassBasedAIService(aiChoice); } else { return getFunctionBasedAIService(aiChoice); } } function getClassBasedAIService(aiChoice) { switch(aiChoice) { case 'openai': return ` import OpenAI from 'openai'; export class AIService { private readonly openai: OpenAI; constructor() { this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); } async generateText(prompt: string): Promise<string> { try { const chatCompletion = await this.openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: [{ role: "user", content: prompt }], }); return chatCompletion.choices[0].message.content || ''; } catch (error) { console.error('Error generating text:', error); throw new Error('Failed to generate text'); } } async generateImage(prompt: string): Promise<string> { try { const response = await this.openai.images.generate({ prompt: prompt, n: 1, size: "1024x1024", }); return response.data[0].url || ''; } catch (error) { console.error('Error generating image:', error); throw new Error('Failed to generate image'); } } } `.trim(); case 'claude': return ` import Anthropic from '@anthropic-ai/sdk'; export class AIService { private anthropic: Anthropic; constructor() { this.anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, }); } async generateText(prompt: string): Promise<string> { try { const completion = await this.anthropic.completions.create({ model: 'claude-2', prompt: prompt, max_tokens_to_sample: 300, }); return completion.completion; } catch (error) { console.error('Error generating text:', error); throw new Error('Failed to generate text'); } } } `.trim(); case 'v0': return ` import { generateText } from 'ai'; import { createOpenAI, OpenAIProvider, } from '@ai-sdk/openai'; export class AIService { private openai: OpenAIProvider; constructor() { this.openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY, compatibility: 'strict', }); } async generateCompletion(prompt: string): Promise<string> { const { text } = await generateText({ model: this.openai('gpt-3.5-turbo'), messages:[ { role: 'user', content: prompt } ], }); return text; } } `.trim(); default: return ''; } } function getFunctionBasedAIService(aiChoice) { switch(aiChoice) { case 'openai': return ` import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); export async function generateText(prompt: string): Promise<string> { const chatCompletion = await openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: [{ role: "user", content: prompt }], }); return chatCompletion.choices[0].message.content || ''; } `.trim(); case 'claude': return ` import Anthropic from '@anthropic-ai/sdk'; const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, }); export async function generateText(prompt: string): Promise<string> { const completion = await anthropic.completions.create({ model: 'claude-2', prompt: prompt, max_tokens_to_sample: 300, }); return completion.completion; } `.trim(); case 'v0': return ` import { createOpenAI, OpenAIProvider, } from '@ai-sdk/openai'; import { generateText } from 'ai'; const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY, compatibility: 'strict', }); export async function generateCompletion(prompt: string): Promise<string> { const {text} = await generateText({ model: openai('gpt-3.5-turbo'), messages:[ { role: 'user', content: prompt } ], }); return text; } `.trim(); default: return ''; } } module.exports = { setup };