forgestack-os-cli 0.2.0 → 0.3.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.
- package/dist/commands/create.d.ts +1 -1
- package/dist/commands/create.js +7 -9
- package/dist/commands/create.js.map +1 -1
- package/dist/generators/api.js +16 -5
- package/dist/generators/api.js.map +1 -1
- package/dist/generators/auth.js +168 -347
- package/dist/generators/auth.js.map +1 -1
- package/dist/generators/backend.js +117 -98
- package/dist/generators/backend.js.map +1 -1
- package/dist/generators/common.js +5 -2
- package/dist/generators/common.js.map +1 -1
- package/dist/generators/docker.js +7 -5
- package/dist/generators/docker.js.map +1 -1
- package/dist/generators/frontend.js +145 -133
- package/dist/generators/frontend.js.map +1 -1
- package/dist/generators/index.js +41 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/utils/prompts.d.ts +1 -1
- package/dist/utils/prompts.js +5 -3
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/security.d.ts +4 -0
- package/dist/utils/security.js +14 -0
- package/dist/utils/security.js.map +1 -0
- package/package.json +12 -12
package/dist/generators/auth.js
CHANGED
|
@@ -26,11 +26,62 @@ async function generateAuth(config, frontendDir, backendDir) {
|
|
|
26
26
|
default:
|
|
27
27
|
throw new Error(`Unsupported auth: ${config.auth}`);
|
|
28
28
|
}
|
|
29
|
+
// Ensure frontend pages exist for all auth types
|
|
30
|
+
await generateFrontendAuthPages(config, frontendDir);
|
|
29
31
|
}
|
|
30
|
-
async function generateJWTAuth(config,
|
|
31
|
-
// Backend: Auth
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
async function generateJWTAuth(config, _frontendDir, backendDir) {
|
|
33
|
+
// Backend: Auth logic
|
|
34
|
+
if (config.backend === 'express') {
|
|
35
|
+
const routesDir = path_1.default.join(backendDir, 'src', 'routes');
|
|
36
|
+
await fs_extra_1.default.ensureDir(routesDir);
|
|
37
|
+
await fs_extra_1.default.writeFile(path_1.default.join(routesDir, 'auth.ts'), getExpressAuthRoutes(config));
|
|
38
|
+
}
|
|
39
|
+
else if (config.backend === 'fastify') {
|
|
40
|
+
const routesDir = path_1.default.join(backendDir, 'src', 'routes');
|
|
41
|
+
await fs_extra_1.default.ensureDir(routesDir);
|
|
42
|
+
await fs_extra_1.default.writeFile(path_1.default.join(routesDir, 'auth.ts'), getFastifyAuthRoutes(config));
|
|
43
|
+
}
|
|
44
|
+
else if (config.backend === 'nestjs') {
|
|
45
|
+
await generateNestJSAuth(config, backendDir);
|
|
46
|
+
}
|
|
47
|
+
// Isolation: Add .eslintrc.json to prevent parent config inheritance
|
|
48
|
+
const eslintConfig = {
|
|
49
|
+
root: true,
|
|
50
|
+
extends: ["eslint:recommended"],
|
|
51
|
+
env: { node: true, es2021: true }
|
|
52
|
+
};
|
|
53
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendDir, '.eslintrc.json'), eslintConfig, { spaces: 2 });
|
|
54
|
+
}
|
|
55
|
+
async function generateFrontendAuthPages(config, frontendDir) {
|
|
56
|
+
const isNext = config.frontend === 'nextjs';
|
|
57
|
+
const frontendLibDir = path_1.default.join(frontendDir, isNext ? 'app/lib' : 'src/lib');
|
|
58
|
+
await fs_extra_1.default.ensureDir(frontendLibDir);
|
|
59
|
+
// Always generate auth context file for JWT-based auth
|
|
60
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendLibDir, 'auth.tsx'), getAuthContextCode());
|
|
61
|
+
if (isNext) {
|
|
62
|
+
const nextEslint = {
|
|
63
|
+
root: true,
|
|
64
|
+
extends: ["next/core-web-vitals"]
|
|
65
|
+
};
|
|
66
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(frontendDir, '.eslintrc.json'), nextEslint, { spaces: 2 });
|
|
67
|
+
const authAppDir = path_1.default.join(frontendDir, 'app');
|
|
68
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(authAppDir, 'login'));
|
|
69
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(authAppDir, 'dashboard'));
|
|
70
|
+
await fs_extra_1.default.writeFile(path_1.default.join(authAppDir, 'login', 'page.tsx'), getLoginPageCode(config));
|
|
71
|
+
await fs_extra_1.default.writeFile(path_1.default.join(authAppDir, 'dashboard', 'page.tsx'), getDashboardPageCode(config));
|
|
72
|
+
await fs_extra_1.default.writeFile(path_1.default.join(authAppDir, 'page.tsx'), getHomePageCode(config));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const pagesDir = path_1.default.join(frontendDir, 'src', 'pages');
|
|
76
|
+
await fs_extra_1.default.ensureDir(pagesDir);
|
|
77
|
+
await fs_extra_1.default.writeFile(path_1.default.join(pagesDir, 'LoginPage.tsx'), getLoginPageCode(config));
|
|
78
|
+
await fs_extra_1.default.writeFile(path_1.default.join(pagesDir, 'HomePage.tsx'), getHomePageCode(config));
|
|
79
|
+
await fs_extra_1.default.writeFile(path_1.default.join(pagesDir, 'DashboardPage.tsx'), getDashboardPageCode(config));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function getExpressAuthRoutes(config) {
|
|
83
|
+
return `import { Router } from 'express';
|
|
84
|
+
import bcrypt from 'bcryptjs';
|
|
34
85
|
import jwt from 'jsonwebtoken';
|
|
35
86
|
import { z } from 'zod';
|
|
36
87
|
${config.database === 'mongodb' ? "import User from '../models/User';" : "import prisma from '../lib/prisma';"}
|
|
@@ -41,128 +92,108 @@ const registerSchema = z.object({
|
|
|
41
92
|
email: z.string().email(),
|
|
42
93
|
password: z.string().min(8),
|
|
43
94
|
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
95
|
});
|
|
51
96
|
|
|
52
97
|
router.post('/register', async (req, res, next) => {
|
|
53
98
|
try {
|
|
54
99
|
const data = registerSchema.parse(req.body);
|
|
55
|
-
|
|
56
100
|
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
|
-
|
|
101
|
+
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 } })`};
|
|
102
|
+
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET || '${config.jwtSecret}', { expiresIn: '7d' });
|
|
86
103
|
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
|
|
87
|
-
} catch (error) {
|
|
88
|
-
next(error);
|
|
89
|
-
}
|
|
104
|
+
} catch (error) { next(error); }
|
|
90
105
|
});
|
|
91
106
|
|
|
92
107
|
router.post('/login', async (req, res, next) => {
|
|
93
108
|
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
|
-
|
|
109
|
+
const { email, password } = req.body;
|
|
110
|
+
const user = ${config.database === 'mongodb' ? `await User.findOne({ email })` : `await prisma.user.findUnique({ where: { email } })`};
|
|
111
|
+
if (!user || !(await bcrypt.compare(password, user.password))) return res.status(401).json({ error: 'Invalid credentials' });
|
|
112
|
+
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET || '${config.jwtSecret}', { expiresIn: '7d' });
|
|
124
113
|
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
|
|
125
|
-
} catch (error) {
|
|
126
|
-
next(error);
|
|
127
|
-
}
|
|
114
|
+
} catch (error) { next(error); }
|
|
128
115
|
});
|
|
129
116
|
|
|
130
117
|
export default router;
|
|
131
118
|
`;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
import api from '../lib/api';
|
|
119
|
+
}
|
|
120
|
+
function getFastifyAuthRoutes(config) {
|
|
121
|
+
return `import { FastifyInstance } from 'fastify';
|
|
122
|
+
import bcrypt from 'bcrypt';
|
|
137
123
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
export default async function authRoutes(fastify: FastifyInstance) {
|
|
125
|
+
fastify.post('/register', async (request, reply) => {
|
|
126
|
+
reply.send({ success: true });
|
|
127
|
+
});
|
|
128
|
+
fastify.post('/login', async (request, reply) => {
|
|
129
|
+
reply.send({ token: '...' });
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
async function generateNestJSAuth(config, backendDir) {
|
|
135
|
+
const authDir = path_1.default.join(backendDir, 'src', 'auth');
|
|
136
|
+
await fs_extra_1.default.ensureDir(authDir);
|
|
137
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(authDir, 'dto'));
|
|
138
|
+
const controller = `import { Controller, Post, Body } from '@nestjs/common';
|
|
139
|
+
import { AuthService } from './auth.service';
|
|
140
|
+
import { RegisterDto, LoginDto } from './dto/auth.dto';
|
|
141
|
+
|
|
142
|
+
@Controller('auth')
|
|
143
|
+
export class AuthController {
|
|
144
|
+
constructor(private authService: AuthService) {}
|
|
145
|
+
@Post('register')
|
|
146
|
+
async register(@Body() dto: RegisterDto) { return this.authService.register(dto); }
|
|
147
|
+
@Post('login')
|
|
148
|
+
async login(@Body() dto: LoginDto) { return this.authService.login(dto); }
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
await fs_extra_1.default.writeFile(path_1.default.join(authDir, 'auth.controller.ts'), controller);
|
|
152
|
+
const dto = `import { IsEmail, IsString, MinLength } from 'class-validator';
|
|
153
|
+
export class RegisterDto {
|
|
154
|
+
@IsEmail()
|
|
155
|
+
email!: string;
|
|
156
|
+
@IsString()
|
|
157
|
+
@MinLength(8)
|
|
158
|
+
password!: string;
|
|
159
|
+
@IsString()
|
|
141
160
|
name?: string;
|
|
142
161
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
logout: () => void;
|
|
149
|
-
isLoading: boolean;
|
|
162
|
+
export class LoginDto {
|
|
163
|
+
@IsEmail()
|
|
164
|
+
email!: string;
|
|
165
|
+
@IsString()
|
|
166
|
+
password!: string;
|
|
150
167
|
}
|
|
168
|
+
`;
|
|
169
|
+
await fs_extra_1.default.writeFile(path_1.default.join(authDir, 'dto/auth.dto.ts'), dto);
|
|
170
|
+
const service = `import { Injectable } from '@nestjs/common';
|
|
171
|
+
import { JwtService } from '@nestjs/jwt';
|
|
172
|
+
|
|
173
|
+
@Injectable()
|
|
174
|
+
export class AuthService {
|
|
175
|
+
constructor(private jwtService: JwtService) {}
|
|
176
|
+
async register(dto: any) { return { success: true }; }
|
|
177
|
+
async login(user: any) { return { token: this.jwtService.sign({ id: '1' }) }; }
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
await fs_extra_1.default.writeFile(path_1.default.join(authDir, 'auth.service.ts'), service);
|
|
181
|
+
}
|
|
182
|
+
function getAuthContextCode() {
|
|
183
|
+
return `'use client';
|
|
184
|
+
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
185
|
+
import api from '../lib/api';
|
|
151
186
|
|
|
152
|
-
const AuthContext = createContext<
|
|
187
|
+
const AuthContext = createContext<any>(undefined);
|
|
153
188
|
|
|
154
189
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
155
|
-
const [user, setUser] = useState
|
|
190
|
+
const [user, setUser] = useState(null);
|
|
156
191
|
const [isLoading, setIsLoading] = useState(true);
|
|
157
192
|
|
|
158
193
|
useEffect(() => {
|
|
159
194
|
const token = localStorage.getItem('token');
|
|
160
195
|
if (token) {
|
|
161
|
-
|
|
162
|
-
api.get('/auth/me')
|
|
163
|
-
.then(res => setUser(res.data.user))
|
|
164
|
-
.catch(() => localStorage.removeItem('token'))
|
|
165
|
-
.finally(() => setIsLoading(false));
|
|
196
|
+
api.get('/auth/me').then(res => setUser(res.data.user)).finally(() => setIsLoading(false));
|
|
166
197
|
} else {
|
|
167
198
|
setIsLoading(false);
|
|
168
199
|
}
|
|
@@ -174,296 +205,86 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
174
205
|
setUser(res.data.user);
|
|
175
206
|
};
|
|
176
207
|
|
|
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
208
|
const logout = () => {
|
|
184
209
|
localStorage.removeItem('token');
|
|
185
210
|
setUser(null);
|
|
186
211
|
};
|
|
187
212
|
|
|
188
|
-
return
|
|
189
|
-
<AuthContext.Provider value={{ user, login, register, logout, isLoading }}>
|
|
190
|
-
{children}
|
|
191
|
-
</AuthContext.Provider>
|
|
192
|
-
);
|
|
213
|
+
return <AuthContext.Provider value={{ user, login, logout, isLoading }}>{children}</AuthContext.Provider>;
|
|
193
214
|
}
|
|
194
215
|
|
|
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
|
-
}
|
|
216
|
+
export const useAuth = () => useContext(AuthContext);
|
|
202
217
|
`;
|
|
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';
|
|
218
|
+
}
|
|
219
|
+
function getLoginPageCode(config) {
|
|
220
|
+
const isNext = config.frontend === 'nextjs';
|
|
221
|
+
const authPath = isNext ? '../lib/auth' : '../lib/auth';
|
|
222
|
+
return `${isNext ? "'use client';" : ""}
|
|
223
|
+
import { useState } from 'react';
|
|
224
|
+
${isNext ? "import { useRouter } from 'next/navigation';" : "import { useNavigate } from 'react-router-dom';"}
|
|
225
|
+
import { useAuth } from '${authPath}';
|
|
217
226
|
|
|
218
227
|
export default function LoginPage() {
|
|
219
228
|
const [email, setEmail] = useState('');
|
|
220
229
|
const [password, setPassword] = useState('');
|
|
221
|
-
const [error, setError] = useState('');
|
|
222
230
|
const { login } = useAuth();
|
|
223
|
-
const navigate = useNavigate();
|
|
231
|
+
${isNext ? "const router = useRouter();" : "const navigate = useNavigate();"}
|
|
224
232
|
|
|
225
|
-
const handleSubmit = async (e:
|
|
233
|
+
const handleSubmit = async (e: any) => {
|
|
226
234
|
e.preventDefault();
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
navigate('/dashboard');
|
|
230
|
-
} catch (err: any) {
|
|
231
|
-
setError(err.response?.data?.error || 'Login failed');
|
|
232
|
-
}
|
|
235
|
+
await login(email, password);
|
|
236
|
+
${isNext ? "router.push('/dashboard');" : "navigate('/dashboard');"}
|
|
233
237
|
};
|
|
234
238
|
|
|
235
239
|
return (
|
|
236
240
|
<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>
|
|
241
|
+
<form onSubmit={handleSubmit} className="bg-gray-800 p-8 rounded shadow-lg">
|
|
242
|
+
<input type="email" value={email} onChange={e => setEmail(e.target.value)} className="block w-full mb-4 p-2 bg-gray-700 text-white" />
|
|
243
|
+
<input type="password" value={password} onChange={e => setPassword(e.target.value)} className="block w-full mb-4 p-2 bg-gray-700 text-white" />
|
|
244
|
+
<button type="submit" className="w-full bg-blue-600 p-2 text-white">Login</button>
|
|
245
|
+
</form>
|
|
269
246
|
</div>
|
|
270
247
|
);
|
|
271
248
|
}
|
|
272
249
|
`;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const homePage = `export default function HomePage() {
|
|
250
|
+
}
|
|
251
|
+
function getHomePageCode(config) {
|
|
252
|
+
return `export default function HomePage() {
|
|
277
253
|
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>
|
|
254
|
+
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
|
|
255
|
+
<h1 className="text-4xl">${config.projectName}</h1>
|
|
293
256
|
</div>
|
|
294
257
|
);
|
|
295
258
|
}
|
|
296
259
|
`;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
260
|
+
}
|
|
261
|
+
function getDashboardPageCode(config) {
|
|
262
|
+
const isNext = config.frontend === 'nextjs';
|
|
263
|
+
const authPath = isNext ? '../lib/auth' : '../lib/auth';
|
|
264
|
+
return `${isNext ? "'use client';" : ""}
|
|
265
|
+
import { useAuth } from '${authPath}';
|
|
303
266
|
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
|
-
|
|
267
|
+
const { user, logout } = useAuth();
|
|
319
268
|
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>
|
|
269
|
+
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
270
|
+
<h1>Dashboard for ${config.projectName}</h1>
|
|
271
|
+
<p>Welcome, {user?.email}</p>
|
|
272
|
+
<button onClick={logout} className="bg-red-600 p-2 rounded">Logout</button>
|
|
336
273
|
</div>
|
|
337
274
|
);
|
|
338
275
|
}
|
|
339
276
|
`;
|
|
340
|
-
await fs_extra_1.default.writeFile(path_1.default.join(pagesDir, 'DashboardPage.tsx'), dashboardPage);
|
|
341
277
|
}
|
|
342
278
|
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
|
-
}
|
|
279
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'CLERK_SETUP.md'), '# Clerk Setup');
|
|
384
280
|
}
|
|
385
281
|
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);
|
|
282
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'SUPABASE_SETUP.md'), '# Supabase Setup');
|
|
413
283
|
}
|
|
414
284
|
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
|
-
}
|
|
285
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'AUTHJS_SETUP.md'), '# Auth.js Setup');
|
|
443
286
|
}
|
|
444
287
|
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);
|
|
288
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'FIREBASE_SETUP.md'), '# Firebase Setup');
|
|
468
289
|
}
|
|
469
290
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/generators/auth.ts"],"names":[],"mappings":";;;;;AAIA,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/generators/auth.ts"],"names":[],"mappings":"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"}
|