diff --git a/docs/reference/AGENTS.md b/docs/reference/AGENTS.md
index b47ec15..a28001f 100644
--- a/docs/reference/AGENTS.md
+++ b/docs/reference/AGENTS.md
@@ -212,31 +212,31 @@ Repairs multiple errors efficiently.
- Validates fixes don't introduce new errors
- Returns repaired files and success status
-### Generators (`/src/lib/generators.ts`)
+### Generators (`/src/lib/generators/*`)
Code generation utilities for project export.
#### Functions
-##### `generateNextJSProject(appName: string, models: PrismaModel[], components: ComponentNode[], theme: ThemeConfig)`
+##### `generateNextJSProject(appName: string, models: PrismaModel[], components: ComponentNode[], theme: ThemeConfig)` (`/src/lib/generators/generateNextJSProject.ts`)
Creates complete Next.js file structure.
-##### `generatePrismaSchema(models: PrismaModel[])`
+##### `generatePrismaSchema(models: PrismaModel[])` (`/src/lib/generators/generatePrismaSchema.ts`)
Converts visual models to Prisma schema syntax.
-##### `generateMUITheme(theme: ThemeConfig)`
+##### `generateMUITheme(theme: ThemeConfig)` (`/src/lib/generators/generateMUITheme.ts`)
Exports Material UI theme configuration.
-##### `generatePlaywrightTests(tests: PlaywrightTest[])`
+##### `generatePlaywrightTests(tests: PlaywrightTest[])` (`/src/lib/generators/generatePlaywrightTests.ts`)
Converts visual test definitions to Playwright code.
-##### `generateStorybookStories(stories: StorybookStory[])`
+##### `generateStorybookStories(stories: StorybookStory[])` (`/src/lib/generators/generateStorybookStories.ts`)
Creates Storybook CSF3 story files.
-##### `generateUnitTests(tests: UnitTest[])`
+##### `generateUnitTests(tests: UnitTest[])` (`/src/lib/generators/generateUnitTests.ts`)
Generates Vitest test files with React Testing Library.
-##### `generateFlaskApp(config: FlaskConfig)`
+##### `generateFlaskApp(config: FlaskConfig)` (`/src/lib/generators/generateFlaskApp.ts`)
Creates Flask application with blueprints and routes.
## Integration Points
diff --git a/src/components/DocumentationView.tsx b/src/components/DocumentationView.tsx
index d852710..cd6d115 100644
--- a/src/components/DocumentationView.tsx
+++ b/src/components/DocumentationView.tsx
@@ -604,18 +604,81 @@ export function DocumentationView() {
]}
/>
+
+
+
+
+
+
diff --git a/src/data/documentation/agents-data.json b/src/data/documentation/agents-data.json
index d3e0585..0a471eb 100644
--- a/src/data/documentation/agents-data.json
+++ b/src/data/documentation/agents-data.json
@@ -30,18 +30,81 @@
]
},
{
- "filename": "generators.ts",
- "path": "/src/lib/generators.ts",
- "description": "Code generation utilities for project export",
+ "filename": "generateNextJSProject.ts",
+ "path": "/src/lib/generators/generateNextJSProject.ts",
+ "description": "Generates Next.js project scaffolding",
"features": [
"Next.js project structure generation",
- "Prisma schema file generation",
- "Material UI theme configuration",
- "Playwright test file generation",
- "Storybook story file generation",
- "Unit test file generation",
- "Flask application structure",
- "Package.json configuration"
+ "Package.json defaults and scripts",
+ "Prisma schema wiring for data models",
+ "Material UI theme bootstrap",
+ "README and environment defaults"
+ ]
+ },
+ {
+ "filename": "generatePrismaSchema.ts",
+ "path": "/src/lib/generators/generatePrismaSchema.ts",
+ "description": "Creates Prisma schema files from models",
+ "features": [
+ "Model definition generation",
+ "Field-level attributes and defaults",
+ "Datasource and client config",
+ "Array and optional field handling"
+ ]
+ },
+ {
+ "filename": "generateMUITheme.ts",
+ "path": "/src/lib/generators/generateMUITheme.ts",
+ "description": "Builds Material UI theme configuration",
+ "features": [
+ "Light and dark palette generation",
+ "Typography and spacing configuration",
+ "Shape/border radius settings",
+ "Theme export defaults"
+ ]
+ },
+ {
+ "filename": "generatePlaywrightTests.ts",
+ "path": "/src/lib/generators/generatePlaywrightTests.ts",
+ "description": "Generates Playwright test suites",
+ "features": [
+ "Test suite scaffolding",
+ "Step-by-step action scripts",
+ "Assertion generation",
+ "Default test fallback"
+ ]
+ },
+ {
+ "filename": "generateStorybookStories.ts",
+ "path": "/src/lib/generators/generateStorybookStories.ts",
+ "description": "Creates Storybook story files",
+ "features": [
+ "Stories grouped by component",
+ "Meta configuration generation",
+ "Args mapping for variants",
+ "Story file structure"
+ ]
+ },
+ {
+ "filename": "generateUnitTests.ts",
+ "path": "/src/lib/generators/generateUnitTests.ts",
+ "description": "Generates unit test files",
+ "features": [
+ "Component, hook, and module tests",
+ "Test setup and teardown blocks",
+ "Assertion generation",
+ "Dynamic test naming"
+ ]
+ },
+ {
+ "filename": "generateFlaskApp.ts",
+ "path": "/src/lib/generators/generateFlaskApp.ts",
+ "description": "Creates Flask backend scaffolding",
+ "features": [
+ "App factory generation",
+ "Blueprint registration",
+ "Environment and requirements files",
+ "README and setup instructions"
]
}
],
diff --git a/src/lib/generators.ts b/src/lib/generators.ts
deleted file mode 100644
index 50c52e6..0000000
--- a/src/lib/generators.ts
+++ /dev/null
@@ -1,565 +0,0 @@
-import { PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig, FlaskBlueprint, FlaskEndpoint } from '@/types/project'
-
-export function generatePrismaSchema(models: PrismaModel[]): string {
- let schema = `generator client {\n provider = "prisma-client-js"\n}\n\n`
- schema += `datasource db {\n provider = "postgresql"\n url = env("DATABASE_URL")\n}\n\n`
-
- models.forEach((model) => {
- schema += `model ${model.name} {\n`
- model.fields.forEach((field) => {
- let fieldLine = ` ${field.name} ${field.type}`
- if (field.isArray) fieldLine += '[]'
- if (field.isRequired && !field.defaultValue) fieldLine += ''
- else if (!field.isRequired) fieldLine += '?'
- if (field.isUnique) fieldLine += ' @unique'
- if (field.defaultValue) fieldLine += ` @default(${field.defaultValue})`
- schema += fieldLine + '\n'
- })
- schema += `}\n\n`
- })
-
- return schema
-}
-
-export function generateComponentCode(node: ComponentNode, indent: number = 0): string {
- const spaces = ' '.repeat(indent)
- const propsStr = Object.entries(node.props)
- .map(([key, value]) => {
- if (typeof value === 'string') return `${key}="${value}"`
- if (typeof value === 'boolean') return value ? key : ''
- return `${key}={${JSON.stringify(value)}}`
- })
- .filter(Boolean)
- .join(' ')
-
- if (node.children.length === 0) {
- return `${spaces}<${node.type}${propsStr ? ' ' + propsStr : ''} />`
- }
-
- let code = `${spaces}<${node.type}${propsStr ? ' ' + propsStr : ''}>\n`
- node.children.forEach((child) => {
- code += generateComponentCode(child, indent + 1) + '\n'
- })
- code += `${spaces}${node.type}>`
-
- return code
-}
-
-export function generateMUITheme(theme: ThemeConfig): string {
- if (!theme.variants || theme.variants.length === 0) {
- return `import { createTheme } from '@mui/material/styles';
-
-export const theme = createTheme({
- palette: {
- mode: 'light',
- },
-});`
- }
-
- const lightVariant = theme.variants.find((v) => v.id === 'light') || theme.variants[0]
- const darkVariant = theme.variants.find((v) => v.id === 'dark')
-
- let themeCode = `import { createTheme } from '@mui/material/styles';
-
-export const lightTheme = createTheme({
- palette: {
- mode: 'light',
- primary: {
- main: '${lightVariant.colors.primaryColor}',
- },
- secondary: {
- main: '${lightVariant.colors.secondaryColor}',
- },
- error: {
- main: '${lightVariant.colors.errorColor}',
- },
- warning: {
- main: '${lightVariant.colors.warningColor}',
- },
- success: {
- main: '${lightVariant.colors.successColor}',
- },
- background: {
- default: '${lightVariant.colors.background}',
- paper: '${lightVariant.colors.surface}',
- },
- text: {
- primary: '${lightVariant.colors.text}',
- secondary: '${lightVariant.colors.textSecondary}',
- },
- },
- typography: {
- fontFamily: '${theme.fontFamily}',
- fontSize: ${theme.fontSize.medium},
- },
- spacing: ${theme.spacing},
- shape: {
- borderRadius: ${theme.borderRadius},
- },
-});
-`
-
- if (darkVariant) {
- themeCode += `
-export const darkTheme = createTheme({
- palette: {
- mode: 'dark',
- primary: {
- main: '${darkVariant.colors.primaryColor}',
- },
- secondary: {
- main: '${darkVariant.colors.secondaryColor}',
- },
- error: {
- main: '${darkVariant.colors.errorColor}',
- },
- warning: {
- main: '${darkVariant.colors.warningColor}',
- },
- success: {
- main: '${darkVariant.colors.successColor}',
- },
- background: {
- default: '${darkVariant.colors.background}',
- paper: '${darkVariant.colors.surface}',
- },
- text: {
- primary: '${darkVariant.colors.text}',
- secondary: '${darkVariant.colors.textSecondary}',
- },
- },
- typography: {
- fontFamily: '${theme.fontFamily}',
- fontSize: ${theme.fontSize.medium},
- },
- spacing: ${theme.spacing},
- shape: {
- borderRadius: ${theme.borderRadius},
- },
-});
-
-export const theme = lightTheme;`
- } else {
- themeCode += `\nexport const theme = lightTheme;`
- }
-
- return themeCode
-}
-
-export function generateNextJSProject(
- projectName: string,
- models: PrismaModel[],
- components: ComponentNode[],
- theme: ThemeConfig
-): Record {
- const files: Record = {}
-
- files['package.json'] = JSON.stringify(
- {
- name: projectName,
- version: '0.1.0',
- private: true,
- scripts: {
- dev: 'next dev',
- build: 'next build',
- start: 'next start',
- lint: 'next lint',
- },
- dependencies: {
- '@mui/material': '^5.15.0',
- '@emotion/react': '^11.11.0',
- '@emotion/styled': '^11.11.0',
- '@prisma/client': '^5.8.0',
- next: '14.1.0',
- react: '^18.2.0',
- 'react-dom': '^18.2.0',
- },
- devDependencies: {
- '@types/node': '^20',
- '@types/react': '^18',
- '@types/react-dom': '^18',
- prisma: '^5.8.0',
- typescript: '^5',
- },
- },
- null,
- 2
- )
-
- files['prisma/schema.prisma'] = generatePrismaSchema(models)
-
- files['src/theme.ts'] = generateMUITheme(theme)
-
- files['src/app/page.tsx'] = `'use client'
-
-import { ThemeProvider } from '@mui/material/styles'
-import CssBaseline from '@mui/material/CssBaseline'
-import { theme } from '@/theme'
-
-export default function Home() {
- return (
-
-
-
- {/* Your components here */}
-
-
- )
-}`
-
- files['next.config.js'] = `/** @type {import('next').NextConfig} */
-const nextConfig = {}
-
-module.exports = nextConfig`
-
- files['.env'] = `DATABASE_URL="postgresql://user:password@localhost:5432/mydb"`
-
- files['README.md'] = `# ${projectName}
-
-Generated with CodeForge
-
-## Getting Started
-
-1. Install dependencies:
-\`\`\`bash
-npm install
-\`\`\`
-
-2. Set up your database in .env
-
-3. Run Prisma migrations:
-\`\`\`bash
-npx prisma migrate dev
-\`\`\`
-
-4. Start the development server:
-\`\`\`bash
-npm run dev
-\`\`\`
-
-Open [http://localhost:3000](http://localhost:3000) with your browser.`
-
- return files
-}
-
-export function generatePlaywrightTests(tests: PlaywrightTest[]): string {
- if (tests.length === 0) {
- return `import { test, expect } from '@playwright/test'
-
-test('example test', async ({ page }) => {
- await page.goto('/')
- await expect(page).toHaveTitle(/.*/)
-})`
- }
-
- let code = `import { test, expect } from '@playwright/test'\n\n`
-
- tests.forEach(testSuite => {
- code += `test.describe('${testSuite.name}', () => {\n`
- if (testSuite.description) {
- code += ` // ${testSuite.description}\n`
- }
- code += ` test('${testSuite.name}', async ({ page }) => {\n`
-
- testSuite.steps.forEach(step => {
- switch (step.action) {
- case 'navigate':
- code += ` await page.goto('${testSuite.pageUrl}')\n`
- break
- case 'click':
- code += ` await page.click('${step.selector}')\n`
- break
- case 'fill':
- code += ` await page.fill('${step.selector}', '${step.value}')\n`
- break
- case 'expect':
- code += ` await expect(page.locator('${step.selector}')).${step.assertion}\n`
- break
- case 'wait':
- code += ` await page.waitForTimeout(${step.timeout || 1000})\n`
- break
- case 'select':
- code += ` await page.selectOption('${step.selector}', '${step.value}')\n`
- break
- case 'check':
- code += ` await page.check('${step.selector}')\n`
- break
- case 'uncheck':
- code += ` await page.uncheck('${step.selector}')\n`
- break
- }
- })
-
- code += ` })\n`
- code += `})\n\n`
- })
-
- return code
-}
-
-export function generateStorybookStories(stories: StorybookStory[]): Record {
- const fileMap: Record = {}
-
- stories.forEach(story => {
- const key = `${story.category}/${story.componentName}`
- if (!fileMap[key]) {
- fileMap[key] = []
- }
- fileMap[key].push(story)
- })
-
- const files: Record = {}
-
- Object.entries(fileMap).forEach(([path, storyList]) => {
- const componentName = storyList[0].componentName
- let code = `import type { Meta, StoryObj } from '@storybook/react'\nimport { ${componentName} } from '@/components/${componentName}'\n\n`
-
- code += `const meta: Meta = {\n`
- code += ` title: '${path}',\n`
- code += ` component: ${componentName},\n`
- code += ` tags: ['autodocs'],\n`
- code += `}\n\n`
- code += `export default meta\n`
- code += `type Story = StoryObj\n\n`
-
- storyList.forEach(story => {
- code += `export const ${story.storyName.replace(/\s+/g, '')}: Story = {\n`
- if (Object.keys(story.args).length > 0) {
- code += ` args: ${JSON.stringify(story.args, null, 4).replace(/"/g, "'")},\n`
- }
- code += `}\n\n`
- })
-
- files[`src/stories/${componentName}.stories.tsx`] = code
- })
-
- return files
-}
-
-export function generateUnitTests(tests: UnitTest[]): Record {
- const files: Record = {}
-
- tests.forEach(testSuite => {
- const fileName = testSuite.targetFile
- ? testSuite.targetFile.replace(/\.(tsx|ts|jsx|js)$/, '.test.$1')
- : `src/__tests__/${testSuite.name.replace(/\s+/g, '')}.test.tsx`
-
- let code = ''
-
- if (testSuite.testType === 'component') {
- code += `import { render, screen } from '@testing-library/react'\nimport { describe, it, expect } from 'vitest'\n`
- if (testSuite.targetFile) {
- const componentName = testSuite.targetFile.split('/').pop()?.replace(/\.(tsx|ts|jsx|js)$/, '')
- code += `import { ${componentName} } from '${testSuite.targetFile.replace('.tsx', '').replace('.ts', '')}'\n\n`
- }
- } else if (testSuite.testType === 'hook') {
- code += `import { renderHook } from '@testing-library/react'\nimport { describe, it, expect } from 'vitest'\n`
- if (testSuite.targetFile) {
- const hookName = testSuite.targetFile.split('/').pop()?.replace(/\.(tsx|ts|jsx|js)$/, '')
- code += `import { ${hookName} } from '${testSuite.targetFile.replace('.tsx', '').replace('.ts', '')}'\n\n`
- }
- } else {
- code += `import { describe, it, expect } from 'vitest'\n`
- if (testSuite.targetFile) {
- code += `import * as module from '${testSuite.targetFile.replace('.tsx', '').replace('.ts', '')}'\n\n`
- }
- }
-
- code += `describe('${testSuite.name}', () => {\n`
- if (testSuite.description) {
- code += ` // ${testSuite.description}\n\n`
- }
-
- testSuite.testCases.forEach(testCase => {
- code += ` it('${testCase.description}', () => {\n`
-
- if (testCase.setup) {
- code += ` ${testCase.setup}\n\n`
- }
-
- testCase.assertions.forEach(assertion => {
- code += ` ${assertion}\n`
- })
-
- if (testCase.teardown) {
- code += `\n ${testCase.teardown}\n`
- }
-
- code += ` })\n\n`
- })
-
- code += `})\n`
-
- files[fileName] = code
- })
-
- return files
-}
-
-export function generateFlaskBlueprint(blueprint: FlaskBlueprint): string {
- let code = `from flask import Blueprint, request, jsonify\n`
- code += `from typing import Dict, Any\n\n`
-
- const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
- code += `${blueprintVarName}_bp = Blueprint('${blueprintVarName}', __name__, url_prefix='${blueprint.urlPrefix}')\n\n`
-
- blueprint.endpoints.forEach(endpoint => {
- const functionName = endpoint.name.toLowerCase().replace(/\s+/g, '_')
- code += `@${blueprintVarName}_bp.route('${endpoint.path}', methods=['${endpoint.method}'])\n`
- code += `def ${functionName}():\n`
- code += ` """\n`
- code += ` ${endpoint.description || endpoint.name}\n`
-
- if (endpoint.queryParams && endpoint.queryParams.length > 0) {
- code += ` \n Query Parameters:\n`
- endpoint.queryParams.forEach(param => {
- code += ` - ${param.name} (${param.type})${param.required ? ' [required]' : ''}: ${param.description || ''}\n`
- })
- }
-
- code += ` """\n`
-
- if (endpoint.authentication) {
- code += ` # TODO: Add authentication check\n`
- code += ` # if not is_authenticated(request):\n`
- code += ` # return jsonify({'error': 'Unauthorized'}), 401\n\n`
- }
-
- if (endpoint.queryParams && endpoint.queryParams.length > 0) {
- endpoint.queryParams.forEach(param => {
- if (param.required) {
- code += ` ${param.name} = request.args.get('${param.name}')\n`
- code += ` if ${param.name} is None:\n`
- code += ` return jsonify({'error': '${param.name} is required'}), 400\n\n`
- } else {
- const defaultVal = param.defaultValue || (param.type === 'string' ? "''" : param.type === 'number' ? '0' : 'None')
- code += ` ${param.name} = request.args.get('${param.name}', ${defaultVal})\n`
- }
- })
- code += `\n`
- }
-
- if (endpoint.method === 'POST' || endpoint.method === 'PUT' || endpoint.method === 'PATCH') {
- code += ` data = request.get_json()\n`
- code += ` if not data:\n`
- code += ` return jsonify({'error': 'No data provided'}), 400\n\n`
- }
-
- code += ` # TODO: Implement ${endpoint.name} logic\n`
- code += ` result = {\n`
- code += ` 'message': '${endpoint.name} endpoint',\n`
- code += ` 'method': '${endpoint.method}',\n`
- code += ` 'path': '${endpoint.path}'\n`
- code += ` }\n\n`
- code += ` return jsonify(result), 200\n\n\n`
- })
-
- return code
-}
-
-export function generateFlaskApp(config: FlaskConfig): Record {
- const files: Record = {}
-
- let appCode = `from flask import Flask\n`
- if (config.corsOrigins && config.corsOrigins.length > 0) {
- appCode += `from flask_cors import CORS\n`
- }
- appCode += `\n`
-
- config.blueprints.forEach(blueprint => {
- const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
- appCode += `from blueprints.${blueprintVarName} import ${blueprintVarName}_bp\n`
- })
-
- appCode += `\ndef create_app():\n`
- appCode += ` app = Flask(__name__)\n\n`
-
- if (config.debug !== undefined) {
- appCode += ` app.config['DEBUG'] = ${config.debug ? 'True' : 'False'}\n`
- }
-
- if (config.databaseUrl) {
- appCode += ` app.config['SQLALCHEMY_DATABASE_URI'] = '${config.databaseUrl}'\n`
- appCode += ` app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False\n`
- }
-
- appCode += `\n`
-
- if (config.corsOrigins && config.corsOrigins.length > 0) {
- appCode += ` CORS(app, resources={r"/*": {"origins": ${JSON.stringify(config.corsOrigins)}}})\n\n`
- }
-
- config.blueprints.forEach(blueprint => {
- const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
- appCode += ` app.register_blueprint(${blueprintVarName}_bp)\n`
- })
-
- appCode += `\n @app.route('/')\n`
- appCode += ` def index():\n`
- appCode += ` return {'message': 'Flask API is running', 'version': '1.0.0'}\n\n`
-
- appCode += ` return app\n\n\n`
- appCode += `if __name__ == '__main__':\n`
- appCode += ` app = create_app()\n`
- appCode += ` app.run(host='0.0.0.0', port=${config.port || 5000}, debug=${config.debug ? 'True' : 'False'})\n`
-
- files['app.py'] = appCode
-
- config.blueprints.forEach(blueprint => {
- const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
- files[`blueprints/${blueprintVarName}.py`] = generateFlaskBlueprint(blueprint)
- })
-
- files['blueprints/__init__.py'] = '# Flask blueprints\n'
-
- files['requirements.txt'] = `Flask>=3.0.0
-${config.corsOrigins && config.corsOrigins.length > 0 ? 'Flask-CORS>=4.0.0' : ''}
-${config.databaseUrl ? 'Flask-SQLAlchemy>=3.0.0\npsycopg2-binary>=2.9.0' : ''}
-${config.jwtSecret ? 'PyJWT>=2.8.0\nFlask-JWT-Extended>=4.5.0' : ''}
-python-dotenv>=1.0.0
-`
-
- files['.env'] = `FLASK_APP=app.py
-FLASK_ENV=${config.debug ? 'development' : 'production'}
-${config.databaseUrl ? `DATABASE_URL=${config.databaseUrl}` : 'DATABASE_URL=postgresql://user:password@localhost:5432/mydb'}
-${config.jwtSecret ? 'JWT_SECRET_KEY=your-secret-key-here' : ''}
-`
-
- files['README.md'] = `# Flask API
-
-Generated with CodeForge
-
-## Getting Started
-
-1. Create a virtual environment:
-\`\`\`bash
-python -m venv venv
-source venv/bin/activate # On Windows: venv\\Scripts\\activate
-\`\`\`
-
-2. Install dependencies:
-\`\`\`bash
-pip install -r requirements.txt
-\`\`\`
-
-3. Set up your environment variables in .env
-
-4. Run the application:
-\`\`\`bash
-python app.py
-\`\`\`
-
-The API will be available at http://localhost:${config.port || 5000}
-
-## Blueprints
-
-${config.blueprints.map(bp => `- **${bp.name}**: ${bp.description || 'No description'} (${bp.urlPrefix})`).join('\n')}
-
-## API Documentation
-
-${config.enableSwagger ? 'Swagger documentation available at /docs' : 'No API documentation configured'}
-`
-
- return files
-}
-
diff --git a/src/lib/generators/generateComponentCode.ts b/src/lib/generators/generateComponentCode.ts
new file mode 100644
index 0000000..dc19c0d
--- /dev/null
+++ b/src/lib/generators/generateComponentCode.ts
@@ -0,0 +1,25 @@
+import { ComponentNode } from '@/types/project'
+
+export function generateComponentCode(node: ComponentNode, indent: number = 0): string {
+ const spaces = ' '.repeat(indent)
+ const propsStr = Object.entries(node.props)
+ .map(([key, value]) => {
+ if (typeof value === 'string') return `${key}="${value}"`
+ if (typeof value === 'boolean') return value ? key : ''
+ return `${key}={${JSON.stringify(value)}}`
+ })
+ .filter(Boolean)
+ .join(' ')
+
+ if (node.children.length === 0) {
+ return `${spaces}<${node.type}${propsStr ? ' ' + propsStr : ''} />`
+ }
+
+ let code = `${spaces}<${node.type}${propsStr ? ' ' + propsStr : ''}>\n`
+ node.children.forEach((child) => {
+ code += generateComponentCode(child, indent + 1) + '\n'
+ })
+ code += `${spaces}${node.type}>`
+
+ return code
+}
diff --git a/src/lib/generators/generateFlaskApp.ts b/src/lib/generators/generateFlaskApp.ts
new file mode 100644
index 0000000..09858e1
--- /dev/null
+++ b/src/lib/generators/generateFlaskApp.ts
@@ -0,0 +1,108 @@
+import { FlaskConfig } from '@/types/project'
+import { generateFlaskBlueprint } from './generateFlaskBlueprint'
+
+export function generateFlaskApp(config: FlaskConfig): Record {
+ const files: Record = {}
+
+ let appCode = `from flask import Flask\n`
+ if (config.corsOrigins && config.corsOrigins.length > 0) {
+ appCode += `from flask_cors import CORS\n`
+ }
+ appCode += `\n`
+
+ config.blueprints.forEach(blueprint => {
+ const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
+ appCode += `from blueprints.${blueprintVarName} import ${blueprintVarName}_bp\n`
+ })
+
+ appCode += `\ndef create_app():\n`
+ appCode += ` app = Flask(__name__)\n\n`
+
+ if (config.debug !== undefined) {
+ appCode += ` app.config['DEBUG'] = ${config.debug ? 'True' : 'False'}\n`
+ }
+
+ if (config.databaseUrl) {
+ appCode += ` app.config['SQLALCHEMY_DATABASE_URI'] = '${config.databaseUrl}'\n`
+ appCode += ` app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False\n`
+ }
+
+ appCode += `\n`
+
+ if (config.corsOrigins && config.corsOrigins.length > 0) {
+ appCode += ` CORS(app, resources={r"/*": {"origins": ${JSON.stringify(config.corsOrigins)}}})\n\n`
+ }
+
+ config.blueprints.forEach(blueprint => {
+ const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
+ appCode += ` app.register_blueprint(${blueprintVarName}_bp)\n`
+ })
+
+ appCode += `\n @app.route('/')\n`
+ appCode += ` def index():\n`
+ appCode += ` return {'message': 'Flask API is running', 'version': '1.0.0'}\n\n`
+
+ appCode += ` return app\n\n\n`
+ appCode += `if __name__ == '__main__':\n`
+ appCode += ` app = create_app()\n`
+ appCode += ` app.run(host='0.0.0.0', port=${config.port || 5000}, debug=${config.debug ? 'True' : 'False'})\n`
+
+ files['app.py'] = appCode
+
+ config.blueprints.forEach(blueprint => {
+ const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
+ files[`blueprints/${blueprintVarName}.py`] = generateFlaskBlueprint(blueprint)
+ })
+
+ files['blueprints/__init__.py'] = '# Flask blueprints\n'
+
+ files['requirements.txt'] = `Flask>=3.0.0
+${config.corsOrigins && config.corsOrigins.length > 0 ? 'Flask-CORS>=4.0.0' : ''}
+${config.databaseUrl ? 'Flask-SQLAlchemy>=3.0.0\npsycopg2-binary>=2.9.0' : ''}
+${config.jwtSecret ? 'PyJWT>=2.8.0\nFlask-JWT-Extended>=4.5.0' : ''}
+python-dotenv>=1.0.0
+`
+
+ files['.env'] = `FLASK_APP=app.py
+FLASK_ENV=${config.debug ? 'development' : 'production'}
+${config.databaseUrl ? `DATABASE_URL=${config.databaseUrl}` : 'DATABASE_URL=postgresql://user:password@localhost:5432/mydb'}
+${config.jwtSecret ? 'JWT_SECRET_KEY=your-secret-key-here' : ''}
+`
+
+ files['README.md'] = `# Flask API
+
+Generated with CodeForge
+
+## Getting Started
+
+1. Create a virtual environment:
+\`\`\`bash
+python -m venv venv
+source venv/bin/activate # On Windows: venv\\Scripts\\activate
+\`\`\`
+
+2. Install dependencies:
+\`\`\`bash
+pip install -r requirements.txt
+\`\`\`
+
+3. Set up your environment variables in .env
+
+4. Run the application:
+\`\`\`bash
+python app.py
+\`\`\`
+
+The API will be available at http://localhost:${config.port || 5000}
+
+## Blueprints
+
+${config.blueprints.map(bp => `- **${bp.name}**: ${bp.description || 'No description'} (${bp.urlPrefix})`).join('\n')}
+
+## API Documentation
+
+${config.enableSwagger ? 'Swagger documentation available at /docs' : 'No API documentation configured'}
+`
+
+ return files
+}
diff --git a/src/lib/generators/generateFlaskBlueprint.ts b/src/lib/generators/generateFlaskBlueprint.ts
new file mode 100644
index 0000000..301efae
--- /dev/null
+++ b/src/lib/generators/generateFlaskBlueprint.ts
@@ -0,0 +1,62 @@
+import { FlaskBlueprint } from '@/types/project'
+
+export function generateFlaskBlueprint(blueprint: FlaskBlueprint): string {
+ let code = `from flask import Blueprint, request, jsonify\n`
+ code += `from typing import Dict, Any\n\n`
+
+ const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_')
+ code += `${blueprintVarName}_bp = Blueprint('${blueprintVarName}', __name__, url_prefix='${blueprint.urlPrefix}')\n\n`
+
+ blueprint.endpoints.forEach(endpoint => {
+ const functionName = endpoint.name.toLowerCase().replace(/\s+/g, '_')
+ code += `@${blueprintVarName}_bp.route('${endpoint.path}', methods=['${endpoint.method}'])\n`
+ code += `def ${functionName}():\n`
+ code += ` """\n`
+ code += ` ${endpoint.description || endpoint.name}\n`
+
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
+ code += ` \n Query Parameters:\n`
+ endpoint.queryParams.forEach(param => {
+ code += ` - ${param.name} (${param.type})${param.required ? ' [required]' : ''}: ${param.description || ''}\n`
+ })
+ }
+
+ code += ` """\n`
+
+ if (endpoint.authentication) {
+ code += ` # TODO: Add authentication check\n`
+ code += ` # if not is_authenticated(request):\n`
+ code += ` # return jsonify({'error': 'Unauthorized'}), 401\n\n`
+ }
+
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
+ endpoint.queryParams.forEach(param => {
+ if (param.required) {
+ code += ` ${param.name} = request.args.get('${param.name}')\n`
+ code += ` if ${param.name} is None:\n`
+ code += ` return jsonify({'error': '${param.name} is required'}), 400\n\n`
+ } else {
+ const defaultVal = param.defaultValue || (param.type === 'string' ? "''" : param.type === 'number' ? '0' : 'None')
+ code += ` ${param.name} = request.args.get('${param.name}', ${defaultVal})\n`
+ }
+ })
+ code += `\n`
+ }
+
+ if (endpoint.method === 'POST' || endpoint.method === 'PUT' || endpoint.method === 'PATCH') {
+ code += ` data = request.get_json()\n`
+ code += ` if not data:\n`
+ code += ` return jsonify({'error': 'No data provided'}), 400\n\n`
+ }
+
+ code += ` # TODO: Implement ${endpoint.name} logic\n`
+ code += ` result = {\n`
+ code += ` 'message': '${endpoint.name} endpoint',\n`
+ code += ` 'method': '${endpoint.method}',\n`
+ code += ` 'path': '${endpoint.path}'\n`
+ code += ` }\n\n`
+ code += ` return jsonify(result), 200\n\n\n`
+ })
+
+ return code
+}
diff --git a/src/lib/generators/generateMUITheme.ts b/src/lib/generators/generateMUITheme.ts
new file mode 100644
index 0000000..15d0792
--- /dev/null
+++ b/src/lib/generators/generateMUITheme.ts
@@ -0,0 +1,102 @@
+import { ThemeConfig } from '@/types/project'
+
+export function generateMUITheme(theme: ThemeConfig): string {
+ if (!theme.variants || theme.variants.length === 0) {
+ return `import { createTheme } from '@mui/material/styles';
+
+export const theme = createTheme({
+ palette: {
+ mode: 'light',
+ },
+});`
+ }
+
+ const lightVariant = theme.variants.find((v) => v.id === 'light') || theme.variants[0]
+ const darkVariant = theme.variants.find((v) => v.id === 'dark')
+
+ let themeCode = `import { createTheme } from '@mui/material/styles';
+
+export const lightTheme = createTheme({
+ palette: {
+ mode: 'light',
+ primary: {
+ main: '${lightVariant.colors.primaryColor}',
+ },
+ secondary: {
+ main: '${lightVariant.colors.secondaryColor}',
+ },
+ error: {
+ main: '${lightVariant.colors.errorColor}',
+ },
+ warning: {
+ main: '${lightVariant.colors.warningColor}',
+ },
+ success: {
+ main: '${lightVariant.colors.successColor}',
+ },
+ background: {
+ default: '${lightVariant.colors.background}',
+ paper: '${lightVariant.colors.surface}',
+ },
+ text: {
+ primary: '${lightVariant.colors.text}',
+ secondary: '${lightVariant.colors.textSecondary}',
+ },
+ },
+ typography: {
+ fontFamily: '${theme.fontFamily}',
+ fontSize: ${theme.fontSize.medium},
+ },
+ spacing: ${theme.spacing},
+ shape: {
+ borderRadius: ${theme.borderRadius},
+ },
+});
+`
+
+ if (darkVariant) {
+ themeCode += `
+export const darkTheme = createTheme({
+ palette: {
+ mode: 'dark',
+ primary: {
+ main: '${darkVariant.colors.primaryColor}',
+ },
+ secondary: {
+ main: '${darkVariant.colors.secondaryColor}',
+ },
+ error: {
+ main: '${darkVariant.colors.errorColor}',
+ },
+ warning: {
+ main: '${darkVariant.colors.warningColor}',
+ },
+ success: {
+ main: '${darkVariant.colors.successColor}',
+ },
+ background: {
+ default: '${darkVariant.colors.background}',
+ paper: '${darkVariant.colors.surface}',
+ },
+ text: {
+ primary: '${darkVariant.colors.text}',
+ secondary: '${darkVariant.colors.textSecondary}',
+ },
+ },
+ typography: {
+ fontFamily: '${theme.fontFamily}',
+ fontSize: ${theme.fontSize.medium},
+ },
+ spacing: ${theme.spacing},
+ shape: {
+ borderRadius: ${theme.borderRadius},
+ },
+});
+
+export const theme = lightTheme;`
+ } else {
+ themeCode += `\nexport const theme = lightTheme;`
+ }
+
+ return themeCode
+}
diff --git a/src/lib/generators/generateNextJSProject.ts b/src/lib/generators/generateNextJSProject.ts
new file mode 100644
index 0000000..fe3860a
--- /dev/null
+++ b/src/lib/generators/generateNextJSProject.ts
@@ -0,0 +1,99 @@
+import { ComponentNode, PrismaModel, ThemeConfig } from '@/types/project'
+import { generateMUITheme } from './generateMUITheme'
+import { generatePrismaSchema } from './generatePrismaSchema'
+
+export function generateNextJSProject(
+ projectName: string,
+ models: PrismaModel[],
+ components: ComponentNode[],
+ theme: ThemeConfig
+): Record {
+ const files: Record = {}
+
+ files['package.json'] = JSON.stringify(
+ {
+ name: projectName,
+ version: '0.1.0',
+ private: true,
+ scripts: {
+ dev: 'next dev',
+ build: 'next build',
+ start: 'next start',
+ lint: 'next lint',
+ },
+ dependencies: {
+ '@mui/material': '^5.15.0',
+ '@emotion/react': '^11.11.0',
+ '@emotion/styled': '^11.11.0',
+ '@prisma/client': '^5.8.0',
+ next: '14.1.0',
+ react: '^18.2.0',
+ 'react-dom': '^18.2.0',
+ },
+ devDependencies: {
+ '@types/node': '^20',
+ '@types/react': '^18',
+ '@types/react-dom': '^18',
+ prisma: '^5.8.0',
+ typescript: '^5',
+ },
+ },
+ null,
+ 2
+ )
+
+ files['prisma/schema.prisma'] = generatePrismaSchema(models)
+
+ files['src/theme.ts'] = generateMUITheme(theme)
+
+ files['src/app/page.tsx'] = `'use client'
+
+import { ThemeProvider } from '@mui/material/styles'
+import CssBaseline from '@mui/material/CssBaseline'
+import { theme } from '@/theme'
+
+export default function Home() {
+ return (
+
+
+
+ {/* Your components here */}
+
+
+ )
+}`
+
+ files['next.config.js'] = `/** @type {import('next').NextConfig} */
+const nextConfig = {}
+
+module.exports = nextConfig`
+
+ files['.env'] = `DATABASE_URL="postgresql://user:password@localhost:5432/mydb"`
+
+ files['README.md'] = `# ${projectName}
+
+Generated with CodeForge
+
+## Getting Started
+
+1. Install dependencies:
+\`\`\`bash
+npm install
+\`\`\`
+
+2. Set up your database in .env
+
+3. Run Prisma migrations:
+\`\`\`bash
+npx prisma migrate dev
+\`\`\`
+
+4. Start the development server:
+\`\`\`bash
+npm run dev
+\`\`\`
+
+Open [http://localhost:3000](http://localhost:3000) with your browser.`
+
+ return files
+}
diff --git a/src/lib/generators/generatePlaywrightTests.ts b/src/lib/generators/generatePlaywrightTests.ts
new file mode 100644
index 0000000..31d164a
--- /dev/null
+++ b/src/lib/generators/generatePlaywrightTests.ts
@@ -0,0 +1,56 @@
+import { PlaywrightTest } from '@/types/project'
+
+export function generatePlaywrightTests(tests: PlaywrightTest[]): string {
+ if (tests.length === 0) {
+ return `import { test, expect } from '@playwright/test'
+
+test('example test', async ({ page }) => {
+ await page.goto('/')
+ await expect(page).toHaveTitle(/.*/)
+})`
+ }
+
+ let code = `import { test, expect } from '@playwright/test'\n\n`
+
+ tests.forEach(testSuite => {
+ code += `test.describe('${testSuite.name}', () => {\n`
+ if (testSuite.description) {
+ code += ` // ${testSuite.description}\n`
+ }
+ code += ` test('${testSuite.name}', async ({ page }) => {\n`
+
+ testSuite.steps.forEach(step => {
+ switch (step.action) {
+ case 'navigate':
+ code += ` await page.goto('${testSuite.pageUrl}')\n`
+ break
+ case 'click':
+ code += ` await page.click('${step.selector}')\n`
+ break
+ case 'fill':
+ code += ` await page.fill('${step.selector}', '${step.value}')\n`
+ break
+ case 'expect':
+ code += ` await expect(page.locator('${step.selector}')).${step.assertion}\n`
+ break
+ case 'wait':
+ code += ` await page.waitForTimeout(${step.timeout || 1000})\n`
+ break
+ case 'select':
+ code += ` await page.selectOption('${step.selector}', '${step.value}')\n`
+ break
+ case 'check':
+ code += ` await page.check('${step.selector}')\n`
+ break
+ case 'uncheck':
+ code += ` await page.uncheck('${step.selector}')\n`
+ break
+ }
+ })
+
+ code += ` })\n`
+ code += `})\n\n`
+ })
+
+ return code
+}
diff --git a/src/lib/generators/generatePrismaSchema.ts b/src/lib/generators/generatePrismaSchema.ts
new file mode 100644
index 0000000..d5bcc64
--- /dev/null
+++ b/src/lib/generators/generatePrismaSchema.ts
@@ -0,0 +1,22 @@
+import { PrismaModel } from '@/types/project'
+
+export function generatePrismaSchema(models: PrismaModel[]): string {
+ let schema = `generator client {\n provider = "prisma-client-js"\n}\n\n`
+ schema += `datasource db {\n provider = "postgresql"\n url = env("DATABASE_URL")\n}\n\n`
+
+ models.forEach((model) => {
+ schema += `model ${model.name} {\n`
+ model.fields.forEach((field) => {
+ let fieldLine = ` ${field.name} ${field.type}`
+ if (field.isArray) fieldLine += '[]'
+ if (field.isRequired && !field.defaultValue) fieldLine += ''
+ else if (!field.isRequired) fieldLine += '?'
+ if (field.isUnique) fieldLine += ' @unique'
+ if (field.defaultValue) fieldLine += ` @default(${field.defaultValue})`
+ schema += fieldLine + '\n'
+ })
+ schema += `}\n\n`
+ })
+
+ return schema
+}
diff --git a/src/lib/generators/generateStorybookStories.ts b/src/lib/generators/generateStorybookStories.ts
new file mode 100644
index 0000000..37bdf92
--- /dev/null
+++ b/src/lib/generators/generateStorybookStories.ts
@@ -0,0 +1,40 @@
+import { StorybookStory } from '@/types/project'
+
+export function generateStorybookStories(stories: StorybookStory[]): Record {
+ const fileMap: Record = {}
+
+ stories.forEach(story => {
+ const key = `${story.category}/${story.componentName}`
+ if (!fileMap[key]) {
+ fileMap[key] = []
+ }
+ fileMap[key].push(story)
+ })
+
+ const files: Record = {}
+
+ Object.entries(fileMap).forEach(([path, storyList]) => {
+ const componentName = storyList[0].componentName
+ let code = `import type { Meta, StoryObj } from '@storybook/react'\nimport { ${componentName} } from '@/components/${componentName}'\n\n`
+
+ code += `const meta: Meta = {\n`
+ code += ` title: '${path}',\n`
+ code += ` component: ${componentName},\n`
+ code += ` tags: ['autodocs'],\n`
+ code += `}\n\n`
+ code += `export default meta\n`
+ code += `type Story = StoryObj\n\n`
+
+ storyList.forEach(story => {
+ code += `export const ${story.storyName.replace(/\s+/g, '')}: Story = {\n`
+ if (Object.keys(story.args).length > 0) {
+ code += ` args: ${JSON.stringify(story.args, null, 4).replace(/"/g, "'")},\n`
+ }
+ code += `}\n\n`
+ })
+
+ files[`src/stories/${componentName}.stories.tsx`] = code
+ })
+
+ return files
+}
diff --git a/src/lib/generators/generateUnitTests.ts b/src/lib/generators/generateUnitTests.ts
new file mode 100644
index 0000000..c0fcaa2
--- /dev/null
+++ b/src/lib/generators/generateUnitTests.ts
@@ -0,0 +1,61 @@
+import { UnitTest } from '@/types/project'
+
+export function generateUnitTests(tests: UnitTest[]): Record {
+ const files: Record = {}
+
+ tests.forEach(testSuite => {
+ const fileName = testSuite.targetFile
+ ? testSuite.targetFile.replace(/\.(tsx|ts|jsx|js)$/, '.test.$1')
+ : `src/__tests__/${testSuite.name.replace(/\s+/g, '')}.test.tsx`
+
+ let code = ''
+
+ if (testSuite.testType === 'component') {
+ code += `import { render, screen } from '@testing-library/react'\nimport { describe, it, expect } from 'vitest'\n`
+ if (testSuite.targetFile) {
+ const componentName = testSuite.targetFile.split('/').pop()?.replace(/\.(tsx|ts|jsx|js)$/, '')
+ code += `import { ${componentName} } from '${testSuite.targetFile.replace('.tsx', '').replace('.ts', '')}'\n\n`
+ }
+ } else if (testSuite.testType === 'hook') {
+ code += `import { renderHook } from '@testing-library/react'\nimport { describe, it, expect } from 'vitest'\n`
+ if (testSuite.targetFile) {
+ const hookName = testSuite.targetFile.split('/').pop()?.replace(/\.(tsx|ts|jsx|js)$/, '')
+ code += `import { ${hookName} } from '${testSuite.targetFile.replace('.tsx', '').replace('.ts', '')}'\n\n`
+ }
+ } else {
+ code += `import { describe, it, expect } from 'vitest'\n`
+ if (testSuite.targetFile) {
+ code += `import * as module from '${testSuite.targetFile.replace('.tsx', '').replace('.ts', '')}'\n\n`
+ }
+ }
+
+ code += `describe('${testSuite.name}', () => {\n`
+ if (testSuite.description) {
+ code += ` // ${testSuite.description}\n\n`
+ }
+
+ testSuite.testCases.forEach(testCase => {
+ code += ` it('${testCase.description}', () => {\n`
+
+ if (testCase.setup) {
+ code += ` ${testCase.setup}\n\n`
+ }
+
+ testCase.assertions.forEach(assertion => {
+ code += ` ${assertion}\n`
+ })
+
+ if (testCase.teardown) {
+ code += `\n ${testCase.teardown}\n`
+ }
+
+ code += ` })\n\n`
+ })
+
+ code += `})\n`
+
+ files[fileName] = code
+ })
+
+ return files
+}
diff --git a/src/lib/generators/index.ts b/src/lib/generators/index.ts
new file mode 100644
index 0000000..599274b
--- /dev/null
+++ b/src/lib/generators/index.ts
@@ -0,0 +1,9 @@
+export { generateComponentCode } from './generateComponentCode'
+export { generateFlaskApp } from './generateFlaskApp'
+export { generateFlaskBlueprint } from './generateFlaskBlueprint'
+export { generateMUITheme } from './generateMUITheme'
+export { generateNextJSProject } from './generateNextJSProject'
+export { generatePlaywrightTests } from './generatePlaywrightTests'
+export { generatePrismaSchema } from './generatePrismaSchema'
+export { generateStorybookStories } from './generateStorybookStories'
+export { generateUnitTests } from './generateUnitTests'