forgestack-os-cli 0.1.0 → 0.1.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/dist/commands/create.js +14 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/generators/auth.js +121 -23
- package/dist/generators/auth.js.map +1 -1
- package/dist/generators/backend.js +160 -9
- package/dist/generators/backend.js.map +1 -1
- package/dist/generators/common.js +4 -1
- package/dist/generators/common.js.map +1 -1
- package/dist/generators/database.js +20 -14
- package/dist/generators/database.js.map +1 -1
- package/dist/generators/docker.js +14 -4
- package/dist/generators/docker.js.map +1 -1
- package/dist/utils/prompts.js +62 -7
- package/dist/utils/prompts.js.map +1 -1
- package/package.json +7 -2
- package/src/commands/create.ts +0 -82
- package/src/generators/api.ts +0 -353
- package/src/generators/auth.ts +0 -406
- package/src/generators/backend.ts +0 -927
- package/src/generators/common.ts +0 -377
- package/src/generators/database.ts +0 -165
- package/src/generators/docker.ts +0 -185
- package/src/generators/frontend.ts +0 -783
- package/src/generators/index.ts +0 -64
- package/src/index.ts +0 -27
- package/src/types.ts +0 -16
- package/src/utils/logger.ts +0 -31
- package/src/utils/prompts.ts +0 -105
- package/src/utils/validators.ts +0 -50
- package/tests/validators.test.ts +0 -69
- package/tsc_output.txt +0 -0
- package/tsconfig.json +0 -21
package/src/generators/auth.ts
DELETED
|
@@ -1,406 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import { StackConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function generateAuth(
|
|
6
|
-
config: StackConfig,
|
|
7
|
-
frontendDir: string,
|
|
8
|
-
backendDir: string
|
|
9
|
-
) {
|
|
10
|
-
switch (config.auth) {
|
|
11
|
-
case 'jwt':
|
|
12
|
-
await generateJWTAuth(config, frontendDir, backendDir);
|
|
13
|
-
break;
|
|
14
|
-
case 'clerk':
|
|
15
|
-
await generateClerkAuth(config, frontendDir, backendDir);
|
|
16
|
-
break;
|
|
17
|
-
case 'supabase':
|
|
18
|
-
await generateSupabaseAuth(config, frontendDir, backendDir);
|
|
19
|
-
break;
|
|
20
|
-
case 'authjs':
|
|
21
|
-
await generateAuthJS(config, frontendDir, backendDir);
|
|
22
|
-
break;
|
|
23
|
-
case 'firebase':
|
|
24
|
-
await generateFirebaseAuth(config, frontendDir, backendDir);
|
|
25
|
-
break;
|
|
26
|
-
default:
|
|
27
|
-
throw new Error(`Unsupported auth: ${config.auth}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function generateJWTAuth(
|
|
32
|
-
config: StackConfig,
|
|
33
|
-
frontendDir: string,
|
|
34
|
-
backendDir: string
|
|
35
|
-
) {
|
|
36
|
-
// Backend: Auth routes
|
|
37
|
-
const authRoutes = `import { Router } from 'express';
|
|
38
|
-
import bcrypt from 'bcrypt';
|
|
39
|
-
import jwt from 'jsonwebtoken';
|
|
40
|
-
import { z } from 'zod';
|
|
41
|
-
${config.database === 'mongodb' ? "import User from '../models/User';" : "import prisma from '../lib/prisma';"}
|
|
42
|
-
|
|
43
|
-
const router = Router();
|
|
44
|
-
|
|
45
|
-
const registerSchema = z.object({
|
|
46
|
-
email: z.string().email(),
|
|
47
|
-
password: z.string().min(8),
|
|
48
|
-
name: z.string().optional(),
|
|
49
|
-
${config.multiTenant ? 'tenantId: z.string(),' : ''}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const loginSchema = z.object({
|
|
53
|
-
email: z.string().email(),
|
|
54
|
-
password: z.string(),
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
router.post('/register', async (req, res, next) => {
|
|
58
|
-
try {
|
|
59
|
-
const data = registerSchema.parse(req.body);
|
|
60
|
-
|
|
61
|
-
const hashedPassword = await bcrypt.hash(data.password, 10);
|
|
62
|
-
|
|
63
|
-
${config.database === 'mongodb' ? `
|
|
64
|
-
const user = await User.create({
|
|
65
|
-
email: data.email,
|
|
66
|
-
password: hashedPassword,
|
|
67
|
-
name: data.name,
|
|
68
|
-
${config.multiTenant ? 'tenantId: data.tenantId,' : ''}
|
|
69
|
-
});
|
|
70
|
-
` : `
|
|
71
|
-
const user = await prisma.user.create({
|
|
72
|
-
data: {
|
|
73
|
-
email: data.email,
|
|
74
|
-
password: hashedPassword,
|
|
75
|
-
name: data.name,
|
|
76
|
-
${config.multiTenant ? 'tenantId: data.tenantId,' : ''}
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
`}
|
|
80
|
-
|
|
81
|
-
const token = jwt.sign(
|
|
82
|
-
{
|
|
83
|
-
userId: user.id,
|
|
84
|
-
email: user.email,
|
|
85
|
-
${config.multiTenant ? 'tenantId: user.tenantId,' : ''}
|
|
86
|
-
},
|
|
87
|
-
process.env.JWT_SECRET!,
|
|
88
|
-
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
|
|
92
|
-
} catch (error) {
|
|
93
|
-
next(error);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
router.post('/login', async (req, res, next) => {
|
|
98
|
-
try {
|
|
99
|
-
const data = loginSchema.parse(req.body);
|
|
100
|
-
|
|
101
|
-
${config.database === 'mongodb' ? `
|
|
102
|
-
const user = await User.findOne({ email: data.email });
|
|
103
|
-
` : `
|
|
104
|
-
const user = await prisma.user.findUnique({
|
|
105
|
-
where: { email: data.email },
|
|
106
|
-
});
|
|
107
|
-
`}
|
|
108
|
-
|
|
109
|
-
if (!user || !user.password) {
|
|
110
|
-
return res.status(401).json({ error: 'Invalid credentials' });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const validPassword = await bcrypt.compare(data.password, user.password);
|
|
114
|
-
|
|
115
|
-
if (!validPassword) {
|
|
116
|
-
return res.status(401).json({ error: 'Invalid credentials' });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const token = jwt.sign(
|
|
120
|
-
{
|
|
121
|
-
userId: user.id,
|
|
122
|
-
email: user.email,
|
|
123
|
-
${config.multiTenant ? 'tenantId: user.tenantId,' : ''}
|
|
124
|
-
},
|
|
125
|
-
process.env.JWT_SECRET!,
|
|
126
|
-
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
|
|
130
|
-
} catch (error) {
|
|
131
|
-
next(error);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
export default router;
|
|
136
|
-
`;
|
|
137
|
-
|
|
138
|
-
const routesDir = path.join(backendDir, 'src', 'routes');
|
|
139
|
-
await fs.writeFile(path.join(routesDir, 'auth.ts'), authRoutes);
|
|
140
|
-
|
|
141
|
-
// Frontend: Auth context
|
|
142
|
-
const authContext = `import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
143
|
-
import api from '../lib/api';
|
|
144
|
-
|
|
145
|
-
interface User {
|
|
146
|
-
id: string;
|
|
147
|
-
email: string;
|
|
148
|
-
name?: string;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
interface AuthContextType {
|
|
152
|
-
user: User | null;
|
|
153
|
-
login: (email: string, password: string) => Promise<void>;
|
|
154
|
-
register: (email: string, password: string, name?: string) => Promise<void>;
|
|
155
|
-
logout: () => void;
|
|
156
|
-
isLoading: boolean;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
160
|
-
|
|
161
|
-
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
162
|
-
const [user, setUser] = useState<User | null>(null);
|
|
163
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
164
|
-
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
const token = localStorage.getItem('token');
|
|
167
|
-
if (token) {
|
|
168
|
-
// Verify token and get user
|
|
169
|
-
api.get('/auth/me')
|
|
170
|
-
.then(res => setUser(res.data.user))
|
|
171
|
-
.catch(() => localStorage.removeItem('token'))
|
|
172
|
-
.finally(() => setIsLoading(false));
|
|
173
|
-
} else {
|
|
174
|
-
setIsLoading(false);
|
|
175
|
-
}
|
|
176
|
-
}, []);
|
|
177
|
-
|
|
178
|
-
const login = async (email: string, password: string) => {
|
|
179
|
-
const res = await api.post('/auth/login', { email, password });
|
|
180
|
-
localStorage.setItem('token', res.data.token);
|
|
181
|
-
setUser(res.data.user);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const register = async (email: string, password: string, name?: string) => {
|
|
185
|
-
const res = await api.post('/auth/register', { email, password, name });
|
|
186
|
-
localStorage.setItem('token', res.data.token);
|
|
187
|
-
setUser(res.data.user);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const logout = () => {
|
|
191
|
-
localStorage.removeItem('token');
|
|
192
|
-
setUser(null);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<AuthContext.Provider value={{ user, login, register, logout, isLoading }}>
|
|
197
|
-
{children}
|
|
198
|
-
</AuthContext.Provider>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function useAuth() {
|
|
203
|
-
const context = useContext(AuthContext);
|
|
204
|
-
if (!context) {
|
|
205
|
-
throw new Error('useAuth must be used within AuthProvider');
|
|
206
|
-
}
|
|
207
|
-
return context;
|
|
208
|
-
}
|
|
209
|
-
`;
|
|
210
|
-
|
|
211
|
-
const frontendLibDir = path.join(frontendDir, 'src', 'lib');
|
|
212
|
-
await fs.writeFile(path.join(frontendLibDir, 'auth.tsx'), authContext);
|
|
213
|
-
|
|
214
|
-
// Frontend: Login page
|
|
215
|
-
const loginPage = `import { useState } from 'react';
|
|
216
|
-
import { useNavigate } from 'react-router-dom';
|
|
217
|
-
import { useAuth } from '../lib/auth';
|
|
218
|
-
|
|
219
|
-
export default function LoginPage() {
|
|
220
|
-
const [email, setEmail] = useState('');
|
|
221
|
-
const [password, setPassword] = useState('');
|
|
222
|
-
const [error, setError] = useState('');
|
|
223
|
-
const { login } = useAuth();
|
|
224
|
-
const navigate = useNavigate();
|
|
225
|
-
|
|
226
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
227
|
-
e.preventDefault();
|
|
228
|
-
try {
|
|
229
|
-
await login(email, password);
|
|
230
|
-
navigate('/dashboard');
|
|
231
|
-
} catch (err: any) {
|
|
232
|
-
setError(err.response?.data?.error || 'Login failed');
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
return (
|
|
237
|
-
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
|
238
|
-
<div className="bg-gray-800 p-8 rounded-lg shadow-lg w-full max-w-md">
|
|
239
|
-
<h1 className="text-3xl font-bold text-white mb-6">Login</h1>
|
|
240
|
-
{error && <div className="bg-red-500 text-white p-3 rounded mb-4">{error}</div>}
|
|
241
|
-
<form onSubmit={handleSubmit}>
|
|
242
|
-
<div className="mb-4">
|
|
243
|
-
<label className="block text-gray-300 mb-2">Email</label>
|
|
244
|
-
<input
|
|
245
|
-
type="email"
|
|
246
|
-
value={email}
|
|
247
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
248
|
-
className="w-full p-3 rounded bg-gray-700 text-white"
|
|
249
|
-
required
|
|
250
|
-
/>
|
|
251
|
-
</div>
|
|
252
|
-
<div className="mb-6">
|
|
253
|
-
<label className="block text-gray-300 mb-2">Password</label>
|
|
254
|
-
<input
|
|
255
|
-
type="password"
|
|
256
|
-
value={password}
|
|
257
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
258
|
-
className="w-full p-3 rounded bg-gray-700 text-white"
|
|
259
|
-
required
|
|
260
|
-
/>
|
|
261
|
-
</div>
|
|
262
|
-
<button
|
|
263
|
-
type="submit"
|
|
264
|
-
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 rounded"
|
|
265
|
-
>
|
|
266
|
-
Login
|
|
267
|
-
</button>
|
|
268
|
-
</form>
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
`;
|
|
274
|
-
|
|
275
|
-
const pagesDir = path.join(frontendDir, 'src', 'pages');
|
|
276
|
-
await fs.writeFile(path.join(pagesDir, 'LoginPage.tsx'), loginPage);
|
|
277
|
-
|
|
278
|
-
// Frontend: Home page
|
|
279
|
-
const homePage = `export default function HomePage() {
|
|
280
|
-
return (
|
|
281
|
-
<div className="min-h-screen bg-gray-900 text-white">
|
|
282
|
-
<div className="container mx-auto px-4 py-16">
|
|
283
|
-
<h1 className="text-5xl font-bold mb-4">${config.projectName}</h1>
|
|
284
|
-
<p className="text-xl text-gray-400 mb-8">
|
|
285
|
-
Generated by ForgeStack OS
|
|
286
|
-
</p>
|
|
287
|
-
<div className="space-x-4">
|
|
288
|
-
<a
|
|
289
|
-
href="/login"
|
|
290
|
-
className="inline-block bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded font-semibold"
|
|
291
|
-
>
|
|
292
|
-
Get Started
|
|
293
|
-
</a>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
`;
|
|
300
|
-
|
|
301
|
-
await fs.writeFile(path.join(pagesDir, 'HomePage.tsx'), homePage);
|
|
302
|
-
|
|
303
|
-
// Frontend: Dashboard page
|
|
304
|
-
const dashboardPage = `import { useAuth } from '../lib/auth';
|
|
305
|
-
import { useNavigate } from 'react-router-dom';
|
|
306
|
-
import { useEffect } from 'react';
|
|
307
|
-
|
|
308
|
-
export default function DashboardPage() {
|
|
309
|
-
const { user, logout, isLoading } = useAuth();
|
|
310
|
-
const navigate = useNavigate();
|
|
311
|
-
|
|
312
|
-
useEffect(() => {
|
|
313
|
-
if (!isLoading && !user) {
|
|
314
|
-
navigate('/login');
|
|
315
|
-
}
|
|
316
|
-
}, [user, isLoading, navigate]);
|
|
317
|
-
|
|
318
|
-
if (isLoading) {
|
|
319
|
-
return <div className="min-h-screen bg-gray-900 flex items-center justify-center">
|
|
320
|
-
<div className="text-white">Loading...</div>
|
|
321
|
-
</div>;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return (
|
|
325
|
-
<div className="min-h-screen bg-gray-900 text-white">
|
|
326
|
-
<nav className="bg-gray-800 p-4">
|
|
327
|
-
<div className="container mx-auto flex justify-between items-center">
|
|
328
|
-
<h1 className="text-2xl font-bold">${config.projectName}</h1>
|
|
329
|
-
<button
|
|
330
|
-
onClick={logout}
|
|
331
|
-
className="bg-red-600 hover:bg-red-700 px-4 py-2 rounded"
|
|
332
|
-
>
|
|
333
|
-
Logout
|
|
334
|
-
</button>
|
|
335
|
-
</div>
|
|
336
|
-
</nav>
|
|
337
|
-
<div className="container mx-auto px-4 py-16">
|
|
338
|
-
<h2 className="text-3xl font-bold mb-4">Welcome, {user?.name || user?.email}!</h2>
|
|
339
|
-
<p className="text-gray-400">You're logged in to your dashboard.</p>
|
|
340
|
-
</div>
|
|
341
|
-
</div>
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
`;
|
|
345
|
-
|
|
346
|
-
await fs.writeFile(path.join(pagesDir, 'DashboardPage.tsx'), dashboardPage);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async function generateClerkAuth(
|
|
350
|
-
config: StackConfig,
|
|
351
|
-
frontendDir: string,
|
|
352
|
-
backendDir: string
|
|
353
|
-
) {
|
|
354
|
-
// Placeholder for Clerk integration
|
|
355
|
-
// Will be implemented in Phase 2
|
|
356
|
-
const readme = `# Clerk Authentication
|
|
357
|
-
|
|
358
|
-
To complete Clerk setup:
|
|
359
|
-
|
|
360
|
-
1. Create a Clerk account at https://clerk.com
|
|
361
|
-
2. Create a new application
|
|
362
|
-
3. Copy your API keys to .env
|
|
363
|
-
4. Install Clerk SDK: npm install @clerk/clerk-react
|
|
364
|
-
5. Wrap your app with ClerkProvider
|
|
365
|
-
|
|
366
|
-
See Clerk documentation for details.
|
|
367
|
-
`;
|
|
368
|
-
|
|
369
|
-
await fs.writeFile(path.join(frontendDir, 'CLERK_SETUP.md'), readme);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function generateSupabaseAuth(
|
|
373
|
-
config: StackConfig,
|
|
374
|
-
frontendDir: string,
|
|
375
|
-
backendDir: string
|
|
376
|
-
) {
|
|
377
|
-
// Placeholder
|
|
378
|
-
const readme = `# Supabase Authentication
|
|
379
|
-
|
|
380
|
-
To complete Supabase Auth setup:
|
|
381
|
-
|
|
382
|
-
1. Create a Supabase project at https://supabase.com
|
|
383
|
-
2. Copy your project URL and anon key to .env
|
|
384
|
-
3. Configure auth providers in Supabase dashboard
|
|
385
|
-
|
|
386
|
-
See Supabase documentation for details.
|
|
387
|
-
`;
|
|
388
|
-
|
|
389
|
-
await fs.writeFile(path.join(frontendDir, 'SUPABASE_SETUP.md'), readme);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function generateAuthJS(
|
|
393
|
-
config: StackConfig,
|
|
394
|
-
frontendDir: string,
|
|
395
|
-
backendDir: string
|
|
396
|
-
) {
|
|
397
|
-
throw new Error('Auth.js support coming in Phase 2');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async function generateFirebaseAuth(
|
|
401
|
-
config: StackConfig,
|
|
402
|
-
frontendDir: string,
|
|
403
|
-
backendDir: string
|
|
404
|
-
) {
|
|
405
|
-
throw new Error('Firebase Auth support coming in Phase 2');
|
|
406
|
-
}
|