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,735 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateFrontend = generateFrontend;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ async function generateFrontend(config, frontendDir) {
10
+ // For Phase 1 MVP, we'll create a basic React + Vite template
11
+ // In later phases, we'll add support for other frameworks
12
+ switch (config.frontend) {
13
+ case 'react-vite':
14
+ await generateReactVite(config, frontendDir);
15
+ break;
16
+ case 'nextjs':
17
+ await generateNextJS(config, frontendDir);
18
+ break;
19
+ case 'vue-vite':
20
+ await generateVueVite(config, frontendDir);
21
+ break;
22
+ case 'sveltekit':
23
+ await generateSvelteKit(config, frontendDir);
24
+ break;
25
+ default:
26
+ throw new Error(`Unsupported frontend: ${config.frontend}`);
27
+ }
28
+ }
29
+ async function generateReactVite(config, frontendDir) {
30
+ // Package.json
31
+ const packageJson = {
32
+ name: `${config.projectName}-frontend`,
33
+ version: '0.1.0',
34
+ type: 'module',
35
+ scripts: {
36
+ dev: 'vite',
37
+ build: 'tsc && vite build',
38
+ preview: 'vite preview',
39
+ lint: 'eslint . --ext ts,tsx',
40
+ },
41
+ dependencies: {
42
+ react: '^18.2.0',
43
+ 'react-dom': '^18.2.0',
44
+ 'react-router-dom': '^6.21.3',
45
+ axios: '^1.6.5',
46
+ ...(config.auth === 'clerk' && { '@clerk/clerk-react': '^4.30.7' }),
47
+ ...(config.auth === 'supabase' && { '@supabase/supabase-js': '^2.39.3' }),
48
+ ...(config.auth === 'firebase' && { firebase: '^10.7.2' }),
49
+ },
50
+ devDependencies: {
51
+ '@types/react': '^18.2.48',
52
+ '@types/react-dom': '^18.2.18',
53
+ '@vitejs/plugin-react': '^4.2.1',
54
+ typescript: '^5.3.3',
55
+ vite: '^5.0.11',
56
+ 'tailwindcss': '^3.4.1',
57
+ 'autoprefixer': '^10.4.17',
58
+ 'postcss': '^8.4.33',
59
+ 'eslint': '^8.56.0',
60
+ '@typescript-eslint/eslint-plugin': '^6.19.0',
61
+ '@typescript-eslint/parser': '^6.19.0',
62
+ },
63
+ };
64
+ await fs_extra_1.default.writeJSON(path_1.default.join(frontendDir, 'package.json'), packageJson, { spaces: 2 });
65
+ // Vite config
66
+ const viteConfig = `import { defineConfig } from 'vite'
67
+ import react from '@vitejs/plugin-react'
68
+
69
+ export default defineConfig({
70
+ plugins: [react()],
71
+ server: {
72
+ port: 5173,
73
+ proxy: {
74
+ '/api': {
75
+ target: 'http://localhost:3000',
76
+ changeOrigin: true,
77
+ },
78
+ },
79
+ },
80
+ })
81
+ `;
82
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'vite.config.ts'), viteConfig);
83
+ // TypeScript config
84
+ const tsConfig = {
85
+ compilerOptions: {
86
+ target: 'ES2020',
87
+ useDefineForClassFields: true,
88
+ lib: ['ES2020', 'DOM', 'DOM.Iterable'],
89
+ module: 'ESNext',
90
+ skipLibCheck: true,
91
+ moduleResolution: 'bundler',
92
+ allowImportingTsExtensions: true,
93
+ resolveJsonModule: true,
94
+ isolatedModules: true,
95
+ noEmit: true,
96
+ jsx: 'react-jsx',
97
+ strict: true,
98
+ noUnusedLocals: true,
99
+ noUnusedParameters: true,
100
+ noFallthroughCasesInSwitch: true,
101
+ },
102
+ include: ['src'],
103
+ references: [{ path: './tsconfig.node.json' }],
104
+ };
105
+ await fs_extra_1.default.writeJSON(path_1.default.join(frontendDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
106
+ // Tailwind config
107
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
108
+ export default {
109
+ content: [
110
+ "./index.html",
111
+ "./src/**/*.{js,ts,jsx,tsx}",
112
+ ],
113
+ theme: {
114
+ extend: {},
115
+ },
116
+ plugins: [],
117
+ }
118
+ `;
119
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'tailwind.config.js'), tailwindConfig);
120
+ // PostCSS config
121
+ const postcssConfig = `export default {
122
+ plugins: {
123
+ tailwindcss: {},
124
+ autoprefixer: {},
125
+ },
126
+ }
127
+ `;
128
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'postcss.config.js'), postcssConfig);
129
+ // Create src directory structure
130
+ const srcDir = path_1.default.join(frontendDir, 'src');
131
+ await fs_extra_1.default.ensureDir(srcDir);
132
+ await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'components'));
133
+ await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'pages'));
134
+ await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'lib'));
135
+ await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'hooks'));
136
+ // index.html
137
+ const indexHtml = `<!doctype html>
138
+ <html lang="en">
139
+ <head>
140
+ <meta charset="UTF-8" />
141
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
142
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
143
+ <title>${config.projectName}</title>
144
+ </head>
145
+ <body>
146
+ <div id="root"></div>
147
+ <script type="module" src="/src/main.tsx"></script>
148
+ </body>
149
+ </html>
150
+ `;
151
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'index.html'), indexHtml);
152
+ // Main entry point
153
+ const mainTsx = `import React from 'react'
154
+ import ReactDOM from 'react-dom/client'
155
+ import App from './App.tsx'
156
+ import './index.css'
157
+
158
+ ReactDOM.createRoot(document.getElementById('root')!).render(
159
+ <React.StrictMode>
160
+ <App />
161
+ </React.StrictMode>,
162
+ )
163
+ `;
164
+ await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'main.tsx'), mainTsx);
165
+ // Main CSS
166
+ const indexCss = `@tailwind base;
167
+ @tailwind components;
168
+ @tailwind utilities;
169
+
170
+ :root {
171
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
172
+ line-height: 1.5;
173
+ font-weight: 400;
174
+
175
+ color-scheme: light dark;
176
+ color: rgba(255, 255, 255, 0.87);
177
+ background-color: #242424;
178
+
179
+ font-synthesis: none;
180
+ text-rendering: optimizeLegibility;
181
+ -webkit-font-smoothing: antialiased;
182
+ -moz-osx-font-smoothing: grayscale;
183
+ }
184
+
185
+ body {
186
+ margin: 0;
187
+ min-height: 100vh;
188
+ }
189
+ `;
190
+ await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'index.css'), indexCss);
191
+ // App component
192
+ const appTsx = getAppComponent(config);
193
+ await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'App.tsx'), appTsx);
194
+ // API client
195
+ const apiClient = getApiClient(config);
196
+ await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'lib', 'api.ts'), apiClient);
197
+ }
198
+ function getAppComponent(config) {
199
+ return `import { BrowserRouter, Routes, Route } from 'react-router-dom'
200
+ import HomePage from './pages/HomePage'
201
+ import DashboardPage from './pages/DashboardPage'
202
+ import LoginPage from './pages/LoginPage'
203
+ import { AuthProvider } from './lib/auth'
204
+ ${config.auth === 'clerk' ? "import { ClerkProvider } from '@clerk/clerk-react'" : ''}
205
+
206
+ function App() {
207
+ ${config.auth === 'clerk' ? `const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;` : ''}
208
+
209
+ return (
210
+ ${config.auth === 'clerk' ? '<ClerkProvider publishableKey={clerkPubKey}>' : ''}
211
+ ${config.auth === 'jwt' ? '<AuthProvider>' : ''}
212
+ <BrowserRouter>
213
+ <Routes>
214
+ <Route path="/" element={<HomePage />} />
215
+ <Route path="/login" element={<LoginPage />} />
216
+ <Route path="/dashboard" element={<DashboardPage />} />
217
+ </Routes>
218
+ </BrowserRouter>
219
+ ${config.auth === 'jwt' ? '</AuthProvider>' : ''}
220
+ ${config.auth === 'clerk' ? '</ClerkProvider>' : ''}
221
+ )
222
+ }
223
+
224
+ export default App
225
+ `;
226
+ }
227
+ function getApiClient(config) {
228
+ return `import axios from 'axios';
229
+
230
+ const api = axios.create({
231
+ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api',
232
+ headers: {
233
+ 'Content-Type': 'application/json',
234
+ },
235
+ });
236
+
237
+ // Request interceptor to add auth token
238
+ api.interceptors.request.use((config) => {
239
+ const token = localStorage.getItem('token');
240
+ if (token) {
241
+ config.headers.Authorization = \`Bearer \${token}\`;
242
+ }
243
+ return config;
244
+ });
245
+
246
+ // Response interceptor for error handling
247
+ api.interceptors.response.use(
248
+ (response) => response,
249
+ (error) => {
250
+ if (error.response?.status === 401) {
251
+ localStorage.removeItem('token');
252
+ window.location.href = '/login';
253
+ }
254
+ return Promise.reject(error);
255
+ }
256
+ );
257
+
258
+ export default api;
259
+ `;
260
+ }
261
+ async function generateNextJS(config, frontendDir) {
262
+ // Package.json
263
+ const packageJson = {
264
+ name: `${config.projectName}-frontend`,
265
+ version: '0.1.0',
266
+ scripts: {
267
+ dev: 'next dev',
268
+ build: 'next build',
269
+ start: 'next start',
270
+ lint: 'next lint',
271
+ },
272
+ dependencies: {
273
+ react: '^18.2.0',
274
+ 'react-dom': '^18.2.0',
275
+ next: '^14.1.0',
276
+ axios: '^1.6.5',
277
+ ...(config.auth === 'clerk' && { '@clerk/nextjs': '^4.29.3' }),
278
+ ...(config.auth === 'supabase' && { '@supabase/supabase-js': '^2.39.3', '@supabase/ssr': '^0.0.10' }),
279
+ ...(config.auth === 'firebase' && { firebase: '^10.7.2' }),
280
+ ...(config.auth === 'authjs' && { 'next-auth': '^4.24.5' }),
281
+ },
282
+ devDependencies: {
283
+ '@types/node': '^20.11.5',
284
+ '@types/react': '^18.2.48',
285
+ '@types/react-dom': '^18.2.18',
286
+ typescript: '^5.3.3',
287
+ tailwindcss: '^3.4.1',
288
+ autoprefixer: '^10.4.17',
289
+ postcss: '^8.4.33',
290
+ eslint: '^8.56.0',
291
+ 'eslint-config-next': '^14.1.0',
292
+ },
293
+ };
294
+ await fs_extra_1.default.writeJSON(path_1.default.join(frontendDir, 'package.json'), packageJson, { spaces: 2 });
295
+ // Next.js config
296
+ const nextConfig = `/** @type {import('next').NextConfig} */
297
+ const nextConfig = {
298
+ reactStrictMode: true,
299
+ }
300
+
301
+ module.exports = nextConfig
302
+ `;
303
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'next.config.js'), nextConfig);
304
+ // TypeScript config
305
+ const tsConfig = {
306
+ compilerOptions: {
307
+ target: 'ES2017',
308
+ lib: ['dom', 'dom.iterable', 'esnext'],
309
+ allowJs: true,
310
+ skipLibCheck: true,
311
+ strict: true,
312
+ noEmit: true,
313
+ esModuleInterop: true,
314
+ module: 'esnext',
315
+ moduleResolution: 'bundler',
316
+ resolveJsonModule: true,
317
+ isolatedModules: true,
318
+ jsx: 'preserve',
319
+ incremental: true,
320
+ plugins: [{ name: 'next' }],
321
+ paths: {
322
+ '@/*': ['./*'],
323
+ },
324
+ },
325
+ include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
326
+ exclude: ['node_modules'],
327
+ };
328
+ await fs_extra_1.default.writeJSON(path_1.default.join(frontendDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
329
+ // Tailwind config
330
+ const tailwindConfig = `import type { Config } from 'tailwindcss'
331
+
332
+ const config: Config = {
333
+ content: [
334
+ './pages/**/*.{js,ts,jsx,tsx,mdx}',
335
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
336
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
337
+ ],
338
+ theme: {
339
+ extend: {},
340
+ },
341
+ plugins: [],
342
+ }
343
+ export default config
344
+ `;
345
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'tailwind.config.ts'), tailwindConfig);
346
+ // PostCSS config
347
+ const postcssConfig = `module.exports = {
348
+ plugins: {
349
+ tailwindcss: {},
350
+ autoprefixer: {},
351
+ },
352
+ }
353
+ `;
354
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'postcss.config.js'), postcssConfig);
355
+ // Create app directory structure
356
+ const appDir = path_1.default.join(frontendDir, 'app');
357
+ await fs_extra_1.default.ensureDir(appDir);
358
+ await fs_extra_1.default.ensureDir(path_1.default.join(appDir, 'login'));
359
+ await fs_extra_1.default.ensureDir(path_1.default.join(appDir, 'dashboard'));
360
+ await fs_extra_1.default.ensureDir(path_1.default.join(frontendDir, 'components'));
361
+ await fs_extra_1.default.ensureDir(path_1.default.join(frontendDir, 'lib'));
362
+ // Root layout
363
+ const layout = getNextJSLayout(config);
364
+ await fs_extra_1.default.writeFile(path_1.default.join(appDir, 'layout.tsx'), layout);
365
+ // Home page
366
+ const homePage = `export default function HomePage() {
367
+ return (
368
+ <div className="min-h-screen bg-gray-900 text-white">
369
+ <div className="container mx-auto px-4 py-16">
370
+ <h1 className="text-5xl font-bold mb-4">${config.projectName}</h1>
371
+ <p className="text-xl text-gray-400 mb-8">
372
+ Generated by ForgeStack OS
373
+ </p>
374
+ <div className="space-x-4">
375
+ <a
376
+ href="/login"
377
+ className="inline-block bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded font-semibold"
378
+ >
379
+ Get Started
380
+ </a>
381
+ </div>
382
+ </div>
383
+ </div>
384
+ )
385
+ }
386
+ `;
387
+ await fs_extra_1.default.writeFile(path_1.default.join(appDir, 'page.tsx'), homePage);
388
+ // Login page
389
+ const loginPage = getNextJSLoginPage(config);
390
+ await fs_extra_1.default.writeFile(path_1.default.join(appDir, 'login', 'page.tsx'), loginPage);
391
+ // Dashboard page
392
+ const dashboardPage = getNextJSDashboardPage(config);
393
+ await fs_extra_1.default.writeFile(path_1.default.join(appDir, 'dashboard', 'page.tsx'), dashboardPage);
394
+ // Global CSS
395
+ const globalsCss = `@tailwind base;
396
+ @tailwind components;
397
+ @tailwind utilities;
398
+
399
+ :root {
400
+ --foreground-rgb: 255, 255, 255;
401
+ --background-start-rgb: 17, 24, 39;
402
+ --background-end-rgb: 0, 0, 0;
403
+ }
404
+
405
+ body {
406
+ color: rgb(var(--foreground-rgb));
407
+ background: linear-gradient(
408
+ to bottom,
409
+ transparent,
410
+ rgb(var(--background-end-rgb))
411
+ )
412
+ rgb(var(--background-start-rgb));
413
+ }
414
+ `;
415
+ await fs_extra_1.default.writeFile(path_1.default.join(appDir, 'globals.css'), globalsCss);
416
+ // API client
417
+ const apiClient = `import axios from 'axios';
418
+
419
+ const api = axios.create({
420
+ baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api',
421
+ headers: {
422
+ 'Content-Type': 'application/json',
423
+ },
424
+ });
425
+
426
+ // Request interceptor
427
+ api.interceptors.request.use((config) => {
428
+ if (typeof window !== 'undefined') {
429
+ const token = localStorage.getItem('token');
430
+ if (token) {
431
+ config.headers.Authorization = \`Bearer \${token}\`;
432
+ }
433
+ }
434
+ return config;
435
+ });
436
+
437
+ // Response interceptor
438
+ api.interceptors.response.use(
439
+ (response) => response,
440
+ (error) => {
441
+ if (error.response?.status === 401) {
442
+ if (typeof window !== 'undefined') {
443
+ localStorage.removeItem('token');
444
+ window.location.href = '/login';
445
+ }
446
+ }
447
+ return Promise.reject(error);
448
+ }
449
+ );
450
+
451
+ export default api;
452
+ `;
453
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'lib', 'api.ts'), apiClient);
454
+ // Middleware for auth (if using Clerk or custom JWT)
455
+ if (config.auth === 'clerk') {
456
+ const middleware = `import { authMiddleware } from '@clerk/nextjs';
457
+
458
+ export default authMiddleware({
459
+ publicRoutes: ['/', '/login'],
460
+ });
461
+
462
+ export const config = {
463
+ matcher: ['/((?!.+\\\\.[\\\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
464
+ };
465
+ `;
466
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'middleware.ts'), middleware);
467
+ }
468
+ else if (config.auth === 'jwt') {
469
+ const middleware = `import { NextResponse } from 'next/server';
470
+ import type { NextRequest } from 'next/server';
471
+
472
+ export function middleware(request: NextRequest) {
473
+ const token = request.cookies.get('token')?.value;
474
+ const isAuthPage = request.nextUrl.pathname.startsWith('/login');
475
+ const isProtectedPage = request.nextUrl.pathname.startsWith('/dashboard');
476
+
477
+ if (isProtectedPage && !token) {
478
+ return NextResponse.redirect(new URL('/login', request.url));
479
+ }
480
+
481
+ if (isAuthPage && token) {
482
+ return NextResponse.redirect(new URL('/dashboard', request.url));
483
+ }
484
+
485
+ return NextResponse.next();
486
+ }
487
+
488
+ export const config = {
489
+ matcher: ['/dashboard/:path*', '/login'],
490
+ };
491
+ `;
492
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, 'middleware.ts'), middleware);
493
+ }
494
+ // .env.local.example
495
+ const envExample = `NEXT_PUBLIC_API_URL=http://localhost:3000/api
496
+ ${config.auth === 'clerk' ? 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\nCLERK_SECRET_KEY=sk_test_xxxxx' : ''}
497
+ ${config.auth === 'supabase' ? 'NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co\nNEXT_PUBLIC_SUPABASE_ANON_KEY=xxxxx' : ''}
498
+ `;
499
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, '.env.local.example'), envExample);
500
+ // .gitignore
501
+ const gitignore = `# dependencies
502
+ /node_modules
503
+ /.pnp
504
+ .pnp.js
505
+
506
+ # testing
507
+ /coverage
508
+
509
+ # next.js
510
+ /.next/
511
+ /out/
512
+
513
+ # production
514
+ /build
515
+
516
+ # misc
517
+ .DS_Store
518
+ *.pem
519
+
520
+ # debug
521
+ npm-debug.log*
522
+ yarn-debug.log*
523
+ yarn-error.log*
524
+
525
+ # local env files
526
+ .env*.local
527
+
528
+ # vercel
529
+ .vercel
530
+
531
+ # typescript
532
+ *.tsbuildinfo
533
+ next-env.d.ts
534
+ `;
535
+ await fs_extra_1.default.writeFile(path_1.default.join(frontendDir, '.gitignore'), gitignore);
536
+ }
537
+ async function generateVueVite(_config, _frontendDir) {
538
+ // Placeholder for Vue generation
539
+ throw new Error('Vue support coming in Phase 2');
540
+ }
541
+ async function generateSvelteKit(_config, _frontendDir) {
542
+ // Placeholder for SvelteKit generation
543
+ throw new Error('SvelteKit support coming in Phase 2');
544
+ }
545
+ // Helper functions for Next.js at the end of frontend.ts
546
+ function getNextJSLayout(config) {
547
+ return `import type { Metadata } from 'next'
548
+ import './globals.css'
549
+
550
+ export const metadata: Metadata = {
551
+ title: '${config.projectName}',
552
+ description: 'Generated by ForgeStack OS',
553
+ }
554
+
555
+ export default function RootLayout({
556
+ children,
557
+ }: {
558
+ children: React.ReactNode
559
+ }) {
560
+ return (
561
+ <html lang="en">
562
+ <body>{children}</body>
563
+ </html>
564
+ )
565
+ }
566
+ `;
567
+ }
568
+ function getNextJSLoginPage(config) {
569
+ if (config.auth === 'clerk') {
570
+ return `import { SignIn } from '@clerk/nextjs'
571
+
572
+ export default function LoginPage() {
573
+ return (
574
+ <div className="min-h-screen flex items-center justify-center bg-gray-900">
575
+ <SignIn />
576
+ </div>
577
+ )
578
+ }
579
+ `;
580
+ }
581
+ return `'use client'
582
+
583
+ import { useState } from 'react'
584
+ import { useRouter } from 'next/navigation'
585
+ import api from '@/lib/api'
586
+
587
+ export default function LoginPage() {
588
+ const [email, setEmail] = useState('')
589
+ const [password, setPassword] = useState('')
590
+ const [error, setError] = useState('')
591
+ const router = useRouter()
592
+
593
+ const handleSubmit = async (e: React.FormEvent) => {
594
+ e.preventDefault()
595
+ try {
596
+ const res = await api.post('/auth/login', { email, password })
597
+ localStorage.setItem('token', res.data.token)
598
+ router.push('/dashboard')
599
+ } catch (err: any) {
600
+ setError(err.response?.data?.error || 'Login failed')
601
+ }
602
+ }
603
+
604
+ return (
605
+ <div className="min-h-screen flex items-center justify-center bg-gray-900">
606
+ <div className="bg-gray-800 p-8 rounded-lg shadow-lg w-full max-w-md">
607
+ <h1 className="text-3xl font-bold text-white mb-6">Login</h1>
608
+ {error && <div className="bg-red-500 text-white p-3 rounded mb-4">{error}</div>}
609
+ <form onSubmit={handleSubmit}>
610
+ <div className="mb-4">
611
+ <label className="block text-gray-300 mb-2">Email</label>
612
+ <input
613
+ type="email"
614
+ value={email}
615
+ onChange={(e) => setEmail(e.target.value)}
616
+ className="w-full p-3 rounded bg-gray-700 text-white"
617
+ required
618
+ />
619
+ </div>
620
+ <div className="mb-6">
621
+ <label className="block text-gray-300 mb-2">Password</label>
622
+ <input
623
+ type="password"
624
+ value={password}
625
+ onChange={(e) => setPassword(e.target.value)}
626
+ className="w-full p-3 rounded bg-gray-700 text-white"
627
+ required
628
+ />
629
+ </div>
630
+ <button
631
+ type="submit"
632
+ className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 rounded"
633
+ >
634
+ Login
635
+ </button>
636
+ </form>
637
+ </div>
638
+ </div>
639
+ )
640
+ }
641
+ `;
642
+ }
643
+ function getNextJSDashboardPage(config) {
644
+ if (config.auth === 'clerk') {
645
+ return `import { UserButton } from '@clerk/nextjs'
646
+ import { currentUser } from '@clerk/nextjs/server'
647
+ import { redirect } from 'next/navigation'
648
+
649
+ export default async function DashboardPage() {
650
+ const user = await currentUser()
651
+
652
+ if (!user) {
653
+ redirect('/login')
654
+ }
655
+
656
+ return (
657
+ <div className="min-h-screen bg-gray-900 text-white">
658
+ <nav className="bg-gray-800 p-4">
659
+ <div className="container mx-auto flex justify-between items-center">
660
+ <h1 className="text-2xl font-bold">${config.projectName}</h1>
661
+ <UserButton afterSignOutUrl="/" />
662
+ </div>
663
+ </nav>
664
+ <div className="container mx-auto px-4 py-16">
665
+ <h2 className="text-3xl font-bold mb-4">Welcome, {user.firstName || user.emailAddresses[0].emailAddress}!</h2>
666
+ <p className="text-gray-400">You're logged in to your dashboard.</p>
667
+ </div>
668
+ </div>
669
+ )
670
+ }
671
+ `;
672
+ }
673
+ return `'use client'
674
+
675
+ import { useEffect, useState } from 'react'
676
+ import { useRouter } from 'next/navigation'
677
+
678
+ export default function DashboardPage() {
679
+ const [user, setUser] = useState<any>(null)
680
+ const [loading, setLoading] = useState(true)
681
+ const router = useRouter()
682
+
683
+ useEffect(() => {
684
+ const token = localStorage.getItem('token')
685
+ if (!token) {
686
+ router.push('/login')
687
+ return
688
+ }
689
+
690
+ try {
691
+ const payload = JSON.parse(atob(token.split('.')[1]))
692
+ setUser(payload)
693
+ } catch (err) {
694
+ router.push('/login')
695
+ } finally {
696
+ setLoading(false)
697
+ }
698
+ }, [router])
699
+
700
+ const handleLogout = () => {
701
+ localStorage.removeItem('token')
702
+ router.push('/login')
703
+ }
704
+
705
+ if (loading) {
706
+ return (
707
+ <div className="min-h-screen bg-gray-900 flex items-center justify-center">
708
+ <div className="text-white">Loading...</div>
709
+ </div>
710
+ )
711
+ }
712
+
713
+ return (
714
+ <div className="min-h-screen bg-gray-900 text-white">
715
+ <nav className="bg-gray-800 p-4">
716
+ <div className="container mx-auto flex justify-between items-center">
717
+ <h1 className="text-2xl font-bold">${config.projectName}</h1>
718
+ <button
719
+ onClick={handleLogout}
720
+ className="bg-red-600 hover:bg-red-700 px-4 py-2 rounded"
721
+ >
722
+ Logout
723
+ </button>
724
+ </div>
725
+ </nav>
726
+ <div className="container mx-auto px-4 py-16">
727
+ <h2 className="text-3xl font-bold mb-4">Welcome, {user?.email}!</h2>
728
+ <p className="text-gray-400">You're logged in to your dashboard.</p>
729
+ </div>
730
+ </div>
731
+ )
732
+ }
733
+ `;
734
+ }
735
+ //# sourceMappingURL=frontend.js.map