forgestack-os-cli 0.2.1 → 0.3.1
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.
- package/README.md +78 -0
- package/dist/commands/create.d.ts +1 -1
- package/dist/commands/create.js +55 -60
- package/dist/commands/create.js.map +1 -1
- package/dist/generators/api.js +42 -38
- package/dist/generators/api.js.map +1 -1
- package/dist/generators/auth.js +171 -356
- package/dist/generators/auth.js.map +1 -1
- package/dist/generators/backend.js +146 -129
- package/dist/generators/backend.js.map +1 -1
- package/dist/generators/common.js +50 -15
- package/dist/generators/common.js.map +1 -1
- package/dist/generators/database.js +18 -24
- package/dist/generators/database.js.map +1 -1
- package/dist/generators/docker.js +17 -21
- package/dist/generators/docker.js.map +1 -1
- package/dist/generators/frontend.js +164 -157
- package/dist/generators/frontend.js.map +1 -1
- package/dist/generators/index.js +69 -34
- package/dist/generators/index.js.map +1 -1
- package/dist/index.js +13 -8
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.js +1 -2
- package/dist/utils/logger.js +9 -15
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/prompts.d.ts +15 -1
- package/dist/utils/prompts.js +111 -33
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/security.d.ts +4 -0
- package/dist/utils/security.js +8 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/validators.js +7 -9
- package/dist/utils/validators.js.map +1 -1
- package/package.json +36 -14
package/dist/generators/auth.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateAuth = generateAuth;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
-
async function generateAuth(config, frontendDir, backendDir) {
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
export async function generateAuth(config, frontendDir, backendDir) {
|
|
10
4
|
switch (config.auth) {
|
|
11
5
|
case 'jwt':
|
|
12
6
|
await generateJWTAuth(config, frontendDir, backendDir);
|
|
@@ -26,11 +20,62 @@ async function generateAuth(config, frontendDir, backendDir) {
|
|
|
26
20
|
default:
|
|
27
21
|
throw new Error(`Unsupported auth: ${config.auth}`);
|
|
28
22
|
}
|
|
23
|
+
// Ensure frontend pages exist for all auth types
|
|
24
|
+
await generateFrontendAuthPages(config, frontendDir);
|
|
29
25
|
}
|
|
30
|
-
async function generateJWTAuth(config,
|
|
31
|
-
// Backend: Auth
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
async function generateJWTAuth(config, _frontendDir, backendDir) {
|
|
27
|
+
// Backend: Auth logic
|
|
28
|
+
if (config.backend === 'express') {
|
|
29
|
+
const routesDir = path.join(backendDir, 'src', 'routes');
|
|
30
|
+
await fs.ensureDir(routesDir);
|
|
31
|
+
await fs.writeFile(path.join(routesDir, 'auth.ts'), getExpressAuthRoutes(config));
|
|
32
|
+
}
|
|
33
|
+
else if (config.backend === 'fastify') {
|
|
34
|
+
const routesDir = path.join(backendDir, 'src', 'routes');
|
|
35
|
+
await fs.ensureDir(routesDir);
|
|
36
|
+
await fs.writeFile(path.join(routesDir, 'auth.ts'), getFastifyAuthRoutes(config));
|
|
37
|
+
}
|
|
38
|
+
else if (config.backend === 'nestjs') {
|
|
39
|
+
await generateNestJSAuth(config, backendDir);
|
|
40
|
+
}
|
|
41
|
+
// Isolation: Add .eslintrc.json to prevent parent config inheritance
|
|
42
|
+
const eslintConfig = {
|
|
43
|
+
root: true,
|
|
44
|
+
extends: ["eslint:recommended"],
|
|
45
|
+
env: { node: true, es2021: true }
|
|
46
|
+
};
|
|
47
|
+
await fs.writeJSON(path.join(backendDir, '.eslintrc.json'), eslintConfig, { spaces: 2 });
|
|
48
|
+
}
|
|
49
|
+
async function generateFrontendAuthPages(config, frontendDir) {
|
|
50
|
+
const isNext = config.frontend === 'nextjs';
|
|
51
|
+
const frontendLibDir = path.join(frontendDir, isNext ? 'app/lib' : 'src/lib');
|
|
52
|
+
await fs.ensureDir(frontendLibDir);
|
|
53
|
+
// Always generate auth context file for JWT-based auth
|
|
54
|
+
await fs.writeFile(path.join(frontendLibDir, 'auth.tsx'), getAuthContextCode());
|
|
55
|
+
if (isNext) {
|
|
56
|
+
const nextEslint = {
|
|
57
|
+
root: true,
|
|
58
|
+
extends: ["next/core-web-vitals"]
|
|
59
|
+
};
|
|
60
|
+
await fs.writeJSON(path.join(frontendDir, '.eslintrc.json'), nextEslint, { spaces: 2 });
|
|
61
|
+
const authAppDir = path.join(frontendDir, 'app');
|
|
62
|
+
await fs.ensureDir(path.join(authAppDir, 'login'));
|
|
63
|
+
await fs.ensureDir(path.join(authAppDir, 'dashboard'));
|
|
64
|
+
await fs.writeFile(path.join(authAppDir, 'login', 'page.tsx'), getLoginPageCode(config));
|
|
65
|
+
await fs.writeFile(path.join(authAppDir, 'dashboard', 'page.tsx'), getDashboardPageCode(config));
|
|
66
|
+
await fs.writeFile(path.join(authAppDir, 'page.tsx'), getHomePageCode(config));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const pagesDir = path.join(frontendDir, 'src', 'pages');
|
|
70
|
+
await fs.ensureDir(pagesDir);
|
|
71
|
+
await fs.writeFile(path.join(pagesDir, 'LoginPage.tsx'), getLoginPageCode(config));
|
|
72
|
+
await fs.writeFile(path.join(pagesDir, 'HomePage.tsx'), getHomePageCode(config));
|
|
73
|
+
await fs.writeFile(path.join(pagesDir, 'DashboardPage.tsx'), getDashboardPageCode(config));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function getExpressAuthRoutes(config) {
|
|
77
|
+
return `import { Router } from 'express';
|
|
78
|
+
import bcrypt from 'bcryptjs';
|
|
34
79
|
import jwt from 'jsonwebtoken';
|
|
35
80
|
import { z } from 'zod';
|
|
36
81
|
${config.database === 'mongodb' ? "import User from '../models/User';" : "import prisma from '../lib/prisma';"}
|
|
@@ -41,128 +86,108 @@ const registerSchema = z.object({
|
|
|
41
86
|
email: z.string().email(),
|
|
42
87
|
password: z.string().min(8),
|
|
43
88
|
name: z.string().optional(),
|
|
44
|
-
${config.multiTenant ? 'tenantId: z.string(),' : ''}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const loginSchema = z.object({
|
|
48
|
-
email: z.string().email(),
|
|
49
|
-
password: z.string(),
|
|
50
89
|
});
|
|
51
90
|
|
|
52
91
|
router.post('/register', async (req, res, next) => {
|
|
53
92
|
try {
|
|
54
93
|
const data = registerSchema.parse(req.body);
|
|
55
|
-
|
|
56
94
|
const hashedPassword = await bcrypt.hash(data.password, 10);
|
|
57
|
-
|
|
58
|
-
${config.
|
|
59
|
-
const user = await User.create({
|
|
60
|
-
email: data.email,
|
|
61
|
-
password: hashedPassword,
|
|
62
|
-
name: data.name,
|
|
63
|
-
${config.multiTenant ? 'tenantId: data.tenantId,' : ''}
|
|
64
|
-
});
|
|
65
|
-
` : `
|
|
66
|
-
const user = await prisma.user.create({
|
|
67
|
-
data: {
|
|
68
|
-
email: data.email,
|
|
69
|
-
password: hashedPassword,
|
|
70
|
-
name: data.name,
|
|
71
|
-
${config.multiTenant ? 'tenantId: data.tenantId,' : ''}
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
`}
|
|
75
|
-
|
|
76
|
-
const token = jwt.sign(
|
|
77
|
-
{
|
|
78
|
-
userId: user.id,
|
|
79
|
-
email: user.email,
|
|
80
|
-
${config.multiTenant ? 'tenantId: user.tenantId,' : ''}
|
|
81
|
-
},
|
|
82
|
-
process.env.JWT_SECRET!,
|
|
83
|
-
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
84
|
-
);
|
|
85
|
-
|
|
95
|
+
const user = ${config.database === 'mongodb' ? `await User.create({ email: data.email, password: hashedPassword, name: data.name })` : `await prisma.user.create({ data: { email: data.email, password: hashedPassword, name: data.name } })`};
|
|
96
|
+
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET || '${config.jwtSecret}', { expiresIn: '7d' });
|
|
86
97
|
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
|
|
87
|
-
} catch (error) {
|
|
88
|
-
next(error);
|
|
89
|
-
}
|
|
98
|
+
} catch (error) { next(error); }
|
|
90
99
|
});
|
|
91
100
|
|
|
92
101
|
router.post('/login', async (req, res, next) => {
|
|
93
102
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
` : `
|
|
99
|
-
const user = await prisma.user.findUnique({
|
|
100
|
-
where: { email: data.email },
|
|
101
|
-
});
|
|
102
|
-
`}
|
|
103
|
-
|
|
104
|
-
if (!user || !user.password) {
|
|
105
|
-
return res.status(401).json({ error: 'Invalid credentials' });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const validPassword = await bcrypt.compare(data.password, user.password);
|
|
109
|
-
|
|
110
|
-
if (!validPassword) {
|
|
111
|
-
return res.status(401).json({ error: 'Invalid credentials' });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const token = jwt.sign(
|
|
115
|
-
{
|
|
116
|
-
userId: user.id,
|
|
117
|
-
email: user.email,
|
|
118
|
-
${config.multiTenant ? 'tenantId: user.tenantId,' : ''}
|
|
119
|
-
},
|
|
120
|
-
process.env.JWT_SECRET!,
|
|
121
|
-
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
122
|
-
);
|
|
123
|
-
|
|
103
|
+
const { email, password } = req.body;
|
|
104
|
+
const user = ${config.database === 'mongodb' ? `await User.findOne({ email })` : `await prisma.user.findUnique({ where: { email } })`};
|
|
105
|
+
if (!user || !(await bcrypt.compare(password, user.password))) return res.status(401).json({ error: 'Invalid credentials' });
|
|
106
|
+
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET || '${config.jwtSecret}', { expiresIn: '7d' });
|
|
124
107
|
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
|
|
125
|
-
} catch (error) {
|
|
126
|
-
next(error);
|
|
127
|
-
}
|
|
108
|
+
} catch (error) { next(error); }
|
|
128
109
|
});
|
|
129
110
|
|
|
130
111
|
export default router;
|
|
131
112
|
`;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
import api from '../lib/api';
|
|
113
|
+
}
|
|
114
|
+
function getFastifyAuthRoutes(_config) {
|
|
115
|
+
return `import { FastifyInstance } from 'fastify';
|
|
116
|
+
import bcrypt from 'bcrypt';
|
|
137
117
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
118
|
+
export default async function authRoutes(fastify: FastifyInstance) {
|
|
119
|
+
fastify.post('/register', async (request, reply) => {
|
|
120
|
+
reply.send({ success: true });
|
|
121
|
+
});
|
|
122
|
+
fastify.post('/login', async (request, reply) => {
|
|
123
|
+
reply.send({ token: '...' });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
async function generateNestJSAuth(_config, backendDir) {
|
|
129
|
+
const authDir = path.join(backendDir, 'src', 'auth');
|
|
130
|
+
await fs.ensureDir(authDir);
|
|
131
|
+
await fs.ensureDir(path.join(authDir, 'dto'));
|
|
132
|
+
const controller = `import { Controller, Post, Body } from '@nestjs/common';
|
|
133
|
+
import { AuthService } from './auth.service';
|
|
134
|
+
import { RegisterDto, LoginDto } from './dto/auth.dto';
|
|
135
|
+
|
|
136
|
+
@Controller('auth')
|
|
137
|
+
export class AuthController {
|
|
138
|
+
constructor(private authService: AuthService) {}
|
|
139
|
+
@Post('register')
|
|
140
|
+
async register(@Body() dto: RegisterDto) { return this.authService.register(dto); }
|
|
141
|
+
@Post('login')
|
|
142
|
+
async login(@Body() dto: LoginDto) { return this.authService.login(dto); }
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
await fs.writeFile(path.join(authDir, 'auth.controller.ts'), controller);
|
|
146
|
+
const dto = `import { IsEmail, IsString, MinLength } from 'class-validator';
|
|
147
|
+
export class RegisterDto {
|
|
148
|
+
@IsEmail()
|
|
149
|
+
email!: string;
|
|
150
|
+
@IsString()
|
|
151
|
+
@MinLength(8)
|
|
152
|
+
password!: string;
|
|
153
|
+
@IsString()
|
|
141
154
|
name?: string;
|
|
142
155
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
logout: () => void;
|
|
149
|
-
isLoading: boolean;
|
|
156
|
+
export class LoginDto {
|
|
157
|
+
@IsEmail()
|
|
158
|
+
email!: string;
|
|
159
|
+
@IsString()
|
|
160
|
+
password!: string;
|
|
150
161
|
}
|
|
162
|
+
`;
|
|
163
|
+
await fs.writeFile(path.join(authDir, 'dto/auth.dto.ts'), dto);
|
|
164
|
+
const service = `import { Injectable } from '@nestjs/common';
|
|
165
|
+
import { JwtService } from '@nestjs/jwt';
|
|
166
|
+
|
|
167
|
+
@Injectable()
|
|
168
|
+
export class AuthService {
|
|
169
|
+
constructor(private jwtService: JwtService) {}
|
|
170
|
+
async register(dto: any) { return { success: true }; }
|
|
171
|
+
async login(user: any) { return { token: this.jwtService.sign({ id: '1' }) }; }
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
await fs.writeFile(path.join(authDir, 'auth.service.ts'), service);
|
|
175
|
+
}
|
|
176
|
+
function getAuthContextCode() {
|
|
177
|
+
return `'use client';
|
|
178
|
+
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
179
|
+
import api from '../lib/api';
|
|
151
180
|
|
|
152
|
-
const AuthContext = createContext<
|
|
181
|
+
const AuthContext = createContext<any>(undefined);
|
|
153
182
|
|
|
154
183
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
155
|
-
const [user, setUser] = useState
|
|
184
|
+
const [user, setUser] = useState(null);
|
|
156
185
|
const [isLoading, setIsLoading] = useState(true);
|
|
157
186
|
|
|
158
187
|
useEffect(() => {
|
|
159
188
|
const token = localStorage.getItem('token');
|
|
160
189
|
if (token) {
|
|
161
|
-
|
|
162
|
-
api.get('/auth/me')
|
|
163
|
-
.then(res => setUser(res.data.user))
|
|
164
|
-
.catch(() => localStorage.removeItem('token'))
|
|
165
|
-
.finally(() => setIsLoading(false));
|
|
190
|
+
api.get('/auth/me').then(res => setUser(res.data.user)).finally(() => setIsLoading(false));
|
|
166
191
|
} else {
|
|
167
192
|
setIsLoading(false);
|
|
168
193
|
}
|
|
@@ -174,296 +199,86 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
174
199
|
setUser(res.data.user);
|
|
175
200
|
};
|
|
176
201
|
|
|
177
|
-
const register = async (email: string, password: string, name?: string) => {
|
|
178
|
-
const res = await api.post('/auth/register', { email, password, name });
|
|
179
|
-
localStorage.setItem('token', res.data.token);
|
|
180
|
-
setUser(res.data.user);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
202
|
const logout = () => {
|
|
184
203
|
localStorage.removeItem('token');
|
|
185
204
|
setUser(null);
|
|
186
205
|
};
|
|
187
206
|
|
|
188
|
-
return
|
|
189
|
-
<AuthContext.Provider value={{ user, login, register, logout, isLoading }}>
|
|
190
|
-
{children}
|
|
191
|
-
</AuthContext.Provider>
|
|
192
|
-
);
|
|
207
|
+
return <AuthContext.Provider value={{ user, login, logout, isLoading }}>{children}</AuthContext.Provider>;
|
|
193
208
|
}
|
|
194
209
|
|
|
195
|
-
export
|
|
196
|
-
const context = useContext(AuthContext);
|
|
197
|
-
if (!context) {
|
|
198
|
-
throw new Error('useAuth must be used within AuthProvider');
|
|
199
|
-
}
|
|
200
|
-
return context;
|
|
201
|
-
}
|
|
210
|
+
export const useAuth = () => useContext(AuthContext);
|
|
202
211
|
`;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
await fs_extra_1.default.ensureDir(frontendLibDir);
|
|
212
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendLibDir, 'auth.tsx'), authContext);
|
|
213
|
-
// Frontend: Login page
|
|
214
|
-
const loginPage = `import { useState } from 'react';
|
|
215
|
-
import { useNavigate } from 'react-router-dom';
|
|
216
|
-
import { useAuth } from '../lib/auth';
|
|
212
|
+
}
|
|
213
|
+
function getLoginPageCode(config) {
|
|
214
|
+
const isNext = config.frontend === 'nextjs';
|
|
215
|
+
const authPath = isNext ? '../lib/auth' : '../lib/auth';
|
|
216
|
+
return `${isNext ? "'use client';" : ""}
|
|
217
|
+
import { useState } from 'react';
|
|
218
|
+
${isNext ? "import { useRouter } from 'next/navigation';" : "import { useNavigate } from 'react-router-dom';"}
|
|
219
|
+
import { useAuth } from '${authPath}';
|
|
217
220
|
|
|
218
221
|
export default function LoginPage() {
|
|
219
222
|
const [email, setEmail] = useState('');
|
|
220
223
|
const [password, setPassword] = useState('');
|
|
221
|
-
const [error, setError] = useState('');
|
|
222
224
|
const { login } = useAuth();
|
|
223
|
-
const navigate = useNavigate();
|
|
225
|
+
${isNext ? "const router = useRouter();" : "const navigate = useNavigate();"}
|
|
224
226
|
|
|
225
|
-
const handleSubmit = async (e:
|
|
227
|
+
const handleSubmit = async (e: any) => {
|
|
226
228
|
e.preventDefault();
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
navigate('/dashboard');
|
|
230
|
-
} catch (err: any) {
|
|
231
|
-
setError(err.response?.data?.error || 'Login failed');
|
|
232
|
-
}
|
|
229
|
+
await login(email, password);
|
|
230
|
+
${isNext ? "router.push('/dashboard');" : "navigate('/dashboard');"}
|
|
233
231
|
};
|
|
234
232
|
|
|
235
233
|
return (
|
|
236
234
|
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
|
237
|
-
<
|
|
238
|
-
<
|
|
239
|
-
{
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
<label className="block text-gray-300 mb-2">Email</label>
|
|
243
|
-
<input
|
|
244
|
-
type="email"
|
|
245
|
-
value={email}
|
|
246
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
247
|
-
className="w-full p-3 rounded bg-gray-700 text-white"
|
|
248
|
-
required
|
|
249
|
-
/>
|
|
250
|
-
</div>
|
|
251
|
-
<div className="mb-6">
|
|
252
|
-
<label className="block text-gray-300 mb-2">Password</label>
|
|
253
|
-
<input
|
|
254
|
-
type="password"
|
|
255
|
-
value={password}
|
|
256
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
257
|
-
className="w-full p-3 rounded bg-gray-700 text-white"
|
|
258
|
-
required
|
|
259
|
-
/>
|
|
260
|
-
</div>
|
|
261
|
-
<button
|
|
262
|
-
type="submit"
|
|
263
|
-
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 rounded"
|
|
264
|
-
>
|
|
265
|
-
Login
|
|
266
|
-
</button>
|
|
267
|
-
</form>
|
|
268
|
-
</div>
|
|
235
|
+
<form onSubmit={handleSubmit} className="bg-gray-800 p-8 rounded shadow-lg">
|
|
236
|
+
<input type="email" value={email} onChange={e => setEmail(e.target.value)} className="block w-full mb-4 p-2 bg-gray-700 text-white" />
|
|
237
|
+
<input type="password" value={password} onChange={e => setPassword(e.target.value)} className="block w-full mb-4 p-2 bg-gray-700 text-white" />
|
|
238
|
+
<button type="submit" className="w-full bg-blue-600 p-2 text-white">Login</button>
|
|
239
|
+
</form>
|
|
269
240
|
</div>
|
|
270
241
|
);
|
|
271
242
|
}
|
|
272
243
|
`;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const homePage = `export default function HomePage() {
|
|
244
|
+
}
|
|
245
|
+
function getHomePageCode(config) {
|
|
246
|
+
return `export default function HomePage() {
|
|
277
247
|
return (
|
|
278
|
-
<div className="min-h-screen bg-gray-900 text-white">
|
|
279
|
-
<
|
|
280
|
-
<h1 className="text-5xl font-bold mb-4">${config.projectName}</h1>
|
|
281
|
-
<p className="text-xl text-gray-400 mb-8">
|
|
282
|
-
Generated by ForgeStack OS
|
|
283
|
-
</p>
|
|
284
|
-
<div className="space-x-4">
|
|
285
|
-
<a
|
|
286
|
-
href="/login"
|
|
287
|
-
className="inline-block bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded font-semibold"
|
|
288
|
-
>
|
|
289
|
-
Get Started
|
|
290
|
-
</a>
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
248
|
+
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
|
|
249
|
+
<h1 className="text-4xl">${config.projectName}</h1>
|
|
293
250
|
</div>
|
|
294
251
|
);
|
|
295
252
|
}
|
|
296
253
|
`;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
254
|
+
}
|
|
255
|
+
function getDashboardPageCode(config) {
|
|
256
|
+
const isNext = config.frontend === 'nextjs';
|
|
257
|
+
const authPath = isNext ? '../lib/auth' : '../lib/auth';
|
|
258
|
+
return `${isNext ? "'use client';" : ""}
|
|
259
|
+
import { useAuth } from '${authPath}';
|
|
303
260
|
export default function DashboardPage() {
|
|
304
|
-
const { user, logout
|
|
305
|
-
const navigate = useNavigate();
|
|
306
|
-
|
|
307
|
-
useEffect(() => {
|
|
308
|
-
if (!isLoading && !user) {
|
|
309
|
-
navigate('/login');
|
|
310
|
-
}
|
|
311
|
-
}, [user, isLoading, navigate]);
|
|
312
|
-
|
|
313
|
-
if (isLoading) {
|
|
314
|
-
return <div className="min-h-screen bg-gray-900 flex items-center justify-center">
|
|
315
|
-
<div className="text-white">Loading...</div>
|
|
316
|
-
</div>;
|
|
317
|
-
}
|
|
318
|
-
|
|
261
|
+
const { user, logout } = useAuth();
|
|
319
262
|
return (
|
|
320
|
-
<div className="min-h-screen bg-gray-900 text-white">
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
<button
|
|
325
|
-
onClick={logout}
|
|
326
|
-
className="bg-red-600 hover:bg-red-700 px-4 py-2 rounded"
|
|
327
|
-
>
|
|
328
|
-
Logout
|
|
329
|
-
</button>
|
|
330
|
-
</div>
|
|
331
|
-
</nav>
|
|
332
|
-
<div className="container mx-auto px-4 py-16">
|
|
333
|
-
<h2 className="text-3xl font-bold mb-4">Welcome, {user?.name || user?.email}!</h2>
|
|
334
|
-
<p className="text-gray-400">You're logged in to your dashboard.</p>
|
|
335
|
-
</div>
|
|
263
|
+
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
264
|
+
<h1>Dashboard for ${config.projectName}</h1>
|
|
265
|
+
<p>Welcome, {user?.email}</p>
|
|
266
|
+
<button onClick={logout} className="bg-red-600 p-2 rounded">Logout</button>
|
|
336
267
|
</div>
|
|
337
268
|
);
|
|
338
269
|
}
|
|
339
270
|
`;
|
|
340
|
-
await fs_extra_1.default.writeFile(path_1.default.join(pagesDir, 'DashboardPage.tsx'), dashboardPage);
|
|
341
271
|
}
|
|
342
272
|
async function generateClerkAuth(config, frontendDir, _backendDir) {
|
|
343
|
-
|
|
344
|
-
const clerkSetup = `# Clerk Authentication Setup
|
|
345
|
-
|
|
346
|
-
1. Create a Clerk account at https://clerk.com
|
|
347
|
-
2. Create a new application
|
|
348
|
-
3. Copy your API keys to .env
|
|
349
|
-
4. For ${isNextJS ? 'Next.js' : 'React'}, the components are already configured.
|
|
350
|
-
|
|
351
|
-
Components used:
|
|
352
|
-
- LoginPage: Uses Clerk's SignIn component
|
|
353
|
-
- Dashboard: Uses Clerk's UserButton
|
|
354
|
-
- Layout: ${isNextJS ? 'Configured with ClerkProvider' : 'Needs ClerkProvider in main.tsx'}
|
|
355
|
-
`;
|
|
356
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'CLERK_SETUP.md'), clerkSetup);
|
|
357
|
-
if (isNextJS) {
|
|
358
|
-
// Next.js specific logic (mostly handled in generateNextJS)
|
|
359
|
-
const middleware = `import { authMiddleware } from '@clerk/nextjs';
|
|
360
|
-
|
|
361
|
-
export default authMiddleware({
|
|
362
|
-
publicRoutes: ['/'],
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
export const config = {
|
|
366
|
-
matcher: ['/((?!.+\\\\.[\\\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
|
|
367
|
-
};
|
|
368
|
-
`;
|
|
369
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'middleware.ts'), middleware);
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
// React+Vite specific logic
|
|
373
|
-
const hook = `import { useAuth, useUser, SignInButton, SignOutButton } from "@clerk/clerk-react";
|
|
374
|
-
|
|
375
|
-
export function AuthStatus() {
|
|
376
|
-
const { isSignedIn, user } = useUser();
|
|
377
|
-
if (isSignedIn) return <div>Hello {user.fullName} <SignOutButton /></div>;
|
|
378
|
-
return <SignInButton />;
|
|
379
|
-
}
|
|
380
|
-
`;
|
|
381
|
-
await fs_extra_1.default.ensureDir(path_1.default.join(frontendDir, 'src', 'components'));
|
|
382
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'src', 'components', 'AuthStatus.tsx'), hook);
|
|
383
|
-
}
|
|
273
|
+
await fs.writeFile(path.join(frontendDir, 'CLERK_SETUP.md'), '# Clerk Setup');
|
|
384
274
|
}
|
|
385
275
|
async function generateSupabaseAuth(config, frontendDir, _backendDir) {
|
|
386
|
-
|
|
387
|
-
const supabaseSetup = `# Supabase Authentication Setup
|
|
388
|
-
|
|
389
|
-
1. Create a Supabase project at https://supabase.com
|
|
390
|
-
2. Copy your project URL and anon key to .env
|
|
391
|
-
3. Configure auth providers in Supabase dashboard
|
|
392
|
-
4. For ${isNextJS ? 'Next.js' : 'React'}, the clients are already configured in src/lib/supabase.ts.
|
|
393
|
-
`;
|
|
394
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'SUPABASE_SETUP.md'), supabaseSetup);
|
|
395
|
-
const supabaseClient = isNextJS
|
|
396
|
-
? `import { createBrowserClient } from '@supabase/ssr'
|
|
397
|
-
|
|
398
|
-
export function createClient() {
|
|
399
|
-
return createBrowserClient(
|
|
400
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
401
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
402
|
-
)
|
|
403
|
-
}`
|
|
404
|
-
: `import { createClient } from '@supabase/supabase-js'
|
|
405
|
-
|
|
406
|
-
export const supabase = createClient(
|
|
407
|
-
process.env.VITE_SUPABASE_URL!,
|
|
408
|
-
process.env.VITE_SUPABASE_ANON_KEY!
|
|
409
|
-
)`;
|
|
410
|
-
const libDir = path_1.default.join(frontendDir, isNextJS ? 'lib' : 'src/lib');
|
|
411
|
-
await fs_extra_1.default.ensureDir(libDir);
|
|
412
|
-
await fs_extra_1.default.writeFile(path_1.default.join(libDir, 'supabase.ts'), supabaseClient);
|
|
276
|
+
await fs.writeFile(path.join(frontendDir, 'SUPABASE_SETUP.md'), '# Supabase Setup');
|
|
413
277
|
}
|
|
414
278
|
async function generateAuthJS(config, frontendDir, _backendDir) {
|
|
415
|
-
|
|
416
|
-
const authJSSetup = `# Auth.js (NextAuth) Setup
|
|
417
|
-
|
|
418
|
-
1. Configure providers in ${isNextJS ? 'app/api/auth/[...nextauth]/route.ts' : 'src/lib/auth.ts'}
|
|
419
|
-
2. Add provider credentials to .env
|
|
420
|
-
3. See https://authjs.dev for setup guides.
|
|
421
|
-
`;
|
|
422
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'AUTHJS_SETUP.md'), authJSSetup);
|
|
423
|
-
if (isNextJS) {
|
|
424
|
-
const authRouteDir = path_1.default.join(frontendDir, 'app/api/auth/[...nextauth]');
|
|
425
|
-
await fs_extra_1.default.ensureDir(authRouteDir);
|
|
426
|
-
const authRoute = `import NextAuth from "next-auth"
|
|
427
|
-
import GithubProvider from "next-auth/providers/github"
|
|
428
|
-
|
|
429
|
-
export const authOptions = {
|
|
430
|
-
providers: [
|
|
431
|
-
GithubProvider({
|
|
432
|
-
clientId: process.env.GITHUB_ID!,
|
|
433
|
-
clientSecret: process.env.GITHUB_SECRET!,
|
|
434
|
-
}),
|
|
435
|
-
],
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const handler = NextAuth(authOptions)
|
|
439
|
-
export { handler as GET, handler as POST }
|
|
440
|
-
`;
|
|
441
|
-
await fs_extra_1.default.writeFile(path_1.default.join(authRouteDir, 'route.ts'), authRoute);
|
|
442
|
-
}
|
|
279
|
+
await fs.writeFile(path.join(frontendDir, 'AUTHJS_SETUP.md'), '# Auth.js Setup');
|
|
443
280
|
}
|
|
444
281
|
async function generateFirebaseAuth(config, frontendDir, _backendDir) {
|
|
445
|
-
|
|
446
|
-
const firebaseSetup = `# Firebase Auth Setup
|
|
447
|
-
|
|
448
|
-
1. Create a Firebase project at https://firebase.google.com
|
|
449
|
-
2. Enable authentication methods
|
|
450
|
-
3. Copy your project config to .env
|
|
451
|
-
`;
|
|
452
|
-
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'FIREBASE_SETUP.md'), firebaseSetup);
|
|
453
|
-
const firebaseConfig = `import { initializeApp } from "firebase/app";
|
|
454
|
-
import { getAuth } from "firebase/auth";
|
|
455
|
-
|
|
456
|
-
const firebaseConfig = {
|
|
457
|
-
apiKey: process.env.${isNextJS ? 'NEXT_PUBLIC_' : 'VITE_'}FIREBASE_API_KEY,
|
|
458
|
-
authDomain: process.env.${isNextJS ? 'NEXT_PUBLIC_' : 'VITE_'}FIREBASE_AUTH_DOMAIN,
|
|
459
|
-
projectId: process.env.${isNextJS ? 'NEXT_PUBLIC_' : 'VITE_'}FIREBASE_PROJECT_ID,
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
const app = initializeApp(firebaseConfig);
|
|
463
|
-
export const auth = getAuth(app);
|
|
464
|
-
`;
|
|
465
|
-
const libDir = path_1.default.join(frontendDir, isNextJS ? 'lib' : 'src/lib');
|
|
466
|
-
await fs_extra_1.default.ensureDir(libDir);
|
|
467
|
-
await fs_extra_1.default.writeFile(path_1.default.join(libDir, 'firebase.ts'), firebaseConfig);
|
|
282
|
+
await fs.writeFile(path.join(frontendDir, 'FIREBASE_SETUP.md'), '# Firebase Setup');
|
|
468
283
|
}
|
|
469
284
|
//# sourceMappingURL=auth.js.map
|