Skip to content

Commit

Permalink
Merge pull request #14 from Yashwanth1906/develop-scaffold
Browse files Browse the repository at this point in the history
[FIX] : Fixed the Bug in Next.js Script
  • Loading branch information
shivasankaran18 authored Feb 23, 2025
2 parents 262a810 + ddd9b42 commit cbd51da
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 41 deletions.
54 changes: 47 additions & 7 deletions apps/web/app/api/scaffold/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,40 @@ import path from 'path'
import fs from 'fs/promises'
import { installDjangoDependencies } from '../../../../../packages/scripts/backend/django'
import createAngularTS from '../../../../../packages/scripts/frontend/angularts'
import simpleGit from 'simple-git'
import { setupNextAuth } from '../../../../../packages/scripts/Auth/nextAuth'
import { setupPassport } from '../../../../../packages/scripts/Auth/passport'
import { setupMongoose } from '../../../../../packages/scripts/orms/mongoSetup'
import { setupDrizzle } from '../../../../../packages/scripts/orms/drizzleSetup'
import { setupTailwindCSS } from '../../../../../packages/scripts/ui/tailwindcss'
import { setupShadcn } from '../../../../../packages/scripts/ui/shadcn'
import simpleGit from 'simple-git'




const encoder = new TextEncoder();

export async function POST(req: NextRequest) {
try {
const config = await req.json()

console.log(config)
const projectDir = join(config.projectPath, config.projectName)

await mkdir(projectDir, { recursive: true })
const git = simpleGit(projectDir);
git.init()
.then(() => console.log('Initialized a new Git repository'))
.catch(err => console.error('Error:', err));

if(config.giturl) {
await git.remote(['add', 'origin', config.giturl]).then(() => console.log('Remote added')).catch(err => console.error('Error:', err));
}
const emitLog = (message: string) => {
console.log(`[Emit Logs]: ${message}`);
global.logs = global.logs || [];
global.logs.push(message);
};


switch(config.frontend) {
case 'react-ts':
await createReactTS(config, projectDir,emitLog)
Expand All @@ -48,12 +63,9 @@ export async function POST(req: NextRequest) {
break;
case 'vue':
await createVueJS(config, projectDir,emitLog)
break
case 'vue-ts':

await createVueTS(config, projectDir,emitLog)

await createVueTS(config, projectDir,emitLog)

break
case 'angularts':
await createAngularTS(config, projectDir)
Expand All @@ -73,6 +85,9 @@ export async function POST(req: NextRequest) {
case 'django':
await installDjangoDependencies(projectDir);
break
case 'nextjs':
await createNextJS(config, projectDir, emitLog);
break;
default:
throw new Error(`Unsupported backend: ${config.backend}`)
}
Expand Down Expand Up @@ -183,6 +198,31 @@ Thumbs.db
)
}
}

export async function GET() {
const stream = new ReadableStream({
start(controller) {
const interval = setInterval(() => {
if (global.logs?.length) {
const log = global.logs.shift();
const data = `data: ${log}\n\n`;
controller.enqueue(encoder.encode(data));
}
}, 100);

return () => clearInterval(interval);
},
});

return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}

async function configureDjangoFiles(projectPath: string) {

const settingsPath = path.join(projectPath, 'core', 'settings.py');
Expand Down
151 changes: 121 additions & 30 deletions apps/web/app/scaffold/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use client';

import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardHeader, CardContent, CardFooter } from '@/components/ui/card';
import { Card } from '@/components/ui/card';
import { Layout, Server, Database, FolderOpen } from 'lucide-react';
import { Steps } from '@/components/ui/steps';
import { toast } from 'sonner';
import { Label } from '@/components/ui/label';
import { motion } from 'framer-motion';

interface ProjectConfig {
projectName: string;
Expand All @@ -21,6 +21,7 @@ interface ProjectConfig {
auth: string | 'Skip' | null;
dbUrl: string;
giturl: string | null;
ui : string | null;
}

const SkipButton = ({ onSkip }: { onSkip: () => void }) => (
Expand Down Expand Up @@ -66,6 +67,47 @@ const Navbar = () => (
</nav>
);

const TerminalLogs = ({ logs }: { logs: string[] }) => (
<div className="bg-black rounded-lg p-4 font-mono text-sm text-green-400 max-h-96 overflow-auto">
{logs.map((log, i) => (
<div key={i} className="whitespace-pre-wrap">
<span className="text-blue-400">$</span> {log}
</div>
))}
</div>
);

const SuccessAnimation = ({ projectPath }: { projectPath: string }) => (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center p-8"
>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 0.5 }}
className="text-6xl mb-4"
>
🎉
</motion.div>
<h2 className="text-2xl font-bold mb-4">Congratulations!</h2>
<p className="text-muted-foreground mb-6">
Your project is ready. Happy coding!
</p>
<div className="bg-secondary p-4 rounded-lg mb-6">
<p className="font-medium mb-2">Project Location:</p>
<code className="text-sm">{projectPath}</code>
</div>
<Button
onClick={() => window.open(`file://${projectPath}`, '_blank')}
className="gap-2"
>
<FolderOpen className="h-4 w-4" />
Open Project Folder
</Button>
</motion.div>
);

const ScaffoldPage = () => {
const [step, setStep] = useState(0);
const [config, setConfig] = useState<ProjectConfig>({
Expand All @@ -80,7 +122,28 @@ const ScaffoldPage = () => {
auth: null,
dbUrl: '',
giturl: null,
ui : null,
});
const [isGenerating, setIsGenerating] = useState(false);
const [logs, setLogs] = useState<string[]>([]);
const [isSuccess, setIsSuccess] = useState(false);
const [generatedPath, setGeneratedPath] = useState('');

useEffect(() => {
if (isGenerating) {
const eventSource = new EventSource('/api/scaffold/logs');

eventSource.onmessage = (event) => {
setLogs(prev => [...prev, event.data]);
};

eventSource.onerror = () => {
eventSource.close();
};

return () => eventSource.close();
}
}, [isGenerating]);

const steps = [
{
Expand Down Expand Up @@ -141,9 +204,20 @@ const ScaffoldPage = () => {
{ id: 'vue-ts', name: 'Vue + TypeScript', description: 'Vue 3 with TypeScript template', features: ['Vite', 'TypeScript', 'Vue Router', 'Pinia', 'TailwindCSS'] },
{ id: 'vue', name: 'Vue (JavaScript)', description: 'Vue 3 with JavaScript template', features: ['Vite', 'JavaScript', 'Vue Router', 'Pinia', 'TailwindCSS'] },
{ id: 'angularts', name: 'Angular (Typescript)', description: 'Angular 16 with Typescript template', features: ['Angular CLI', 'Typescript', 'Angular Router', 'Angular Material', 'TailwindCSS'] },
{ id: 'nextjs', name: 'Next.js', description: 'Next.js with TypeScript template', features: ['Next.js', 'TypeScript', 'TailwindCSS'] },
{ id: 'Skip', name: 'Skip', description: 'Skip frontend configuration', features: ['Skip this step'] },
]
},
{
title : "UI",
description : "Choose your UI",
icon : <Layout className="w-5 h-5" />,
options : [
{ id: 'shadcn', name: 'Shadcn', description: 'Shadcn UI', features: ['Shadcn UI', 'TailwindCSS'] },
{ id: 'tailwind', name: 'TailwindCSS', description: 'TailwindCSS', features: ['TailwindCSS', 'React'] },
{ id: 'Skip', name: 'Skip', description: 'Skip UI configuration', features: ['Skip this step'] },
]
},
{
title: "Backend",
description: "Select your backend",
Expand Down Expand Up @@ -181,7 +255,7 @@ const ScaffoldPage = () => {
]
},
{
title: "Authentication",
title: "auth",
description: "Choose your authentication method",
icon: <Server className="w-5 h-5" />,
options: [
Expand All @@ -208,7 +282,24 @@ const ScaffoldPage = () => {
</div>
</div>
) : null
}
},{
title: "Git URL",
description: "Enter your git URL",
icon: <Database className="w-5 h-5" />,
component: config.giturl !== 'Skip' ? (
<div className="space-y-4 w-full max-w-md">
<div>
<Label htmlFor="giturl">Git URL</Label>
<Input
id="giturl"
placeholder="Enter your git URL"
value={config.giturl || ''}
onChange={(e) => setConfig(prev => ({ ...prev, giturl: e.target.value }))}
/>
</div>
</div>
) : null
}
];

const handleSelect = (key: keyof ProjectConfig, value: string) => {
Expand Down Expand Up @@ -238,6 +329,9 @@ const ScaffoldPage = () => {
if (!validateConfig()) return;

try {
setIsGenerating(true);
setLogs([]);

const response = await fetch('/api/scaffold', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
Expand All @@ -249,33 +343,13 @@ const ScaffoldPage = () => {
const result = await response.json();

if (result.success) {
toast.success('Project generated successfully!', {
description: (
<div className="mt-2">
<p className="font-medium">Project created at:</p>
<code className="block bg-secondary p-2 rounded mt-1">
{result.projectPath}
</code>

<p className="font-medium mt-4">To start development:</p>
<div className="bg-secondary p-2 rounded mt-1">
{result.instructions.setup.map((step: string, i: number) => (
<code key={i} className="block">{step}</code>
))}
</div>

<p className="font-medium mt-4">Your app will run at:</p>
<div className="bg-secondary p-2 rounded mt-1">
<code className="block">Frontend: http://localhost:{config.frontendPort}</code>
<code className="block">Backend: http://localhost:{config.backendPort}</code>
</div>
</div>
),
duration: 10000,
});
setGeneratedPath(result.projectPath);
setIsSuccess(true);
}
} catch (error) {
toast.error('Failed to generate project');
} finally {
setIsGenerating(false);
}
};

Expand Down Expand Up @@ -363,7 +437,15 @@ const ScaffoldPage = () => {
) : (
<Button
onClick={() => setStep(Math.min(steps.length - 1, step + 1))}
disabled={step === 0 ? !config.projectName || !config.projectPath : !config[steps[step]?.title.toLowerCase() as keyof ProjectConfig]}
disabled={
step === 0
? !config.projectName || !config.projectPath
: step === steps.length - 2 && config.database !== 'Skip' // Database URL step
? !config.dbUrl
: step === steps.length - 1 && config.giturl !== 'Skip' // Git URL step
? !config.giturl
: !config[steps[step]?.title.toLowerCase() as keyof ProjectConfig]
}
className="px-6"
>
Next
Expand All @@ -372,6 +454,15 @@ const ScaffoldPage = () => {
</div>
</div>
</div>

{isSuccess ? (
<SuccessAnimation projectPath={generatedPath} />
) : isGenerating ? (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Generating Your Project...</h2>
<TerminalLogs logs={logs} />
</div>
) : null}
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions apps/web/types/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface HTMLInputElement {
webkitdirectory: string;
directory: string;
}
10 changes: 6 additions & 4 deletions packages/scripts/frontend/nextjs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join } from 'node:path'
import { execSync } from 'child_process'
import { execSync, ExecSyncOptions } from 'child_process'
import { writeFile, mkdir } from 'node:fs/promises'
import { existsSync } from 'fs'

Expand All @@ -11,11 +11,13 @@ export async function createNextJS(config: any, projectDir: string, emitLog: (lo
await mkdir(frontendDir, { recursive: true });
}
emitLog('Creating Next.js project...');
execSync('npm init next-app@latest . -- --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --no-git', {
const execOptions: ExecSyncOptions = {
cwd: frontendDir,
stdio: 'inherit',
shell: true,
});
encoding: 'utf-8'
};

execSync('npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --no-git', execOptions);

emitLog('Updating package.json...');
const packageJsonPath = join(frontendDir, 'package.json');
Expand Down

0 comments on commit cbd51da

Please sign in to comment.