forgestack-os-cli 0.1.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 (82) hide show
  1. package/dist/commands/create.d.ts +1 -0
  2. package/dist/commands/create.d.ts.map +1 -0
  3. package/dist/commands/create.js +78 -0
  4. package/dist/commands/create.js.map +1 -0
  5. package/dist/generators/api.d.ts +3 -0
  6. package/dist/generators/api.d.ts.map +1 -0
  7. package/dist/generators/api.js +346 -0
  8. package/dist/generators/api.js.map +1 -0
  9. package/dist/generators/auth.d.ts +2 -0
  10. package/dist/generators/auth.d.ts.map +1 -0
  11. package/dist/generators/auth.js +371 -0
  12. package/dist/generators/auth.js.map +1 -0
  13. package/dist/generators/backend.d.ts +2 -0
  14. package/dist/generators/backend.d.ts.map +1 -0
  15. package/dist/generators/backend.js +875 -0
  16. package/dist/generators/backend.js.map +1 -0
  17. package/dist/generators/common.d.ts +2 -0
  18. package/dist/generators/common.d.ts.map +1 -0
  19. package/dist/generators/common.js +354 -0
  20. package/dist/generators/common.js.map +1 -0
  21. package/dist/generators/database.d.ts +2 -0
  22. package/dist/generators/database.d.ts.map +1 -0
  23. package/dist/generators/database.js +157 -0
  24. package/dist/generators/database.js.map +1 -0
  25. package/dist/generators/docker.d.ts +2 -0
  26. package/dist/generators/docker.d.ts.map +1 -0
  27. package/dist/generators/docker.js +181 -0
  28. package/dist/generators/docker.js.map +1 -0
  29. package/dist/generators/frontend-helpers.d.ts +3 -0
  30. package/dist/generators/frontend-helpers.d.ts.map +1 -0
  31. package/dist/generators/frontend-helpers.js +23 -0
  32. package/dist/generators/frontend-helpers.js.map +1 -0
  33. package/dist/generators/frontend.d.ts +2 -0
  34. package/dist/generators/frontend.d.ts.map +1 -0
  35. package/dist/generators/frontend.js +735 -0
  36. package/dist/generators/frontend.js.map +1 -0
  37. package/dist/generators/index.d.ts +2 -0
  38. package/dist/generators/index.d.ts.map +1 -0
  39. package/dist/generators/index.js +59 -0
  40. package/dist/generators/index.js.map +1 -0
  41. package/dist/generators/nextjs-helpers.d.ts +6 -0
  42. package/dist/generators/nextjs-helpers.d.ts.map +1 -0
  43. package/dist/generators/nextjs-helpers.js +216 -0
  44. package/dist/generators/nextjs-helpers.js.map +1 -0
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +24 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/types.d.ts +15 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +3 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/utils/logger.d.ts +9 -0
  54. package/dist/utils/logger.d.ts.map +1 -0
  55. package/dist/utils/logger.js +32 -0
  56. package/dist/utils/logger.js.map +1 -0
  57. package/dist/utils/prompts.d.ts +2 -0
  58. package/dist/utils/prompts.d.ts.map +1 -0
  59. package/dist/utils/prompts.js +107 -0
  60. package/dist/utils/prompts.js.map +1 -0
  61. package/dist/utils/validators.d.ts +2 -0
  62. package/dist/utils/validators.d.ts.map +1 -0
  63. package/dist/utils/validators.js +48 -0
  64. package/dist/utils/validators.js.map +1 -0
  65. package/package.json +49 -0
  66. package/src/commands/create.ts +82 -0
  67. package/src/generators/api.ts +353 -0
  68. package/src/generators/auth.ts +406 -0
  69. package/src/generators/backend.ts +927 -0
  70. package/src/generators/common.ts +377 -0
  71. package/src/generators/database.ts +165 -0
  72. package/src/generators/docker.ts +185 -0
  73. package/src/generators/frontend.ts +783 -0
  74. package/src/generators/index.ts +64 -0
  75. package/src/index.ts +27 -0
  76. package/src/types.ts +16 -0
  77. package/src/utils/logger.ts +31 -0
  78. package/src/utils/prompts.ts +105 -0
  79. package/src/utils/validators.ts +50 -0
  80. package/tests/validators.test.ts +69 -0
  81. package/tsc_output.txt +0 -0
  82. package/tsconfig.json +21 -0
@@ -0,0 +1,406 @@
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
+ }