Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ create-react-forge is a modular, layered CLI tool for scaffolding production-rea
┌────────────────────▼────────────────────────────────┐
│ Docs Layer │
│ (auto-generates ARCHITECTURE.md for projects)
│ (auto-generates README.md & ARCHITECTURE.md)
│ Location: src/docs/ │
└─────────────────────────────────────────────────────┘
```
Expand Down Expand Up @@ -111,7 +111,7 @@ interface ProjectConfig {
runtime: 'vite' | 'nextjs';
language: 'javascript' | 'typescript';
styling: { solution: 'css' | 'tailwind' | 'styled-components' | 'css-modules' };
stateManagement: 'none' | 'redux' | 'zustand';
stateManagement: 'none' | 'redux' | 'zustand' | 'jotai';
testing: TestingConfig;
dataFetching: DataFetchingConfig;
linting: LintingConfig;
Expand Down Expand Up @@ -144,7 +144,7 @@ interface ProjectConfig {
1. Check if directory exists → fail if yes
2. Load templates based on config
3. Merge all template files
4. Generate ARCHITECTURE.md for the project
4. Generate README.md and ARCHITECTURE.md for the project
5. Merge dependencies and scripts
6. Write files to disk
7. Initialize git (optional)
Expand Down Expand Up @@ -184,7 +184,8 @@ src/templates/overlays/
│ └── styled-components/ # styled-components setup
├── state/
│ ├── redux/ # Redux Toolkit store structure
│ └── zustand/ # Zustand store setup
│ ├── zustand/ # Zustand store setup
│ └── jotai/ # Jotai atomic state management
├── features/
│ └── tanstack-query/ # TanStack Query + hooks pattern
├── testing/
Expand Down Expand Up @@ -221,7 +222,7 @@ interface TemplateManifest {
1. **Base** — Core React files (components, hooks, lib, types)
2. **Runtime** — Vite or Next.js specific configs
3. **Styling** — Tailwind/CSS/Styled Components setup
4. **State** — Redux/Zustand store setup
4. **State** — Redux/Zustand/Jotai store setup
5. **Features** — TanStack Query, etc.
6. **Testing** — Vitest/Jest + RTL + Playwright

Expand Down Expand Up @@ -294,6 +295,7 @@ interface TemplateManifest {
'@reduxjs/toolkit': '^2.5.0',
'react-redux': '^9.2.0',
'zustand': '^5.0.3',
'jotai': '^2.10.0',

// Data Fetching
'@tanstack/react-query': '^5.62.10',
Expand Down Expand Up @@ -371,6 +373,12 @@ interface PluginContext {

#### Files:

- **readme-generator.ts** — Generates README.md
- Dynamic project-specific README with tech stack badges
- Package manager-specific commands
- Available scripts table
- Project structure overview
- Documentation links based on config
- **architecture-generator.ts** — Generates ARCHITECTURE.md
- Creates project-specific documentation
- Documents selected configuration
Expand Down Expand Up @@ -523,7 +531,7 @@ Load Template Overlays (TemplateRegistry)
Merge All Template Files
Generate ARCHITECTURE.md
Generate README.md & ARCHITECTURE.md
Aggregate Dependencies (from manifests)
Expand Down Expand Up @@ -668,16 +676,23 @@ npm run test:coverage # Coverage report

### Styling

Options are conditional based on runtime:

**Vite (4 options)**:
- `tailwind` — Tailwind CSS v4 (recommended)
- `css` — Plain CSS
- `styled-components` — CSS-in-JS
- `css-modules` — Scoped CSS
- `css` — Plain CSS

**Next.js**:
- `tailwind` — Auto-selected (recommended for App Router)

### State Management

- `none` — No setup (default)
- `redux` — Redux Toolkit
- `zustand` — Lightweight alternative
- `jotai` — Primitive and flexible atomic state

### Testing

Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ The CLI is **prompt-driven**. You'll choose:
- **Project directory**
- **Runtime**: Vite or Next.js
- **Language**: TypeScript or JavaScript
- **Styling**: Tailwind, CSS, Styled Components, or CSS Modules
- **State**: none, Zustand, or Redux Toolkit
- **Styling**: Tailwind, Styled Components, CSS Modules, or Plain CSS (Vite offers all 4; Next.js auto-selects Tailwind)
- **State**: none, Zustand, Jotai, or Redux Toolkit
- **Testing**: full (unit+component+E2E), unit+component only, or none
- **Unit runner**: Vitest or Jest (if testing enabled)
- **E2E runner**: Playwright or Cypress (if full testing)
Expand All @@ -72,6 +72,7 @@ my-app/
│ ├── testing/ # Test utilities, mocks (if selected)
│ └── types/ # Shared types
├── tests/ # E2E tests (if selected)
├── README.md # Auto-generated project README
├── ARCHITECTURE.md # Auto-generated architecture docs
└── [config files]
```
Expand All @@ -82,8 +83,8 @@ my-app/
|---|---|
| **Runtime** | `vite`, `nextjs` |
| **Language** | `typescript`, `javascript` |
| **Styling** | `tailwind`, `css`, `styled-components`, `css-modules` |
| **State** | `none`, `zustand`, `redux` |
| **Styling** | `tailwind`, `styled-components`, `css-modules`, `css` (Vite: all 4, Next.js: tailwind only) |
| **State** | `none`, `zustand`, `jotai`, `redux` |
| **Testing** | `full`, `unit-component`, `none` |
| **Unit runner** | `vitest`, `jest` |
| **E2E runner** | `playwright`, `cypress` |
Expand Down
101 changes: 101 additions & 0 deletions src/__tests__/conditional-prompts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { describe, expect, it } from 'vitest';
import { STATE_DESCRIPTIONS, STYLING_DESCRIPTIONS } from '../config/defaults';
import { StateManagementSchema, StylingSchema } from '../config/schema';

/**
* Tests for conditional prompts logic
* These tests verify the configuration and validation for runtime-dependent styling options
*/
describe('Conditional Prompts Configuration', () => {
describe('Styling options', () => {
it('should have all 4 styling options defined', () => {
const stylingValues = StylingSchema.options;

expect(stylingValues).toContain('css');
expect(stylingValues).toContain('tailwind');
expect(stylingValues).toContain('styled-components');
expect(stylingValues).toContain('css-modules');
expect(stylingValues).toHaveLength(4);
});

it('should have descriptions for all styling options', () => {
expect(STYLING_DESCRIPTIONS.css).toBeDefined();
expect(STYLING_DESCRIPTIONS.tailwind).toBeDefined();
expect(STYLING_DESCRIPTIONS['styled-components']).toBeDefined();
expect(STYLING_DESCRIPTIONS['css-modules']).toBeDefined();
});

it('Vite should support all 4 styling options', () => {
// For Vite, all 4 options should be valid
const viteOptions = ['tailwind', 'styled-components', 'css-modules', 'css'];

viteOptions.forEach((option) => {
const result = StylingSchema.safeParse(option);
expect(result.success).toBe(true);
});
});

it('Next.js should use tailwind (auto-selected)', () => {
// For Next.js, tailwind is auto-selected
const nextjsDefault = 'tailwind';
const result = StylingSchema.safeParse(nextjsDefault);

expect(result.success).toBe(true);
});
});

describe('State management options', () => {
it('should have all state management options including jotai', () => {
const stateValues = StateManagementSchema.options;

expect(stateValues).toContain('none');
expect(stateValues).toContain('redux');
expect(stateValues).toContain('zustand');
expect(stateValues).toContain('jotai');
expect(stateValues).toHaveLength(4);
});

it('should have descriptions for all state management options', () => {
expect(STATE_DESCRIPTIONS.none).toBeDefined();
expect(STATE_DESCRIPTIONS.redux).toBeDefined();
expect(STATE_DESCRIPTIONS.zustand).toBeDefined();
expect(STATE_DESCRIPTIONS.jotai).toBeDefined();
});

it('should validate jotai as a valid state management option', () => {
const result = StateManagementSchema.safeParse('jotai');
expect(result.success).toBe(true);
});
});

describe('Styling choices logic', () => {
/**
* Helper to get styling choices based on runtime
*/
function getStylingChoicesForRuntime(runtime: 'vite' | 'nextjs'): string[] {
if (runtime === 'vite') {
return ['tailwind', 'styled-components', 'css-modules', 'css'];
}
// Next.js auto-selects tailwind
return ['tailwind'];
}

it('should return 4 styling options for Vite', () => {
const choices = getStylingChoicesForRuntime('vite');

expect(choices).toHaveLength(4);
expect(choices).toContain('tailwind');
expect(choices).toContain('styled-components');
expect(choices).toContain('css-modules');
expect(choices).toContain('css');
});

it('should return only tailwind for Next.js', () => {
const choices = getStylingChoicesForRuntime('nextjs');

expect(choices).toHaveLength(1);
expect(choices).toContain('tailwind');
});
});
});

50 changes: 48 additions & 2 deletions src/__tests__/integration/template-loading.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('TemplateRegistry', () => {
expect(template.manifest.devDependencies).toHaveProperty('tailwindcss');
});

it.skip('should load css-modules styling template', () => {
it('should load css-modules styling template', () => {
const template = registry.loadAndRegister('styling/css-modules');

expect(template).toBeDefined();
Expand All @@ -70,6 +70,22 @@ describe('TemplateRegistry', () => {
expect(template.manifest.dependencies).toHaveProperty('@reduxjs/toolkit');
});

it('should load jotai state template', () => {
const template = registry.loadAndRegister('state/jotai');

expect(template).toBeDefined();
expect(template.manifest).toBeDefined();
expect(template.manifest.dependencies).toHaveProperty('jotai');
});

it('should load styled-components styling template', () => {
const template = registry.loadAndRegister('styling/styled-components');

expect(template).toBeDefined();
expect(template.manifest).toBeDefined();
expect(template.manifest.dependencies).toHaveProperty('styled-components');
});

it('should load vitest testing template', () => {
const template = registry.loadAndRegister('testing/vitest');

Expand Down Expand Up @@ -151,6 +167,32 @@ describe('TemplateRegistry', () => {
expect(templates.some(t => t.path === 'state/redux')).toBe(true);
});

it('should load templates for Vite + Jotai config', () => {
const templates = registry.loadTemplatesForConfig({
runtime: 'vite',
styling: { solution: 'styled-components' },
stateManagement: 'jotai',
testing: { enabled: false, e2e: { enabled: false, runner: 'none' } },
dataFetching: { enabled: false },
});

expect(templates.some(t => t.path === 'runtime/vite')).toBe(true);
expect(templates.some(t => t.path === 'state/jotai')).toBe(true);
expect(templates.some(t => t.path === 'styling/styled-components')).toBe(true);
});

it('should load templates for Vite + CSS Modules config', () => {
const templates = registry.loadTemplatesForConfig({
runtime: 'vite',
styling: { solution: 'css-modules' },
stateManagement: 'none',
testing: { enabled: false, e2e: { enabled: false, runner: 'none' } },
dataFetching: { enabled: false },
});

expect(templates.some(t => t.path === 'styling/css-modules')).toBe(true);
});

it('should load testing templates when testing is enabled', () => {
const templates = registry.loadTemplatesForConfig({
runtime: 'vite',
Expand Down Expand Up @@ -279,9 +321,11 @@ describe('TemplateRegistry', () => {
'runtime/vite',
'runtime/nextjs',
'styling/tailwind',
// 'styling/css-modules', // Skipped - not available in CLI
'styling/css-modules',
'styling/styled-components',
'state/zustand',
'state/redux',
'state/jotai',
'testing/vitest',
'testing/jest',
'testing/playwright',
Expand All @@ -301,6 +345,8 @@ describe('TemplateRegistry', () => {
const templatePaths = [
'state/zustand',
'state/redux',
'state/jotai',
'styling/styled-components',
'testing/vitest',
'testing/playwright',
'features/tanstack-query',
Expand Down
Loading
Loading