@shivasankaran18/stackd 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/apps/cli/src/cli.js +217 -0
  2. package/dist/apps/cli/src/commands/create.js +148 -0
  3. package/dist/apps/cli/src/scripts/Auth/jwt.js +78 -0
  4. package/dist/apps/cli/src/scripts/Auth/nextAuth.js +131 -0
  5. package/dist/apps/cli/src/scripts/Auth/passport.js +218 -0
  6. package/dist/apps/cli/src/scripts/backend/django.js +20 -0
  7. package/dist/apps/cli/src/scripts/backend/expressjs.js +58 -0
  8. package/dist/apps/cli/src/scripts/backend/expressts.js +83 -0
  9. package/dist/apps/cli/src/scripts/frontend/angularjs.js +1 -0
  10. package/dist/apps/cli/src/scripts/frontend/angularts.js +22 -0
  11. package/dist/apps/cli/src/scripts/frontend/nextjs.js +62 -0
  12. package/dist/apps/cli/src/scripts/frontend/reactjs.js +31 -0
  13. package/dist/apps/cli/src/scripts/frontend/reactts.js +30 -0
  14. package/dist/apps/cli/src/scripts/frontend/vuejs.js +37 -0
  15. package/dist/apps/cli/src/scripts/frontend/vuets.js +43 -0
  16. package/dist/apps/cli/src/scripts/orms/drizzleSetup.js +85 -0
  17. package/dist/apps/cli/src/scripts/orms/mongoSetup.js +53 -0
  18. package/dist/apps/cli/src/scripts/orms/prismaSetup.js +12 -0
  19. package/dist/apps/cli/src/scripts/ui/shadcn.js +207 -0
  20. package/dist/apps/cli/src/scripts/ui/tailwindcss.js +102 -0
  21. package/dist/apps/web/app/api/auth/[...nextauth]/route.js +5 -0
  22. package/dist/apps/web/app/api/scaffold/route.js +251 -0
  23. package/dist/apps/web/app/home/page.js +19 -0
  24. package/dist/apps/web/app/layout.js +19 -0
  25. package/dist/apps/web/app/page.js +1 -0
  26. package/dist/apps/web/app/providers.js +7 -0
  27. package/dist/apps/web/app/scaffold/page.js +326 -0
  28. package/dist/apps/web/components/Sidebar.js +54 -0
  29. package/dist/apps/web/components/theme-provider.js +6 -0
  30. package/dist/apps/web/components/ui/button.js +32 -0
  31. package/dist/apps/web/components/ui/card.js +15 -0
  32. package/dist/apps/web/components/ui/dropdown-menu.js +54 -0
  33. package/dist/apps/web/components/ui/input.js +7 -0
  34. package/dist/apps/web/components/ui/label.js +9 -0
  35. package/dist/apps/web/components/ui/scroll-area.js +19 -0
  36. package/dist/apps/web/components/ui/sonner.js +15 -0
  37. package/dist/apps/web/components/ui/steps.js +14 -0
  38. package/dist/apps/web/components/ui/switch.js +9 -0
  39. package/dist/apps/web/lib/auth.js +32 -0
  40. package/dist/apps/web/lib/redis.js +9 -0
  41. package/dist/apps/web/lib/utils.js +5 -0
  42. package/dist/packages/scripts/Auth/jwt.js +78 -0
  43. package/dist/packages/scripts/Auth/nextAuth.js +131 -0
  44. package/dist/packages/scripts/Auth/passport.js +218 -0
  45. package/dist/packages/scripts/backend/django.js +20 -0
  46. package/dist/packages/scripts/backend/expressjs.js +58 -0
  47. package/dist/packages/scripts/backend/expressts.js +83 -0
  48. package/dist/packages/scripts/frontend/angularjs.js +1 -0
  49. package/dist/packages/scripts/frontend/angularts.js +22 -0
  50. package/dist/packages/scripts/frontend/nextjs.js +62 -0
  51. package/dist/packages/scripts/frontend/reactjs.js +31 -0
  52. package/dist/packages/scripts/frontend/reactts.js +30 -0
  53. package/dist/packages/scripts/frontend/vuejs.js +37 -0
  54. package/dist/packages/scripts/frontend/vuets.js +43 -0
  55. package/dist/packages/scripts/orms/drizzleSetup.js +85 -0
  56. package/dist/packages/scripts/orms/mongoSetup.js +53 -0
  57. package/dist/packages/scripts/orms/prismaSetup.js +12 -0
  58. package/dist/packages/scripts/ui/shadcn.js +207 -0
  59. package/dist/packages/scripts/ui/tailwindcss.js +102 -0
  60. package/dist/packages/ui/src/button.js +10 -0
  61. package/dist/packages/ui/src/card.js +11 -0
  62. package/dist/packages/ui/src/code.js +6 -0
  63. package/dist/packages/ui/turbo/generators/config.js +30 -0
  64. package/dist/stackd.js +114 -0
  65. package/dist/tsconfig.tsbuildinfo +1 -0
  66. package/package.json +3 -3
  67. package/stackd.ts +0 -0
@@ -0,0 +1,251 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { createReactTS } from '../../../../../packages/scripts/frontend/reactts';
5
+ import { createReactJS } from '../../../../../packages/scripts/frontend/reactjs';
6
+ import { createExpressTS } from '../../../../../packages/scripts/backend/expressts';
7
+ import { createExpressJS } from '../../../../../packages/scripts/backend/expressjs';
8
+ import { setupPrisma } from '../../../../../packages/scripts/orms/prismaSetup';
9
+ import { createVueJS } from '../../../../../packages/scripts/frontend/vuejs';
10
+ // import { createNextJS } from '../../../../../packages/scripts/frontend/nextjs'
11
+ import { createVueTS } from '../../../../../packages/scripts/frontend/vuets';
12
+ import { jwtAuthts, jwtAuthdjango } from '../../../../../packages/scripts/Auth/jwt';
13
+ import path from 'path';
14
+ import fs from 'fs/promises';
15
+ import { installDjangoDependencies } from '../../../../../packages/scripts/backend/django';
16
+ import createAngularTS from '../../../../../packages/scripts/frontend/angularts';
17
+ import { setupNextAuth } from '../../../../../packages/scripts/Auth/nextAuth';
18
+ import { setupPassport } from '../../../../../packages/scripts/Auth/passport';
19
+ import { setupMongoose } from '../../../../../packages/scripts/orms/mongoSetup';
20
+ import { setupDrizzle } from '../../../../../packages/scripts/orms/drizzleSetup';
21
+ import { setupTailwindCSS } from '../../../../../packages/scripts/ui/tailwindcss';
22
+ import { setupShadcn } from '../../../../../packages/scripts/ui/shadcn';
23
+ import simpleGit from 'simple-git';
24
+ const encoder = new TextEncoder();
25
+ export async function POST(req) {
26
+ try {
27
+ const config = await req.json();
28
+ console.log(config);
29
+ const projectDir = join(config.projectPath, config.projectName);
30
+ await mkdir(projectDir, { recursive: true });
31
+ const git = simpleGit(projectDir);
32
+ git.init()
33
+ .then(() => console.log('Initialized a new Git repository'))
34
+ .catch(err => console.error('Error:', err));
35
+ if (config.giturl) {
36
+ await git.remote(['add', 'origin', config.giturl]).then(() => console.log('Remote added')).catch(err => console.error('Error:', err));
37
+ }
38
+ const emitLog = (message) => {
39
+ console.log(`[Emit Logs]: ${message}`);
40
+ global.logs = global.logs || [];
41
+ global.logs.push(message);
42
+ };
43
+ switch (config.frontend) {
44
+ case 'react-ts':
45
+ await createReactTS(config, projectDir, emitLog);
46
+ break;
47
+ case 'react':
48
+ await createReactJS(config, projectDir, emitLog);
49
+ break;
50
+ // case 'nextjs':
51
+ // await createNextJS(config, projectDir, emitLog);
52
+ // break;
53
+ case 'django':
54
+ await installDjangoDependencies(projectDir);
55
+ break;
56
+ case 'vue':
57
+ await createVueJS(config, projectDir, emitLog);
58
+ break;
59
+ case 'vue-ts':
60
+ await createVueTS(config, projectDir, emitLog);
61
+ break;
62
+ case 'angularts':
63
+ await createAngularTS(config, projectDir);
64
+ break;
65
+ default:
66
+ throw new Error(`Unsupported frontend`);
67
+ }
68
+ switch (config.backend) {
69
+ case 'express-ts':
70
+ await createExpressTS(config, projectDir, emitLog);
71
+ break;
72
+ case 'express':
73
+ console.log("Creating the backend");
74
+ await createExpressJS(config, projectDir, emitLog);
75
+ break;
76
+ case 'django':
77
+ await installDjangoDependencies(projectDir);
78
+ break;
79
+ // case 'nextjs':
80
+ // await createNextJS(config, projectDir, emitLog);
81
+ // break;
82
+ default:
83
+ throw new Error(`Unsupported backend`);
84
+ }
85
+ switch (config.auth) {
86
+ case 'jwt':
87
+ await jwtAuthts(config, projectDir, emitLog);
88
+ break;
89
+ case 'nextauth':
90
+ await setupNextAuth(config, projectDir, emitLog);
91
+ break;
92
+ case 'passport':
93
+ await setupPassport(config, projectDir, emitLog);
94
+ break;
95
+ default:
96
+ throw new Error(`Unsupported auth`);
97
+ }
98
+ switch (config.orm) {
99
+ case 'drizzle':
100
+ await setupDrizzle(config, projectDir, emitLog);
101
+ break;
102
+ case 'prisma':
103
+ await setupPrisma(config, projectDir, emitLog);
104
+ break;
105
+ case 'mongoose':
106
+ await setupMongoose(config, projectDir, emitLog);
107
+ break;
108
+ default:
109
+ throw new Error(`Unsupported orm`);
110
+ }
111
+ const gitignore = `
112
+ # Dependencies
113
+ node_modules
114
+ .pnp
115
+ .pnp.js
116
+
117
+ # Production
118
+ dist
119
+ build
120
+
121
+ # Environment
122
+ .env
123
+ .env.local
124
+ .env.development.local
125
+ .env.test.local
126
+ .env.production.local
127
+
128
+ # Logs
129
+ npm-debug.log*
130
+ yarn-debug.log*
131
+ yarn-error.log*
132
+
133
+ # Editor
134
+ .vscode
135
+ .idea
136
+ *.swp
137
+ *.swo
138
+
139
+ # OS
140
+ .DS_Store
141
+ Thumbs.db
142
+ `;
143
+ switch (config.backend) {
144
+ case 'express-ts':
145
+ await jwtAuthts(config, projectDir, emitLog);
146
+ break;
147
+ case 'express':
148
+ await jwtAuthts(config, projectDir, emitLog);
149
+ break;
150
+ case 'django':
151
+ await jwtAuthdjango(config, projectDir, emitLog);
152
+ break;
153
+ default:
154
+ break;
155
+ }
156
+ switch (config.ui) {
157
+ case 'tailwind':
158
+ await setupTailwindCSS(config, projectDir, emitLog);
159
+ break;
160
+ case 'shadcn':
161
+ await setupTailwindCSS(config, projectDir, emitLog);
162
+ await setupShadcn(config, projectDir, emitLog);
163
+ break;
164
+ default:
165
+ break;
166
+ }
167
+ return NextResponse.json({
168
+ success: true,
169
+ projectPath: projectDir,
170
+ instructions: {
171
+ setup: [
172
+ `cd ${projectDir}`,
173
+ 'npm install',
174
+ 'npm run install:all',
175
+ 'npm run dev'
176
+ ]
177
+ }
178
+ });
179
+ }
180
+ catch (error) {
181
+ console.error('Error:', error);
182
+ return NextResponse.json({
183
+ error: 'Failed to generate project',
184
+ details: error instanceof Error ? error.message : 'Unknown error'
185
+ }, { status: 500 });
186
+ }
187
+ }
188
+ export async function GET() {
189
+ const stream = new ReadableStream({
190
+ start(controller) {
191
+ const interval = setInterval(() => {
192
+ if (global.logs?.length) {
193
+ const log = global.logs.shift();
194
+ const data = `data: ${log}\n\n`;
195
+ controller.enqueue(encoder.encode(data));
196
+ }
197
+ }, 100);
198
+ return () => clearInterval(interval);
199
+ },
200
+ });
201
+ return new Response(stream, {
202
+ headers: {
203
+ 'Content-Type': 'text/event-stream',
204
+ 'Cache-Control': 'no-cache',
205
+ 'Connection': 'keep-alive',
206
+ },
207
+ });
208
+ }
209
+ async function configureDjangoFiles(projectPath) {
210
+ const settingsPath = path.join(projectPath, 'core', 'settings.py');
211
+ const urlsPath = path.join(projectPath, 'core', 'urls.py');
212
+ try {
213
+ let settingsContent = await fs.readFile(settingsPath, 'utf8');
214
+ const restFrameworkSettings = `
215
+
216
+ REST_FRAMEWORK = {
217
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
218
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
219
+ ],
220
+ }
221
+ `;
222
+ const installedAppsIndex = settingsContent.indexOf('INSTALLED_APPS');
223
+ const insertPosition = settingsContent.indexOf(']', installedAppsIndex) + 1;
224
+ settingsContent =
225
+ settingsContent.slice(0, insertPosition) +
226
+ restFrameworkSettings +
227
+ settingsContent.slice(insertPosition);
228
+ await fs.writeFile(settingsPath, settingsContent, 'utf8');
229
+ let urlsContent = await fs.readFile(urlsPath, 'utf8');
230
+ const newUrlsContent = `from django.contrib import admin
231
+ from django.urls import path, include
232
+ from rest_framework_simplejwt import views as jwt_views
233
+
234
+ urlpatterns = [
235
+ path('admin/', admin.site.urls),
236
+ path('api/token/',
237
+ jwt_views.TokenObtainPairView.as_view(),
238
+ name='token_obtain_pair'),
239
+ path('api/token/refresh/',
240
+ jwt_views.TokenRefreshView.as_view(),
241
+ name='token_refresh'),
242
+ path('', include('main.urls')),
243
+ ]
244
+ `;
245
+ await fs.writeFile(urlsPath, newUrlsContent, 'utf8');
246
+ }
247
+ catch (error) {
248
+ console.error('Error configuring Django files:', error);
249
+ throw error;
250
+ }
251
+ }
@@ -0,0 +1,19 @@
1
+ import { Button } from "@/components/ui/button";
2
+ import Link from "next/link";
3
+ import { ArrowRight } from "lucide-react";
4
+ export default function Home() {
5
+ return (<main className="flex min-h-screen flex-col items-center justify-center p-24">
6
+ <div className="text-center">
7
+ <h1 className="text-4xl font-bold mb-4">Project Scaffolder</h1>
8
+ <p className="text-xl text-muted-foreground mb-8">
9
+ Create your full-stack application in minutes
10
+ </p>
11
+ <Link href="/scaffold">
12
+ <Button size="lg">
13
+ Get Started
14
+ <ArrowRight className="ml-2 h-4 w-4"/>
15
+ </Button>
16
+ </Link>
17
+ </div>
18
+ </main>);
19
+ }
@@ -0,0 +1,19 @@
1
+ import { Inter } from "next/font/google";
2
+ import "./globals.css";
3
+ import { ThemeProvider } from "@/components/theme-provider";
4
+ import { Toaster } from "@/components/ui/sonner";
5
+ const inter = Inter({ subsets: ["latin"] });
6
+ export const metadata = {
7
+ title: "Stack'd - Full Stack Project Generator",
8
+ description: "Generate full-stack applications with ease",
9
+ };
10
+ export default function RootLayout({ children, }) {
11
+ return (<html lang="en" suppressHydrationWarning>
12
+ <body className={inter.className}>
13
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
14
+ <main>{children}</main>
15
+ <Toaster />
16
+ </ThemeProvider>
17
+ </body>
18
+ </html>);
19
+ }
@@ -0,0 +1 @@
1
+ export { default } from './scaffold/page';
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { SessionProvider } from "next-auth/react";
3
+ export const Providers = ({ children }) => {
4
+ return <SessionProvider>
5
+ {children}
6
+ </SessionProvider>;
7
+ };
@@ -0,0 +1,326 @@
1
+ 'use client';
2
+ import React, { useState, useEffect } from 'react';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Input } from '@/components/ui/input';
5
+ import { Card } from '@/components/ui/card';
6
+ import { Layout, Server, Database, FolderOpen } from 'lucide-react';
7
+ import { Steps } from '@/components/ui/steps';
8
+ import { toast } from 'sonner';
9
+ import { Label } from '@/components/ui/label';
10
+ import { motion } from 'framer-motion';
11
+ const SkipButton = ({ onSkip }) => (<Button variant="outline" onClick={onSkip} className="text-orange-500 hover:text-orange-600 hover:bg-orange-50 dark:hover:bg-orange-950/10 border-orange-200 hover:border-orange-300 dark:border-orange-800">
12
+ Skip
13
+ </Button>);
14
+ const Navbar = () => (<nav className="navbar backdrop-blur-sm">
15
+ <div className="max-w-6xl mx-auto px-8 py-3 flex justify-center">
16
+ <div className="flex flex-col items-center">
17
+ <pre className="text-cyan-500 text-xs leading-none font-bold">
18
+ {` ██████╗████████╗ █████╗ ██████╗██╗ ██╗'██████╗
19
+ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝██╔══██╗
20
+ ╚█████╗ ██║ ███████║██║ █████═╝ ██║ ██║
21
+ ╚═══██╗ ██║ ██╔══██║██║ ██╔═██╗ ██║ ██║
22
+ ██████╔╝ ██║ ██║ ██║╚██████╗██║ ██╗██████╔╝
23
+ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═════╝`}
24
+ </pre>
25
+ <p className="text-sm text-cyan-500/80 mt-2">Full Stack Project Generator</p>
26
+ </div>
27
+
28
+ <a href="https://github.com/ShyamSunder06/STACKD" target="_blank" rel="noopener noreferrer" className="absolute right-8 text-muted-foreground hover:text-foreground">
29
+ <svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
30
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
31
+ </svg>
32
+ </a>
33
+ </div>
34
+ </nav>);
35
+ const TerminalLogs = ({ logs }) => (<div className="bg-black rounded-lg p-4 font-mono text-sm text-green-400 max-h-96 overflow-auto">
36
+ {logs.map((log, i) => (<div key={i} className="whitespace-pre-wrap">
37
+ <span className="text-blue-400">$</span> {log}
38
+ </div>))}
39
+ </div>);
40
+ const SuccessAnimation = ({ projectPath }) => (<motion.div initial={{ opacity: 0, scale: 0.5 }} animate={{ opacity: 1, scale: 1 }} className="text-center p-8">
41
+ <motion.div animate={{ rotate: 360 }} transition={{ duration: 0.5 }} className="text-6xl mb-4">
42
+ 🎉
43
+ </motion.div>
44
+ <h2 className="text-2xl font-bold mb-4">Congratulations!</h2>
45
+ <p className="text-muted-foreground mb-6">
46
+ Your project is ready. Happy coding!
47
+ </p>
48
+ <div className="bg-secondary p-4 rounded-lg mb-6">
49
+ <p className="font-medium mb-2">Project Location:</p>
50
+ <code className="text-sm">{projectPath}</code>
51
+ </div>
52
+ <Button onClick={() => window.open(`file://${projectPath}`, '_blank')} className="gap-2">
53
+ <FolderOpen className="h-4 w-4"/>
54
+ Open Project Folder
55
+ </Button>
56
+ </motion.div>);
57
+ const ScaffoldPage = () => {
58
+ const [step, setStep] = useState(0);
59
+ const [config, setConfig] = useState({
60
+ projectName: '',
61
+ projectPath: process.cwd(),
62
+ frontendPort: 3000,
63
+ backendPort: 3001,
64
+ frontend: null,
65
+ backend: null,
66
+ database: null,
67
+ orm: null,
68
+ auth: null,
69
+ dbUrl: '',
70
+ giturl: null,
71
+ ui: null,
72
+ });
73
+ const [isGenerating, setIsGenerating] = useState(false);
74
+ const [logs, setLogs] = useState([]);
75
+ const [isSuccess, setIsSuccess] = useState(false);
76
+ const [generatedPath, setGeneratedPath] = useState('');
77
+ useEffect(() => {
78
+ if (isGenerating) {
79
+ const eventSource = new EventSource('/api/scaffold/logs');
80
+ eventSource.onmessage = (event) => {
81
+ setLogs(prev => [...prev, event.data]);
82
+ };
83
+ eventSource.onerror = () => {
84
+ eventSource.close();
85
+ };
86
+ return () => eventSource.close();
87
+ }
88
+ }, [isGenerating]);
89
+ const steps = [
90
+ {
91
+ title: "Project Settings",
92
+ description: "Basic configuration",
93
+ icon: <FolderOpen className="w-5 h-5"/>,
94
+ component: (<div className="space-y-4 w-full max-w-md">
95
+ <div>
96
+ <Label htmlFor="projectName">Project Name</Label>
97
+ <Input id="projectName" placeholder="my-fullstack-app" value={config.projectName} onChange={(e) => setConfig(prev => ({ ...prev, projectName: e.target.value }))}/>
98
+ </div>
99
+ <div>
100
+ <Label htmlFor="projectPath">Project Location</Label>
101
+ <Input id="projectPath" placeholder="/path/to/your/project" value={config.projectPath} onChange={(e) => setConfig(prev => ({ ...prev, projectPath: e.target.value }))}/>
102
+ </div>
103
+ <div className="grid grid-cols-2 gap-4">
104
+ <div>
105
+ <Label htmlFor="frontendPort">Frontend Port</Label>
106
+ <Input id="frontendPort" type="number" value={config.frontendPort} onChange={(e) => setConfig(prev => ({ ...prev, frontendPort: parseInt(e.target.value) }))}/>
107
+ </div>
108
+ <div>
109
+ <Label htmlFor="backendPort">Backend Port</Label>
110
+ <Input id="backendPort" type="number" value={config.backendPort} onChange={(e) => setConfig(prev => ({ ...prev, backendPort: parseInt(e.target.value) }))}/>
111
+ </div>
112
+ </div>
113
+ </div>)
114
+ },
115
+ {
116
+ title: "Frontend",
117
+ description: "Choose your frontend",
118
+ icon: <Layout className="w-5 h-5"/>,
119
+ options: [
120
+ { id: 'react-ts', name: 'React + TypeScript', description: 'React with TypeScript template', features: ['Vite', 'TypeScript', 'React Router', 'TailwindCSS'] },
121
+ { id: 'react', name: 'React (JavaScript)', description: 'React with JavaScript template', features: ['Vite', 'JavaScript', 'React Router', 'TailwindCSS'] },
122
+ { id: 'django', name: 'Django Templates', description: 'Django framework', features: ['Full-stack', 'Django ORM', 'Django Admin'] },
123
+ { id: 'vue-ts', name: 'Vue + TypeScript', description: 'Vue 3 with TypeScript template', features: ['Vite', 'TypeScript', 'Vue Router', 'Pinia', 'TailwindCSS'] },
124
+ { id: 'vue', name: 'Vue (JavaScript)', description: 'Vue 3 with JavaScript template', features: ['Vite', 'JavaScript', 'Vue Router', 'Pinia', 'TailwindCSS'] },
125
+ { id: 'angularts', name: 'Angular (Typescript)', description: 'Angular 16 with Typescript template', features: ['Angular CLI', 'Typescript', 'Angular Router', 'Angular Material', 'TailwindCSS'] },
126
+ { id: 'nextjs', name: 'Next.js', description: 'Next.js with TypeScript template', features: ['Next.js', 'TypeScript', 'TailwindCSS'] },
127
+ { id: 'Skip', name: 'Skip', description: 'Skip frontend configuration', features: ['Skip this step'] },
128
+ ]
129
+ },
130
+ {
131
+ title: "UI",
132
+ description: "Choose your UI",
133
+ icon: <Layout className="w-5 h-5"/>,
134
+ options: [
135
+ { id: 'shadcn', name: 'Shadcn', description: 'Shadcn UI', features: ['Shadcn UI', 'TailwindCSS'] },
136
+ { id: 'tailwind', name: 'TailwindCSS', description: 'TailwindCSS', features: ['TailwindCSS', 'React'] },
137
+ { id: 'Skip', name: 'Skip', description: 'Skip UI configuration', features: ['Skip this step'] },
138
+ ]
139
+ },
140
+ {
141
+ title: "Backend",
142
+ description: "Select your backend",
143
+ icon: <Server className="w-5 h-5"/>,
144
+ options: [
145
+ { id: 'express-ts', name: 'Express + TypeScript', description: 'Express with TypeScript setup', features: ['TypeScript', 'API Routes', 'Middleware', 'CORS'] },
146
+ { id: 'express', name: 'Express (JavaScript)', description: 'Express with JavaScript setup', features: ['JavaScript', 'API Routes', 'Middleware', 'CORS'] },
147
+ { id: 'django', name: 'Django', description: 'Django framework', features: ['Full-stack', 'Django ORM', 'Django Admin'] },
148
+ { id: 'Skip', name: 'Skip', description: 'Skip backend configuration', features: ['Skip this step'] }
149
+ ]
150
+ },
151
+ {
152
+ title: "Database",
153
+ description: "Pick your database",
154
+ icon: <Database className="w-5 h-5"/>,
155
+ options: [
156
+ { id: 'postgresql', name: 'PostgreSQL', description: 'Powerful, open source database', features: ['Prisma ORM', 'Migrations', 'TypeScript'] },
157
+ { id: 'mongodb', name: 'MongoDB', description: 'NoSQL document database', features: ['Mongoose', 'Schemas', 'TypeScript'] },
158
+ { id: 'Skip', name: 'Skip', description: 'Skip database configuration', features: ['Skip this step'] }
159
+ ]
160
+ },
161
+ {
162
+ title: "ORM",
163
+ description: "Select your ORM",
164
+ icon: <Database className="w-5 h-5"/>,
165
+ options: config.database === 'postgresql' ? [
166
+ { id: 'prisma', name: 'Prisma', description: 'Prisma ORM', features: ['Type-safe', 'Migrations'] },
167
+ { id: 'drizzle', name: 'Drizzle', description: 'Drizzle ORM', features: ['Lightweight', 'Flexible'] },
168
+ { id: 'Skip', name: 'Skip', description: 'Skip ORM configuration', features: ['Skip this step'] }
169
+ ] : config.database === 'mongodb' ? [
170
+ { id: 'mongoose', name: 'Mongoose', description: 'Mongoose ORM', features: ['Schemas', 'Validation'] },
171
+ { id: 'Skip', name: 'Skip', description: 'Skip ORM configuration', features: ['Skip this step'] }
172
+ ] : [
173
+ { id: 'Skip', name: 'Skip', description: 'Skip ORM configuration', features: ['Skip this step'] }
174
+ ]
175
+ },
176
+ {
177
+ title: "auth",
178
+ description: "Choose your authentication method",
179
+ icon: <Server className="w-5 h-5"/>,
180
+ options: [
181
+ { id: 'jwt', name: 'JWT', description: 'JSON Web Tokens', features: ['Stateless', 'Secure'] },
182
+ { id: 'nextauth', name: 'NextAuth', description: 'NextAuth.js', features: ['Strategies', 'Middleware'] },
183
+ { id: 'passport', name: 'Passport', description: 'Passport.js', features: ['Strategies', 'Middleware'] },
184
+ { id: 'Skip', name: 'Skip', description: 'Skip authentication configuration', features: ['Skip this step'] }
185
+ ]
186
+ },
187
+ {
188
+ title: "Database URL",
189
+ description: "Enter your database URL",
190
+ icon: <Database className="w-5 h-5"/>,
191
+ component: config.database !== 'Skip' ? (<div className="space-y-4 w-full max-w-md">
192
+ <div>
193
+ <Label htmlFor="dbUrl">Database URL</Label>
194
+ <Input id="dbUrl" placeholder="Enter your database URL" value={config.dbUrl} onChange={(e) => setConfig(prev => ({ ...prev, dbUrl: e.target.value }))}/>
195
+ </div>
196
+ </div>) : null
197
+ }, {
198
+ title: "Git URL",
199
+ description: "Enter your git URL",
200
+ icon: <Database className="w-5 h-5"/>,
201
+ component: config.giturl !== 'Skip' ? (<div className="space-y-4 w-full max-w-md">
202
+ <div>
203
+ <Label htmlFor="giturl">Git URL</Label>
204
+ <Input id="giturl" placeholder="Enter your git URL" value={config.giturl || ''} onChange={(e) => setConfig(prev => ({ ...prev, giturl: e.target.value }))}/>
205
+ </div>
206
+ </div>) : null
207
+ }
208
+ ];
209
+ const handleSelect = (key, value) => {
210
+ setConfig(prev => ({ ...prev, [key]: value }));
211
+ if (step < steps.length - 1) {
212
+ setStep(step + 1);
213
+ }
214
+ };
215
+ const validateConfig = () => {
216
+ if (!config.projectName.trim()) {
217
+ toast.error('Please enter a project name');
218
+ return false;
219
+ }
220
+ if (!config.projectPath.trim()) {
221
+ toast.error('Please enter a project location');
222
+ return false;
223
+ }
224
+ if (config.frontendPort === config.backendPort) {
225
+ toast.error('Frontend and backend ports must be different');
226
+ return false;
227
+ }
228
+ return true;
229
+ };
230
+ const handleGenerate = async () => {
231
+ if (!validateConfig())
232
+ return;
233
+ try {
234
+ setIsGenerating(true);
235
+ setLogs([]);
236
+ const response = await fetch('/api/scaffold', {
237
+ method: 'POST',
238
+ headers: { 'Content-Type': 'application/json' },
239
+ body: JSON.stringify(config)
240
+ });
241
+ if (!response.ok)
242
+ throw new Error('Failed to generate project');
243
+ const result = await response.json();
244
+ if (result.success) {
245
+ setGeneratedPath(result.projectPath);
246
+ setIsSuccess(true);
247
+ }
248
+ }
249
+ catch (error) {
250
+ toast.error('Failed to generate project');
251
+ }
252
+ finally {
253
+ setIsGenerating(false);
254
+ }
255
+ };
256
+ return (<div className="min-h-screen bg-background">
257
+ <Navbar />
258
+ <div className="p-8">
259
+ <div className="max-w-6xl mx-auto">
260
+ <div className="mb-8">
261
+ <h1 className="text-4xl font-bold mb-2">Create Your Project</h1>
262
+ <p className="text-muted-foreground">
263
+ Configure your full-stack application
264
+ </p>
265
+ </div>
266
+
267
+ <Steps steps={steps.map(s => ({
268
+ title: s.title,
269
+ description: s.description,
270
+ icon: s.icon
271
+ }))} currentStep={step} onStepClick={setStep}/>
272
+
273
+ <div className="mt-8">
274
+ {steps[step]?.component ? (<div className="flex justify-center">
275
+ {steps[step].component}
276
+ </div>) : (<div className="relative">
277
+ <div className="grid md:grid-cols-3 gap-4 card-grid">
278
+ {steps[step]?.options?.filter(option => option.id !== 'Skip').map((option) => (<Card key={option.id} className={`card p-4 cursor-pointer interactive-element ${config[steps[step]?.title.toLowerCase()] === option.id
279
+ ? 'border-primary/50 bg-primary/5 card-selected'
280
+ : ''}`} onClick={() => handleSelect(steps[step]?.title.toLowerCase(), option.id)}>
281
+ <div className="relative z-10">
282
+ <h3 className="font-medium mb-2 text-lg">{option.name}</h3>
283
+ <p className="text-sm text-muted-foreground mb-4">
284
+ {option.description}
285
+ </p>
286
+ <div className="space-y-1">
287
+ {option.features.map((feature, index) => (<div key={index} className="text-xs text-muted-foreground flex items-center gap-1">
288
+ <span className="text-primary">•</span> {feature}
289
+ </div>))}
290
+ </div>
291
+ </div>
292
+ </Card>))}
293
+ </div>
294
+ </div>)}
295
+
296
+ <div className="mt-8 flex justify-between">
297
+ <Button variant="outline" onClick={() => setStep(Math.max(0, step - 1))} disabled={step === 0} className="px-6">
298
+ Previous
299
+ </Button>
300
+
301
+ {step === steps.length - 1 ? (<Button onClick={handleGenerate} className="px-6">
302
+ Generate Project
303
+ </Button>) : (<div className="flex gap-2">
304
+ {step > 0 && !steps[step].component && (<SkipButton onSkip={() => handleSelect(steps[step]?.title.toLowerCase(), 'Skip')}/>)}
305
+ <Button onClick={() => setStep(Math.min(steps.length - 1, step + 1))} disabled={step === 0
306
+ ? !config.projectName || !config.projectPath
307
+ : step === steps.length - 2 && config.database !== 'Skip' // Database URL step
308
+ ? !config.dbUrl
309
+ : step === steps.length - 1 && config.giturl !== 'Skip' // Git URL step
310
+ ? !config.giturl
311
+ : !config[steps[step]?.title.toLowerCase()]} className="px-6">
312
+ Next
313
+ </Button>
314
+ </div>)}
315
+ </div>
316
+ </div>
317
+
318
+ {isSuccess ? (<SuccessAnimation projectPath={generatedPath}/>) : isGenerating ? (<div className="space-y-4">
319
+ <h2 className="text-xl font-semibold">Generating Your Project...</h2>
320
+ <TerminalLogs logs={logs}/>
321
+ </div>) : null}
322
+ </div>
323
+ </div>
324
+ </div>);
325
+ };
326
+ export default ScaffoldPage;