Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:29:07 +08:00
commit 8b4a1b1a99
75 changed files with 18583 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
---
name: grey-haven-project-scaffolding
description: "Generate production-ready project scaffolds for Grey Haven stack with Cloudflare Workers, React + TypeScript, Python + Pydantic, PlanetScale, proper structure, and configuration. Use when starting new projects, creating microservices, setting up monorepo workspaces, initializing projects, or when user mentions 'new project', 'project scaffold', 'project template', 'project setup', 'bootstrap project', 'project starter', or 'initialize project'."
---
# Project Scaffolding Skill
Generate production-ready project scaffolds for Grey Haven stack (Cloudflare Workers, React + TypeScript, Python + Pydantic, PlanetScale).
## Description
Rapid project initialization with best practices, proper structure, and configuration for Grey Haven technology stack.
## What's Included
- **Examples**: Full-stack app scaffolds, API-only projects, frontend templates
- **Reference**: Project structure conventions, configuration guides
- **Templates**: Project templates for different stacks
- **Checklists**: Scaffold verification, deployment readiness
## Use When
- Starting new projects
- Creating microservices
- Setting up monorepo workspaces
## Related Agents
- `project-scaffolder`
**Skill Version**: 1.0

View File

@@ -0,0 +1,711 @@
# Grey Haven Project Setup Checklist
Comprehensive checklist for scaffolding new Grey Haven projects with TanStack Start, FastAPI, or both.
## Pre-Project Planning
- [ ] **Define project scope** (MVP features, future roadmap)
- [ ] **Choose architecture**:
- [ ] TanStack Start only (frontend + BFF)
- [ ] FastAPI only (backend API)
- [ ] Full-stack (TanStack Start + FastAPI)
- [ ] Monorepo or separate repos
- [ ] **Define multi-tenant strategy**:
- [ ] Single-tenant (one customer)
- [ ] Multi-tenant (multiple customers)
- [ ] Tenant isolation: subdomain, custom domain, path-based
- [ ] **Plan authentication**:
- [ ] Better Auth (recommended for Grey Haven)
- [ ] OAuth providers (Google, GitHub, etc.)
- [ ] Email/password
- [ ] Magic links
- [ ] **Database choice**:
- [ ] PostgreSQL (recommended for Grey Haven)
- [ ] MySQL
- [ ] SQLite (development only)
- [ ] **Hosting platform**:
- [ ] Vercel (TanStack Start)
- [ ] Railway (FastAPI)
- [ ] AWS (ECS, Lambda)
- [ ] Self-hosted
## Repository Setup
### Initialize Git
- [ ] **Create repository** (GitHub, GitLab, Bitbucket)
- [ ] **Initialize git**: `git init`
- [ ] **Add .gitignore**:
```
node_modules/
.env
.env.local
__pycache__/
*.pyc
.venv/
dist/
.output/
.vercel/
.DS_Store
```
- [ ] **Initial commit**: `git commit -m "Initial commit"`
- [ ] **Create dev branch**: `git checkout -b dev`
- [ ] **Set up branch protection** (require PRs for main)
### Project Structure
- [ ] **Create standard directories**:
```
.
├── .claude/ # Claude Code config (optional)
├── apps/ # Monorepo applications
│ ├── web/ # TanStack Start app
│ └── api/ # FastAPI app
├── packages/ # Shared packages (monorepo)
│ ├── shared-types/ # TypeScript types
│ ├── ui/ # Shared UI components
│ └── utils/ # Shared utilities
├── docs/ # Documentation
├── scripts/ # Automation scripts
└── .github/ # GitHub workflows
└── workflows/
```
- [ ] **Add README.md** with:
- [ ] Project description
- [ ] Tech stack
- [ ] Getting started guide
- [ ] Environment variables
- [ ] Deployment instructions
## TanStack Start Setup
### Installation
- [ ] **Create Vite project**:
```bash
npm create vite@latest my-app -- --template react-ts
cd my-app
```
- [ ] **Install TanStack Start**:
```bash
npm install @tanstack/react-router @tanstack/react-query
npm install -D @tanstack/router-vite-plugin @tanstack/router-devtools
```
- [ ] **Install dependencies**:
```bash
npm install zod drizzle-orm @better-auth/react
npm install -D drizzle-kit tailwindcss postcss autoprefixer
```
### Configure Vite
- [ ] **Update vite.config.ts**:
```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-vite-plugin'
export default defineConfig({
plugins: [
react(),
TanStackRouterVite()
],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
}
})
```
### Configure TailwindCSS
- [ ] **Initialize Tailwind**:
```bash
npx tailwindcss init -p
```
- [ ] **Update tailwind.config.js**:
```javascript
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
```
- [ ] **Add Tailwind directives** to `src/index.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
### Setup Drizzle (Database)
- [ ] **Install Drizzle**:
```bash
npm install drizzle-orm postgres
npm install -D drizzle-kit
```
- [ ] **Create drizzle.config.ts**:
```typescript
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
})
```
- [ ] **Create schema file** `src/db/schema.ts`:
```typescript
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
tenantId: uuid('tenant_id').notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
})
export const tenants = pgTable('tenants', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
slug: text('slug').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
})
```
- [ ] **Create migration**: `npx drizzle-kit generate`
- [ ] **Run migration**: `npx drizzle-kit migrate`
### Setup Better Auth
- [ ] **Install Better Auth**:
```bash
npm install better-auth @better-auth/react
```
- [ ] **Create auth config** `src/lib/auth.ts`:
```typescript
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: 'pg'
}),
emailAndPassword: {
enabled: true
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
}
}
})
```
- [ ] **Create auth context** for React
- [ ] **Add protected route wrapper**
### Router Setup
- [ ] **Create routes directory** `src/routes/`
- [ ] **Create index route** `src/routes/index.tsx`
- [ ] **Create auth routes** (login, register, logout)
- [ ] **Create protected routes** (dashboard, settings)
- [ ] **Configure router** in `src/main.tsx`
### Environment Variables
- [ ] **Create .env.local**:
```
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
VITE_API_URL=http://localhost:8000
```
- [ ] **Create .env.example** (without secrets)
- [ ] **Add to .gitignore**: `.env.local`
## FastAPI Setup
### Installation
- [ ] **Create project directory**:
```bash
mkdir my-api && cd my-api
```
- [ ] **Setup Python virtual environment**:
```bash
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
```
- [ ] **Install FastAPI and dependencies**:
```bash
pip install fastapi uvicorn sqlmodel psycopg2-binary pydantic python-dotenv
pip install pytest pytest-asyncio httpx # Testing
```
- [ ] **Create requirements.txt**:
```bash
pip freeze > requirements.txt
```
### Project Structure
- [ ] **Create standard structure**:
```
my-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app
│ ├── models/ # SQLModel models
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── tenant.py
│ ├── repositories/ # Data access layer
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── user_repository.py
│ ├── services/ # Business logic
│ │ ├── __init__.py
│ │ └── user_service.py
│ ├── api/ # API routes
│ │ ├── __init__.py
│ │ ├── deps.py # Dependencies
│ │ └── v1/
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── auth.py
│ ├── core/ # Config, security
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── security.py
│ └── db/ # Database
│ ├── __init__.py
│ └── session.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ └── test_users.py
├── alembic/ # Migrations
├── .env
└── requirements.txt
```
### Database Setup (SQLModel)
- [ ] **Create SQLModel models** `app/models/user.py`:
```python
from sqlmodel import SQLModel, Field
from uuid import UUID, uuid4
from datetime import datetime
class User(SQLModel, table=True):
__tablename__ = "users"
id: UUID = Field(default_factory=uuid4, primary_key=True)
email: str = Field(unique=True, index=True)
name: str
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
```
- [ ] **Create database session** `app/db/session.py`:
```python
from sqlmodel import create_engine, Session
from app.core.config import settings
engine = create_engine(settings.DATABASE_URL, echo=True)
def get_session():
with Session(engine) as session:
yield session
```
- [ ] **Create tables**: `SQLModel.metadata.create_all(engine)`
### Repository Pattern
- [ ] **Create base repository** `app/repositories/base.py`:
```python
from typing import Generic, TypeVar, Type, Optional, List
from sqlmodel import Session, select
from uuid import UUID
ModelType = TypeVar("ModelType", bound=SQLModel)
class BaseRepository(Generic[ModelType]):
def __init__(self, model: Type[ModelType], session: Session):
self.model = model
self.session = session
def get(self, id: UUID) -> Optional[ModelType]:
return self.session.get(self.model, id)
def get_all(self, tenant_id: UUID) -> List[ModelType]:
statement = select(self.model).where(
self.model.tenant_id == tenant_id
)
return self.session.exec(statement).all()
def create(self, obj: ModelType) -> ModelType:
self.session.add(obj)
self.session.commit()
self.session.refresh(obj)
return obj
```
- [ ] **Create specific repositories** (UserRepository, TenantRepository)
### API Routes
- [ ] **Create main app** `app/main.py`:
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1 import users, auth
app = FastAPI(title="My API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
@app.get("/health")
def health_check():
return {"status": "healthy"}
```
- [ ] **Create route handlers** `app/api/v1/users.py`
- [ ] **Add dependency injection** for tenant_id, user auth
### Environment Variables
- [ ] **Create .env**:
```
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
SECRET_KEY=generate-with-openssl-rand-hex-32
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
CORS_ORIGINS=http://localhost:3000
```
- [ ] **Create config** `app/core/config.py`:
```python
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
DATABASE_URL: str
SECRET_KEY: str
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
class Config:
env_file = ".env"
settings = Settings()
```
## Testing Setup
### TypeScript/Vitest (Frontend)
- [ ] **Install Vitest**:
```bash
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom
```
- [ ] **Create vitest.config.ts**:
```typescript
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts'
}
})
```
- [ ] **Add test script** to package.json: `"test": "vitest"`
- [ ] **Create sample test** `src/test/example.test.ts`
### Pytest (Backend)
- [ ] **Create conftest.py** with test fixtures:
```python
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, create_engine, SQLModel
from app.main import app
from app.db.session import get_session
@pytest.fixture(name="session")
def session_fixture():
engine = create_engine("sqlite:///:memory:")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
@pytest.fixture(name="client")
def client_fixture(session: Session):
def get_session_override():
return session
app.dependency_overrides[get_session] = get_session_override
client = TestClient(app)
yield client
app.dependency_overrides.clear()
```
- [ ] **Add test script**: `pytest tests/ -v`
- [ ] **Create sample test** `tests/test_users.py`
## Linting & Formatting
### TypeScript (ESLint + Prettier)
- [ ] **Install ESLint**:
```bash
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
```
- [ ] **Install Prettier**:
```bash
npm install -D prettier eslint-config-prettier
```
- [ ] **Create .eslintrc.json**
- [ ] **Create .prettierrc**
- [ ] **Add scripts** to package.json:
```json
"lint": "eslint src --ext ts,tsx",
"format": "prettier --write src"
```
### Python (Ruff + Black)
- [ ] **Install Ruff and Black**:
```bash
pip install ruff black
```
- [ ] **Create pyproject.toml**:
```toml
[tool.black]
line-length = 100
[tool.ruff]
line-length = 100
select = ["E", "F", "I"]
```
- [ ] **Add to requirements.txt** (dev dependencies)
## CI/CD Setup
### GitHub Actions
- [ ] **Create workflow** `.github/workflows/ci.yml`:
```yaml
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
jobs:
test-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: pytest tests/ -v
```
- [ ] **Add deployment workflow** (Vercel, Railway, AWS)
## Database Migrations
- [ ] **Setup Drizzle migrations** (TanStack Start):
- [ ] `npx drizzle-kit generate` to create migrations
- [ ] `npx drizzle-kit migrate` to apply migrations
- [ ] **Setup Alembic** (FastAPI):
```bash
pip install alembic
alembic init alembic
```
- [ ] Configure alembic.ini with DATABASE_URL
- [ ] Create migration: `alembic revision --autogenerate -m "message"`
- [ ] Apply migration: `alembic upgrade head`
## Secrets Management
- [ ] **Choose secrets manager**:
- [ ] Doppler (recommended for Grey Haven)
- [ ] AWS Secrets Manager
- [ ] Environment variables (for local dev only)
- [ ] **Install Doppler CLI** (if using Doppler):
```bash
# Install: https://docs.doppler.com/docs/install-cli
doppler login
doppler setup
```
- [ ] **Never commit secrets** to git
- [ ] **Use .env.example** for documentation
## Deployment
### Vercel (TanStack Start)
- [ ] **Install Vercel CLI**: `npm i -g vercel`
- [ ] **Connect to Vercel**: `vercel link`
- [ ] **Configure environment variables** in Vercel dashboard
- [ ] **Deploy**: `vercel --prod`
- [ ] **Setup custom domain** (if needed)
### Railway (FastAPI)
- [ ] **Install Railway CLI**: `npm i -g @railway/cli`
- [ ] **Login**: `railway login`
- [ ] **Initialize**: `railway init`
- [ ] **Add environment variables**: `railway variables`
- [ ] **Deploy**: `railway up`
## Multi-Tenant Configuration
- [ ] **Add tenant_id** to all relevant tables
- [ ] **Create RLS policies** (if using PostgreSQL RLS)
- [ ] **Repository pattern** enforces tenant filtering
- [ ] **Subdomain routing** (if applicable):
- [ ] tenant1.myapp.com → tenant_id = uuid1
- [ ] tenant2.myapp.com → tenant_id = uuid2
- [ ] **Tenant signup flow**:
- [ ] Create tenant record
- [ ] Create owner user
- [ ] Associate user with tenant
- [ ] Generate invitation links
## Monitoring & Observability
- [ ] **Setup error tracking** (Sentry, Datadog)
- [ ] **Add structured logging** (Pino for Node, structlog for Python)
- [ ] **Setup metrics** (Prometheus, Datadog)
- [ ] **Create health check endpoint** (`/health`)
- [ ] **Setup uptime monitoring** (Pingdom, UptimeRobot)
## Documentation
- [ ] **README.md** with setup instructions
- [ ] **API documentation** (auto-generated with FastAPI `/docs`)
- [ ] **Architecture diagram** (optional, but recommended)
- [ ] **Environment variables** documented in .env.example
- [ ] **Contributing guide** (if open-source or team project)
## Scoring
- **80+ items checked**: Excellent - Production-ready setup ✅
- **60-79 items**: Good - Most setup complete ⚠️
- **40-59 items**: Fair - Missing important pieces 🔴
- **<40 items**: Poor - Not ready for development ❌
## Priority Items
Complete these first:
1. **Repository setup** - Git, structure, README
2. **Database schema** - Models, migrations
3. **Authentication** - Better Auth, protected routes
4. **Testing setup** - Vitest, pytest
5. **CI/CD** - GitHub Actions, deployment
## Common Pitfalls
**Don't:**
- Commit .env files (use .env.example instead)
- Skip testing setup (add it from day one)
- Ignore linting (consistent code quality matters)
- Deploy without health checks
- Skip multi-tenant isolation (add tenant_id early)
**Do:**
- Use repository pattern for data access
- Set up CI/CD early (automate testing)
- Document environment variables
- Test authentication thoroughly
- Plan for scale (database indexes, caching)
## Related Resources
- [TanStack Start Documentation](https://tanstack.com/start)
- [FastAPI Documentation](https://fastapi.tiangolo.com)
- [Better Auth Documentation](https://better-auth.com)
- [Drizzle ORM Documentation](https://orm.drizzle.team)
- [project-scaffolding skill](../SKILL.md)
---
**Total Items**: 130+ setup checks
**Critical Items**: Repository, Database, Auth, Testing, Deployment
**Coverage**: TanStack Start, FastAPI, Multi-tenant, Testing, CI/CD
**Last Updated**: 2025-11-10

View File

@@ -0,0 +1,272 @@
# Scaffold Quality Checklist
Comprehensive checklist for validating generated scaffolds before delivery.
---
## Configuration Files
### TypeScript Projects
- [ ] **package.json** present with correct project name
- [ ] **tsconfig.json** with strict mode enabled
- [ ] **Scripts** configured (dev, build, test, deploy)
- [ ] **Dependencies** at correct versions
- [ ] **devDependencies** include linting/testing tools
### Python Projects
- [ ] **pyproject.toml** present with project metadata
- [ ] **Dependencies** specified with version ranges
- [ ] **Dev dependencies** include pytest, ruff, mypy
- [ ] **Tool configurations** (ruff, mypy, pytest) configured
### All Projects
- [ ] **.gitignore** includes node_modules, .env, build artifacts
- [ ] **.env.example** provided for environment variables
- [ ] **README.md** with setup instructions
- [ ] **License file** (if applicable)
---
## Source Code Structure
### Directory Organization
- [ ] **src/** directory exists
- [ ] **routes/** for API endpoints or pages
- [ ] **components/** for UI components (frontend)
- [ ] **services/** for business logic
- [ ] **utils/** for helper functions
- [ ] **types/** for TypeScript definitions
### Entry Points
- [ ] **Main file** exists (index.ts, main.py, App.tsx)
- [ ] **Exports** correctly configured
- [ ] **Health check endpoint** implemented
- [ ] **Error handling** middleware included
---
## Code Quality
### TypeScript
- [ ] **Strict mode** enabled in tsconfig.json
- [ ] **Type annotations** on all functions
- [ ] **Interfaces** defined for props/config
- [ ] **ESLint** configuration present
- [ ] **Prettier** configuration present
- [ ] **No `any` types** (except explicit)
### Python
- [ ] **Type hints** on all functions
- [ ] **Pydantic models** for validation
- [ ] **Async/await** used correctly
- [ ] **Docstrings** on public functions
- [ ] **Ruff configuration** present
- [ ] **mypy strict mode** enabled
### All Languages
- [ ] **Consistent naming** (camelCase, snake_case)
- [ ] **No hard-coded secrets** or API keys
- [ ] **Environment variables** used correctly
- [ ] **Error handling** implemented
- [ ] **Logging** configured
---
## Testing
### Test Files
- [ ] **tests/** directory exists
- [ ] **Test files** mirror src/ structure
- [ ] **Test fixtures** configured (conftest.py, setup.ts)
- [ ] **Coverage** configuration present
### Test Quality
- [ ] **Sample tests** included
- [ ] **Tests pass** out of the box
- [ ] **Health check test** present
- [ ] **Test commands** in package.json/pyproject.toml
---
## Deployment
### Cloudflare Workers
- [ ] **wrangler.toml** configured
- [ ] **Database bindings** defined (if D1)
- [ ] **Environment** sections (production, staging)
- [ ] **Secrets** documented in README
### Cloudflare Pages
- [ ] **Build command** configured
- [ ] **Output directory** specified
- [ ] **Environment variables** documented
### Python
- [ ] **Dockerfile** (if containerized)
- [ ] **Requirements** frozen
- [ ] **Database migrations** configured (Alembic)
---
## Documentation
### README.md
- [ ] **Project description** clear
- [ ] **Quick start** instructions
- [ ] **Setup steps** documented
- [ ] **Development** commands listed
- [ ] **Deployment** instructions
- [ ] **Environment variables** documented
- [ ] **API endpoints** listed (if API)
### Additional Docs
- [ ] **Architecture** diagram/description (full-stack)
- [ ] **API documentation** (FastAPI auto-docs, etc.)
- [ ] **Contributing** guidelines (if open source)
---
## Security
### Secrets Management
- [ ] **No secrets** committed to git
- [ ] **.env** in .gitignore
- [ ] **.env.example** provided
- [ ] **Secret management** documented
### Authentication
- [ ] **Auth middleware** included (if applicable)
- [ ] **JWT handling** implemented correctly
- [ ] **CORS** configured properly
### Input Validation
- [ ] **Zod/Pydantic** validation on inputs
- [ ] **SQL injection** prevention (parameterized queries)
- [ ] **XSS prevention** (sanitized outputs)
---
## Dependencies
### Version Management
- [ ] **Package versions** pinned or ranged appropriately
- [ ] **No deprecated** packages
- [ ] **Security** vulnerabilities checked
- [ ] **License** compatibility verified
### Peer Dependencies
- [ ] **React version** compatible (if React)
- [ ] **Node version** specified (engines field)
- [ ] **Python version** specified (requires-python)
---
## CI/CD
### GitHub Actions
- [ ] **.github/workflows/** directory exists
- [ ] **Test workflow** configured
- [ ] **Deploy workflow** configured
- [ ] **Lint workflow** configured
### Workflow Quality
- [ ] **Tests run** on PR
- [ ] **Deployment** on main branch
- [ ] **Secrets** properly configured
- [ ] **Environment variables** set
---
## User Experience
### Developer Experience
- [ ] **Setup time** < 5 minutes
- [ ] **All commands work** (dev, test, build)
- [ ] **Hot reload** functional
- [ ] **Error messages** helpful
### Production Readiness
- [ ] **Health endpoint** returns 200
- [ ] **Error handling** doesn't expose internals
- [ ] **Logging** configured
- [ ] **Monitoring** hooks present
---
## Checklist Summary
### Must Have (Critical)
- ✅ Configuration files present and correct
- ✅ Source code structure follows Grey Haven conventions
- ✅ Tests included and passing
- ✅ README with setup instructions
- ✅ No secrets committed
### Should Have (Important)
- ✅ Type safety (TypeScript strict, Python type hints)
- ✅ Linting and formatting configured
- ✅ CI/CD pipeline included
- ✅ Health check endpoint
- ✅ Error handling
### Nice to Have (Optional)
- ✅ Architecture documentation
- ✅ API documentation
- ✅ Storybook (components)
- ✅ Database migrations
- ✅ Monitoring setup
---
## Quick Validation Script
```bash
#!/bin/bash
# Quick scaffold validation
echo "Checking scaffold quality..."
# Check files exist
test -f package.json && echo "✅ package.json" || echo "❌ package.json"
test -f README.md && echo "✅ README.md" || echo "✅ README.md"
test -d src && echo "✅ src/" || echo "❌ src/"
test -d tests && echo "✅ tests/" || echo "❌ tests/"
# Check no secrets
! grep -r "api[_-]key" . && echo "✅ No API keys" || echo "⚠️ API key found"
# Install and test
npm install && npm test && echo "✅ Tests pass" || echo "❌ Tests fail"
```
---
**Version**: 1.0
**Last Updated**: 2024-01-15

View File

@@ -0,0 +1,225 @@
# Project Scaffolder Examples
Real-world examples of scaffolding production-ready projects with Grey Haven stack.
---
## Quick Navigation
### Scaffold Types
| Example | Stack | Time | Files | Description |
|---------|-------|------|-------|-------------|
| [Cloudflare Worker API](cloudflare-worker-scaffold-example.md) | Hono + TypeScript + D1 | 15 min | 18 | Production API with auth, logging, tests |
| [React Component](react-component-scaffold-example.md) | React + TypeScript + Vitest | 5 min | 6 | Reusable component with tests, stories |
| [Python API](python-api-scaffold-example.md) | FastAPI + Pydantic + PostgreSQL | 20 min | 22 | Async API with validation, migrations |
| [Full-Stack App](full-stack-scaffold-example.md) | React + Worker + D1 | 30 min | 35 | Complete app with frontend/backend |
---
## What's Included in Each Example
### Structure
- **Complete file tree** - Every file that gets created
- **Configuration files** - Package management, tooling, deployment
- **Source code** - Production-ready starting point
- **Tests** - Pre-written test suites
- **Documentation** - README with next steps
### Tooling
- **Type Safety** - TypeScript strict mode, Pydantic validation
- **Testing** - Vitest for TS/JS, pytest for Python
- **Linting** - ESLint, Prettier, Ruff
- **CI/CD** - GitHub Actions workflows
- **Deployment** - Cloudflare Pages/Workers config
---
## Scaffold Comparison
### When to Use Each
| Use Case | Scaffold | Why |
|----------|----------|-----|
| **REST API** | Cloudflare Worker | Fast, serverless, global edge deployment |
| **GraphQL API** | Cloudflare Worker | Hono supports GraphQL, D1 for persistence |
| **Web App** | Full-Stack | Frontend + backend in monorepo |
| **Static Site** | React Component | Build with Vite, deploy to Pages |
| **Background Jobs** | Python API | Long-running tasks, async processing |
| **Data Pipeline** | Python API | ETL, data validation with Pydantic |
---
## Quick Reference
### Cloudflare Worker API
```bash
# Generate
scaffold-worker --name my-api
# Structure
my-api/
├── src/
│ ├── index.ts # Hono app
│ ├── routes/ # API handlers
│ └── middleware/ # Auth, CORS
├── tests/
├── wrangler.toml
└── package.json
# Deploy
cd my-api && npm install && npm run deploy
```
### React Component
```bash
# Generate
scaffold-component --name Button --path src/components
# Structure
src/components/Button/
├── Button.tsx # Implementation
├── Button.test.tsx # Tests
├── Button.stories.tsx # Storybook
└── Button.module.css # Styles
```
### Python API
```bash
# Generate
scaffold-python --name my-api
# Structure
my-api/
├── app/
│ ├── main.py # FastAPI
│ ├── schemas/ # Pydantic
│ └── models/ # SQLAlchemy
├── tests/
├── pyproject.toml
└── alembic/
# Run
cd my-api && uv venv && uv pip install -e .[dev] && uvicorn app.main:app
```
### Full-Stack App
```bash
# Generate
scaffold-fullstack --name my-app
# Structure
my-app/
├── frontend/ # React + Vite
├── backend/ # Worker
└── docs/
# Dev
cd my-app && npm install && npm run dev
```
---
## Common Patterns
### All Scaffolds Include
**Configuration**:
- ✅ TypeScript/Python type checking
- ✅ Linting (ESLint/Ruff)
- ✅ Formatting (Prettier)
- ✅ Testing framework
- ✅ Git ignore rules
**Development**:
- ✅ Local development server
- ✅ Hot reload
- ✅ Environment variables
- ✅ Debug configuration
**Production**:
- ✅ Build optimization
- ✅ Deployment configuration
- ✅ Error handling
- ✅ Logging setup
---
## Grey Haven Conventions Applied
### Naming
- Components: `PascalCase` (Button, UserProfile)
- Files: `kebab-case` for routes, `PascalCase` for components
- Variables: `camelCase` (userId, isActive)
- Constants: `UPPER_SNAKE_CASE` (API_URL, MAX_RETRIES)
- Database: `snake_case` (user_profiles, api_keys)
### Structure
```
src/
├── routes/ # API endpoints or page routes
├── components/ # Reusable UI components
├── services/ # Business logic
├── utils/ # Pure helper functions
└── types/ # TypeScript type definitions
tests/ # Mirror src/ structure
├── routes/
├── components/
└── services/
```
### Dependencies
- **Package Manager**: npm (Node.js), uv (Python)
- **Frontend**: Vite + React + TypeScript + TanStack
- **Backend**: Cloudflare Workers + Hono
- **Database**: PlanetScale PostgreSQL
- **Testing**: Vitest (TS), pytest (Python)
- **Validation**: Zod (TS), Pydantic (Python)
---
## Metrics
### Scaffold Generation Speed
| Scaffold | Files Created | LOC | Time |
|----------|--------------|-----|------|
| Cloudflare Worker | 18 | ~450 | 15 min |
| React Component | 6 | ~120 | 5 min |
| Python API | 22 | ~600 | 20 min |
| Full-Stack | 35 | ~850 | 30 min |
### Developer Productivity Gains
**Before Scaffolding**:
- Setup time: 2-4 hours
- Configuration errors: Common
- Inconsistent structure: Yes
- Missing best practices: Often
**After Scaffolding**:
- Setup time: 5-30 minutes
- Configuration errors: Rare
- Consistent structure: Always
- Best practices: Built-in
**Time Savings**: 80-90% reduction in project setup time
---
## Next Steps
After scaffolding:
1. **Review generated code** - Understand structure and conventions
2. **Customize for your needs** - Modify templates, add features
3. **Run tests** - Verify everything works: `npm test` or `pytest`
4. **Start development** - Add your business logic
5. **Deploy** - Use provided deployment configuration
---
**Total Examples**: 4 complete scaffold types
**Coverage**: Frontend, backend, full-stack, component
**Tooling**: Modern Grey Haven stack with best practices

View File

@@ -0,0 +1,602 @@
# Cloudflare Worker API Scaffold Example
Complete example of scaffolding a production-ready Cloudflare Workers API with Hono, TypeScript, D1 database, and comprehensive testing.
**Duration**: 15 minutes
**Files Created**: 18 files
**Lines of Code**: ~450 LOC
**Stack**: Cloudflare Workers + Hono + TypeScript + D1 + Vitest
---
## Complete File Tree
```
my-worker-api/
├── src/
│ ├── index.ts # Main entry point with Hono app
│ ├── routes/
│ │ ├── health.ts # Health check endpoint
│ │ ├── users.ts # User CRUD endpoints
│ │ └── index.ts # Route exports
│ ├── middleware/
│ │ ├── auth.ts # JWT authentication
│ │ ├── cors.ts # CORS configuration
│ │ ├── logger.ts # Request logging
│ │ └── error-handler.ts # Global error handling
│ ├── services/
│ │ └── user-service.ts # Business logic
│ ├── types/
│ │ └── environment.d.ts # TypeScript types for env
│ └── utils/
│ └── db.ts # Database helpers
├── tests/
│ ├── health.test.ts
│ ├── users.test.ts
│ └── setup.ts # Test configuration
├── .github/
│ └── workflows/
│ └── deploy.yml # CI/CD pipeline
├── wrangler.toml # Cloudflare configuration
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── .gitignore
├── .env.example
└── README.md
```
**Total**: 18 files, ~450 lines of code
---
## Generated Files
### 1. wrangler.toml (Cloudflare Configuration)
```toml
name = "my-worker-api"
main = "src/index.ts"
compatibility_date = "2024-01-15"
node_compat = true
[observability]
enabled = true
[[d1_databases]]
binding = "DB"
database_name = "my-worker-api-db"
database_id = "" # Add your database ID
[env.production]
[[env.production.d1_databases]]
binding = "DB"
database_name = "my-worker-api-prod"
database_id = "" # Add production database ID
[vars]
ENVIRONMENT = "development"
# Secrets (set via: wrangler secret put SECRET_NAME)
# JWT_SECRET
# API_KEY
```
### 2. package.json
```json
{
"name": "my-worker-api",
"version": "1.0.0",
"description": "Production Cloudflare Workers API",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"deploy:production": "wrangler deploy --env production",
"test": "vitest",
"test:coverage": "vitest --coverage",
"lint": "eslint src --ext .ts",
"format": "prettier --write \"src/**/*.ts\"",
"typecheck": "tsc --noEmit",
"d1:migrations": "wrangler d1 migrations list DB",
"d1:migrate": "wrangler d1 migrations apply DB"
},
"dependencies": {
"hono": "^4.0.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240117.0",
"@types/node": "^20.11.0",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"eslint": "^8.56.0",
"prettier": "^3.2.4",
"typescript": "^5.3.3",
"vitest": "^1.2.0",
"wrangler": "^3.25.0"
}
}
```
### 3. src/index.ts (Main Entry Point)
```typescript
import { Hono } from 'hono';
import { cors } from './middleware/cors';
import { logger } from './middleware/logger';
import { errorHandler } from './middleware/error-handler';
import { healthRoutes } from './routes/health';
import { userRoutes } from './routes/users';
import type { Environment } from './types/environment';
const app = new Hono<{ Bindings: Environment }>();
// Global middleware
app.use('*', cors());
app.use('*', logger());
// Routes
app.route('/health', healthRoutes);
app.route('/api/users', userRoutes);
// Error handling
app.onError(errorHandler);
// 404 handler
app.notFound((c) => {
return c.json({ error: 'Not Found', path: c.req.path }, 404);
});
export default app;
```
### 4. src/routes/health.ts (Health Check)
```typescript
import { Hono } from 'hono';
import type { Environment } from '../types/environment';
export const healthRoutes = new Hono<{ Bindings: Environment }>();
healthRoutes.get('/', async (c) => {
const db = c.env.DB;
try {
// Check database connection
const result = await db.prepare('SELECT 1 as health').first();
return c.json({
status: 'healthy',
timestamp: new Date().toISOString(),
environment: c.env.ENVIRONMENT || 'unknown',
database: result ? 'connected' : 'error',
});
} catch (error) {
return c.json({
status: 'unhealthy',
timestamp: new Date().toISOString(),
error: error instanceof Error ? error.message : 'Unknown error',
}, 503);
}
});
```
### 5. src/routes/users.ts (User CRUD)
```typescript
import { Hono } from 'hono';
import { auth } from '../middleware/auth';
import { UserService } from '../services/user-service';
import type { Environment } from '../types/environment';
export const userRoutes = new Hono<{ Bindings: Environment }>();
// List users (requires auth)
userRoutes.get('/', auth(), async (c) => {
const userService = new UserService(c.env.DB);
const users = await userService.listUsers();
return c.json({ users });
});
// Get user by ID
userRoutes.get('/:id', auth(), async (c) => {
const id = c.req.param('id');
const userService = new UserService(c.env.DB);
const user = await userService.getUserById(id);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ user });
});
// Create user
userRoutes.post('/', auth(), async (c) => {
const body = await c.req.json();
const userService = new UserService(c.env.DB);
// Validate input
if (!body.email || !body.name) {
return c.json({ error: 'Email and name are required' }, 400);
}
const user = await userService.createUser(body);
return c.json({ user }, 201);
});
// Update user
userRoutes.put('/:id', auth(), async (c) => {
const id = c.req.param('id');
const body = await c.req.json();
const userService = new UserService(c.env.DB);
const user = await userService.updateUser(id, body);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ user });
});
// Delete user
userRoutes.delete('/:id', auth(), async (c) => {
const id = c.req.param('id');
const userService = new UserService(c.env.DB);
const deleted = await userService.deleteUser(id);
if (!deleted) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ message: 'User deleted successfully' });
});
```
### 6. src/middleware/auth.ts (JWT Authentication)
```typescript
import { createMiddleware } from 'hono/factory';
import type { Environment } from '../types/environment';
export const auth = () => {
return createMiddleware<{ Bindings: Environment }>(async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return c.json({ error: 'Unauthorized' }, 401);
}
const token = authHeader.substring(7);
// Verify JWT (simplified - use proper JWT library in production)
if (token !== c.env.JWT_SECRET) {
return c.json({ error: 'Invalid token' }, 401);
}
// Add user info to context
c.set('user', { id: 'user-123', email: 'user@example.com' });
await next();
});
};
```
### 7. src/middleware/cors.ts (CORS Configuration)
```typescript
import { createMiddleware } from 'hono/factory';
export const cors = () => {
return createMiddleware(async (c, next) => {
await next();
c.header('Access-Control-Allow-Origin', '*');
c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (c.req.method === 'OPTIONS') {
return c.text('', 204);
}
});
};
```
### 8. src/middleware/logger.ts (Request Logging)
```typescript
import { createMiddleware } from 'hono/factory';
export const logger = () => {
return createMiddleware(async (c, next) => {
const start = Date.now();
const method = c.req.method;
const path = c.req.path;
await next();
const duration = Date.now() - start;
const status = c.res.status;
console.log(`${method} ${path} ${status} ${duration}ms`);
});
};
```
### 9. src/middleware/error-handler.ts (Global Error Handling)
```typescript
import type { ErrorHandler } from 'hono';
export const errorHandler: ErrorHandler = (err, c) => {
console.error('Error:', err);
const status = err.status || 500;
const message = err.message || 'Internal Server Error';
return c.json(
{
error: message,
...(c.env.ENVIRONMENT === 'development' && { stack: err.stack }),
},
status
);
};
```
### 10. src/services/user-service.ts (Business Logic)
```typescript
import type { D1Database } from '@cloudflare/workers-types';
interface User {
id: string;
email: string;
name: string;
created_at: string;
}
export class UserService {
constructor(private db: D1Database) {}
async listUsers(): Promise<User[]> {
const result = await this.db
.prepare('SELECT * FROM users ORDER BY created_at DESC')
.all<User>();
return result.results || [];
}
async getUserById(id: string): Promise<User | null> {
const result = await this.db
.prepare('SELECT * FROM users WHERE id = ?')
.bind(id)
.first<User>();
return result;
}
async createUser(data: { email: string; name: string }): Promise<User> {
const id = crypto.randomUUID();
const now = new Date().toISOString();
await this.db
.prepare('INSERT INTO users (id, email, name, created_at) VALUES (?, ?, ?, ?)')
.bind(id, data.email, data.name, now)
.run();
return {
id,
email: data.email,
name: data.name,
created_at: now,
};
}
async updateUser(id: string, data: Partial<{ email: string; name: string }>): Promise<User | null> {
const existing = await this.getUserById(id);
if (!existing) return null;
const updates: string[] = [];
const values: any[] = [];
if (data.email !== undefined) {
updates.push('email = ?');
values.push(data.email);
}
if (data.name !== undefined) {
updates.push('name = ?');
values.push(data.name);
}
if (updates.length > 0) {
values.push(id);
await this.db
.prepare(`UPDATE users SET ${updates.join(', ')} WHERE id = ?`)
.bind(...values)
.run();
}
return this.getUserById(id);
}
async deleteUser(id: string): Promise<boolean> {
const result = await this.db
.prepare('DELETE FROM users WHERE id = ?')
.bind(id)
.run();
return result.success;
}
}
```
### 11. src/types/environment.d.ts (TypeScript Types)
```typescript
export interface Environment {
DB: D1Database;
JWT_SECRET: string;
API_KEY: string;
ENVIRONMENT: string;
}
```
### 12. tests/health.test.ts (Health Check Tests)
```typescript
import { describe, it, expect } from 'vitest';
import app from '../src/index';
describe('Health Check', () => {
it('should return healthy status', async () => {
const req = new Request('http://localhost/health');
const res = await app.fetch(req, {
DB: getMockDB(),
ENVIRONMENT: 'test',
} as any);
expect(res.status).toBe(200);
const data = await res.json();
expect(data).toHaveProperty('status', 'healthy');
expect(data).toHaveProperty('timestamp');
});
});
function getMockDB() {
return {
prepare: () => ({
first: async () => ({ health: 1 }),
}),
};
}
```
### 13. README.md (Documentation)
```markdown
# My Worker API
Production-ready Cloudflare Workers API with TypeScript, Hono, and D1 database.
## Quick Start
\`\`\`bash
# Install dependencies
npm install
# Create D1 database
wrangler d1 create my-worker-api-db
# Update wrangler.toml with database ID
# Run migrations
npm run d1:migrate
# Start development server
npm run dev
# Open http://localhost:8787/health
\`\`\`
## API Endpoints
- `GET /health` - Health check
- `GET /api/users` - List users (requires auth)
- `GET /api/users/:id` - Get user by ID (requires auth)
- `POST /api/users` - Create user (requires auth)
- `PUT /api/users/:id` - Update user (requires auth)
- `DELETE /api/users/:id` - Delete user (requires auth)
## Authentication
Include Bearer token in Authorization header:
\`\`\`bash
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8787/api/users
\`\`\`
## Deployment
\`\`\`bash
# Deploy to production
npm run deploy:production
# Set secrets
wrangler secret put JWT_SECRET
wrangler secret put API_KEY
\`\`\`
## Testing
\`\`\`bash
# Run tests
npm test
# With coverage
npm run test:coverage
\`\`\`
```
---
## Scaffold Process
### Step 1: Initialize (2 minutes)
```bash
mkdir my-worker-api && cd my-worker-api
npm init -y
npm install hono
npm install -D @cloudflare/workers-types typescript wrangler vitest
```
### Step 2: Generate Configuration (3 minutes)
- Create wrangler.toml
- Create tsconfig.json
- Create package.json scripts
- Create .gitignore
### Step 3: Generate Source Code (5 minutes)
- Create src/index.ts
- Create routes/
- Create middleware/
- Create services/
- Create types/
### Step 4: Generate Tests (3 minutes)
- Create tests/ directory
- Create test files
- Create test setup
### Step 5: Generate CI/CD (2 minutes)
- Create .github/workflows/deploy.yml
- Create README.md
- Create .env.example
---
## Next Steps
After scaffolding:
1. **Update database ID** in wrangler.toml
2. **Run migrations**: `npm run d1:migrate`
3. **Set secrets**: `wrangler secret put JWT_SECRET`
4. **Test locally**: `npm run dev`
5. **Deploy**: `npm run deploy:production`
---
**Total Time**: 15 minutes
**Total Files**: 18
**Total LOC**: ~450
**Ready for**: Production deployment

View File

@@ -0,0 +1,373 @@
# Full-Stack Application Scaffold Example
Complete monorepo with React frontend (TanStack) and Cloudflare Worker backend with shared database.
**Duration**: 30 min | **Files**: 35 | **LOC**: ~850 | **Stack**: React + Vite + TanStack + Cloudflare Worker + D1
---
## Monorepo Structure
```
my-fullstack-app/
├── frontend/ # React + Vite + TypeScript
│ ├── src/
│ │ ├── main.tsx # Entry point
│ │ ├── routes/ # TanStack Router routes
│ │ ├── components/ # React components
│ │ ├── services/ # API client
│ │ └── lib/ # Utilities
│ ├── tests/
│ ├── package.json
│ ├── vite.config.ts
│ └── tsconfig.json
├── backend/ # Cloudflare Worker
│ ├── src/
│ │ ├── index.ts # Hono app
│ │ ├── routes/ # API routes
│ │ ├── middleware/ # Auth, CORS
│ │ └── services/ # Business logic
│ ├── tests/
│ ├── wrangler.toml
│ ├── package.json
│ └── tsconfig.json
├── packages/ # Shared code
│ └── types/
│ ├── src/
│ │ ├── api.ts # API types
│ │ └── models.ts # Data models
│ ├── package.json
│ └── tsconfig.json
├── docs/
│ ├── README.md # Project overview
│ ├── ARCHITECTURE.md # System architecture
│ └── API.md # API documentation
├── .github/
│ └── workflows/
│ └── deploy.yml # CI/CD pipeline
├── package.json # Root workspace config
├── pnpm-workspace.yaml # pnpm workspaces
└── README.md
```
---
## Key Features
### Frontend (React + TanStack)
**Tech Stack**:
- **Vite**: Fast build tool
- **TanStack Router**: Type-safe routing
- **TanStack Query**: Server state management
- **TanStack Table**: Data tables
- **Zod**: Runtime validation
**File**: `frontend/src/main.tsx`
```typescript
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { routeTree } from './routeTree.gen';
const queryClient = new QueryClient();
const router = createRouter({ routeTree });
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</StrictMode>,
);
```
**File**: `frontend/src/services/api.ts`
```typescript
import { apiClient } from '@my-app/types';
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8787';
export const api = {
users: {
list: () => fetch(`${API_BASE}/api/users`).then(r => r.json()),
get: (id: string) => fetch(`${API_BASE}/api/users/${id}`).then(r => r.json()),
create: (data: any) =>
fetch(`${API_BASE}/api/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}).then(r => r.json()),
},
};
```
### Backend (Cloudflare Worker)
**File**: `backend/src/index.ts`
```typescript
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { userRoutes } from './routes/users';
const app = new Hono();
app.use('*', cors({ origin: process.env.FRONTEND_URL || '*' }));
app.route('/api/users', userRoutes);
export default app;
```
### Shared Types
**File**: `packages/types/src/models.ts`
```typescript
export interface User {
id: string;
email: string;
name: string;
created_at: string;
}
export interface CreateUserInput {
email: string;
name: string;
}
export interface UpdateUserInput {
email?: string;
name?: string;
}
```
**File**: `packages/types/src/api.ts`
```typescript
import type { User } from './models';
export interface ApiResponse<T> {
data?: T;
error?: string;
}
export interface UserListResponse extends ApiResponse<User[]> {
total: number;
}
export interface UserResponse extends ApiResponse<User> {}
```
---
## Workspace Configuration
### Root package.json (pnpm workspaces)
```json
{
"name": "my-fullstack-app",
"private": true,
"scripts": {
"dev": "concurrently \"pnpm --filter frontend dev\" \"pnpm --filter backend dev\"",
"build": "pnpm --filter \"./packages/*\" build && pnpm --filter frontend build && pnpm --filter backend build",
"test": "pnpm --recursive test",
"deploy": "pnpm --filter backend deploy && pnpm --filter frontend deploy"
},
"devDependencies": {
"concurrently": "^8.2.2",
"typescript": "^5.3.3"
}
}
```
### pnpm-workspace.yaml
```yaml
packages:
- 'frontend'
- 'backend'
- 'packages/*'
```
---
## Development Workflow
### 1. Setup
```bash
# Clone and install
git clone <repo>
cd my-fullstack-app
pnpm install
# Setup database
cd backend
wrangler d1 create my-app-db
# Update wrangler.toml with database ID
cd ..
# Create .env files
cp frontend/.env.example frontend/.env
cp backend/.env.example backend/.env
```
### 2. Development
```bash
# Start both frontend and backend
pnpm dev
# Frontend: http://localhost:5173
# Backend: http://localhost:8787
```
### 3. Testing
```bash
# Run all tests
pnpm test
# Test specific workspace
pnpm --filter frontend test
pnpm --filter backend test
```
### 4. Deployment
```bash
# Deploy backend (Cloudflare Workers)
cd backend
pnpm deploy
# Deploy frontend (Cloudflare Pages)
cd ../frontend
pnpm build
wrangler pages deploy dist
```
---
## CI/CD Pipeline
**File**: `.github/workflows/deploy.yml`
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
- run: pnpm build
deploy-backend:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm --filter backend deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
deploy-frontend:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm --filter frontend build
- uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-app
directory: frontend/dist
```
---
## Architecture
### Data Flow
```
User → React App → TanStack Query → API Client
Cloudflare Worker
D1 Database
```
### Authentication Flow
```typescript
// frontend/src/lib/auth.ts
export async function login(email: string, password: string) {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const { token } = await response.json();
localStorage.setItem('token', token);
return token;
}
// backend/src/middleware/auth.ts
export const auth = () => async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '');
if (!token) return c.json({ error: 'Unauthorized' }, 401);
// Verify JWT token
const user = await verifyToken(token);
c.set('user', user);
await next();
};
```
---
## Metrics
| Component | Files | LOC | Tests | Coverage |
|-----------|-------|-----|-------|----------|
| Frontend | 15 | ~350 | 8 | 85% |
| Backend | 12 | ~300 | 6 | 90% |
| Shared | 4 | ~80 | 2 | 100% |
| Docs | 4 | ~120 | - | - |
| **Total** | **35** | **~850** | **16** | **88%** |
---
## Next Steps
1. ✅ Run `pnpm install` to install dependencies
2. ✅ Setup D1 database and update configuration
3. ✅ Run `pnpm dev` to start development servers
4. ✅ Implement your business logic
5. ✅ Deploy with `pnpm deploy`
---
**Setup Time**: 30 minutes
**Production Ready**: Yes
**Deployment**: Cloudflare Pages + Workers
**Monitoring**: Built-in observability

View File

@@ -0,0 +1,403 @@
# Python API Scaffold Example
Production-ready FastAPI application with Pydantic v2 validation, async PostgreSQL (PlanetScale), and comprehensive testing.
**Duration**: 20 minutes | **Files**: 22 | **LOC**: ~600 | **Stack**: FastAPI + Pydantic v2 + SQLAlchemy + PostgreSQL
---
## File Tree
```
my-python-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI application
│ ├── config.py # Configuration management
│ ├── dependencies.py # Dependency injection
│ ├── api/
│ │ ├── __init__.py
│ │ ├── users.py # User endpoints
│ │ └── health.py # Health check
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py # SQLAlchemy models
│ ├── schemas/
│ │ ├── __init__.py
│ │ └── user.py # Pydantic schemas
│ ├── services/
│ │ ├── __init__.py
│ │ └── user_service.py # Business logic
│ └── db/
│ ├── __init__.py
│ ├── base.py # Database base
│ └── session.py # Async session
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Pytest fixtures
│ ├── test_health.py
│ └── test_users.py
├── alembic/
│ ├── versions/
│ └── env.py # Migration environment
├── pyproject.toml # Modern Python config (uv)
├── .env.example
├── .gitignore
├── alembic.ini
└── README.md
```
---
## Key Files
### 1. pyproject.toml (uv configuration)
```toml
[project]
name = "my-python-api"
version = "0.1.0"
description = "Production FastAPI with Pydantic v2"
requires-python = ">=3.11"
dependencies = [
"fastapi[standard]>=0.109.0",
"pydantic>=2.5.0",
"pydantic-settings>=2.1.0",
"sqlalchemy[asyncio]>=2.0.25",
"alembic>=1.13.0",
"asyncpg>=0.29.0",
"uvicorn[standard]>=0.27.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.3",
"pytest-asyncio>=0.23.0",
"pytest-cov>=4.1.0",
"httpx>=0.26.0",
"ruff>=0.1.11",
"mypy>=1.8.0",
]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
[tool.mypy]
python_version = "3.11"
strict = true
```
### 2. app/main.py (FastAPI Application)
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import health, users
from app.config import settings
app = FastAPI(
title=settings.PROJECT_NAME,
version="1.0.0",
docs_url="/api/docs",
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Routes
app.include_router(health.router, tags=["health"])
app.include_router(users.router, prefix="/api/users", tags=["users"])
@app.on_event("startup")
async def startup():
print(f"Starting {settings.PROJECT_NAME} in {settings.ENVIRONMENT} mode")
@app.on_event("shutdown")
async def shutdown():
print("Shutting down...")
```
### 3. app/schemas/user.py (Pydantic v2 Schemas)
```python
from pydantic import BaseModel, EmailStr, Field, ConfigDict
from datetime import datetime
from uuid import UUID
class UserBase(BaseModel):
email: EmailStr
name: str = Field(min_length=1, max_length=100)
class UserCreate(UserBase):
password: str = Field(min_length=12, max_length=100)
class UserUpdate(BaseModel):
email: EmailStr | None = None
name: str | None = Field(None, min_length=1, max_length=100)
class UserResponse(UserBase):
id: UUID
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class UserList(BaseModel):
users: list[UserResponse]
total: int
page: int
page_size: int
```
### 4. app/models/user.py (SQLAlchemy Model)
```python
from sqlalchemy import Column, String, DateTime
from sqlalchemy.dialects.postgresql import UUID
from datetime import datetime
import uuid
from app.db.base import Base
class User(Base):
__tablename__ = "users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String(255), unique=True, nullable=False, index=True)
name = Column(String(100), nullable=False)
hashed_password = Column(String(255), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
```
### 5. app/api/users.py (User Endpoints)
```python
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db
from app.schemas.user import UserCreate, UserResponse, UserUpdate, UserList
from app.services.user_service import UserService
router = APIRouter()
@router.get("/", response_model=UserList)
async def list_users(
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(get_db),
):
service = UserService(db)
users, total = await service.list_users(skip=skip, limit=limit)
return UserList(users=users, total=total, page=skip // limit + 1, page_size=limit)
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: str, db: AsyncSession = Depends(get_db)):
service = UserService(db)
user = await service.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
service = UserService(db)
return await service.create_user(user_data)
@router.put("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: str,
user_data: UserUpdate,
db: AsyncSession = Depends(get_db),
):
service = UserService(db)
user = await service.update_user(user_id, user_data)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: str, db: AsyncSession = Depends(get_db)):
service = UserService(db)
deleted = await service.delete_user(user_id)
if not deleted:
raise HTTPException(status_code=404, detail="User not found")
```
### 6. app/services/user_service.py (Business Logic)
```python
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from uuid import UUID
from app.models.user import User
from app.schemas.user import UserCreate, UserUpdate, UserResponse
class UserService:
def __init__(self, db: AsyncSession):
self.db = db
async def list_users(self, skip: int = 0, limit: int = 100):
query = select(User).offset(skip).limit(limit)
result = await self.db.execute(query)
users = result.scalars().all()
count_query = select(func.count()).select_from(User)
total = await self.db.scalar(count_query)
return [UserResponse.model_validate(u) for u in users], total or 0
async def get_user(self, user_id: str) -> UserResponse | None:
query = select(User).where(User.id == UUID(user_id))
result = await self.db.execute(query)
user = result.scalar_one_or_none()
return UserResponse.model_validate(user) if user else None
async def create_user(self, user_data: UserCreate) -> UserResponse:
user = User(
email=user_data.email,
name=user_data.name,
hashed_password=self._hash_password(user_data.password),
)
self.db.add(user)
await self.db.commit()
await self.db.refresh(user)
return UserResponse.model_validate(user)
async def update_user(self, user_id: str, user_data: UserUpdate) -> UserResponse | None:
query = select(User).where(User.id == UUID(user_id))
result = await self.db.execute(query)
user = result.scalar_one_or_none()
if not user:
return None
if user_data.email is not None:
user.email = user_data.email
if user_data.name is not None:
user.name = user_data.name
await self.db.commit()
await self.db.refresh(user)
return UserResponse.model_validate(user)
async def delete_user(self, user_id: str) -> bool:
query = select(User).where(User.id == UUID(user_id))
result = await self.db.execute(query)
user = result.scalar_one_or_none()
if not user:
return False
await self.db.delete(user)
await self.db.commit()
return True
def _hash_password(self, password: str) -> str:
# Use proper password hashing (bcrypt, argon2) in production
return f"hashed_{password}"
```
### 7. tests/test_users.py (Tests)
```python
import pytest
from httpx import AsyncClient
from app.main import app
@pytest.mark.asyncio
async def test_list_users():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/api/users/")
assert response.status_code == 200
data = response.json()
assert "users" in data
assert "total" in data
@pytest.mark.asyncio
async def test_create_user():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.post(
"/api/users/",
json={
"email": "test@example.com",
"name": "Test User",
"password": "securepassword123",
},
)
assert response.status_code == 201
data = response.json()
assert data["email"] == "test@example.com"
assert "id" in data
```
---
## Setup Commands
```bash
# Initialize with uv
uv init my-python-api
cd my-python-api
# Create virtual environment
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
uv pip install -e ".[dev]"
# Setup database
alembic revision --autogenerate -m "Initial migration"
alembic upgrade head
# Run development server
uvicorn app.main:app --reload
```
---
## Testing
```bash
# Run tests
pytest
# With coverage
pytest --cov=app --cov-report=html
# Type checking
mypy app/
# Linting
ruff check app/
ruff format app/
```
---
**Metrics**:
- Files: 22
- LOC: ~600
- Test Coverage: 85%+
- Type Safety: 100% (mypy strict)
- API Docs: Auto-generated (FastAPI)

View File

@@ -0,0 +1,358 @@
# React Component Scaffold Example
Complete example of scaffolding a reusable React component with TypeScript, tests, Storybook stories, and CSS modules.
**Duration**: 5 minutes | **Files**: 6 | **LOC**: ~120 | **Stack**: React + TypeScript + Vitest + Storybook
---
## File Tree
```
src/components/Button/
├── Button.tsx # Component implementation
├── Button.test.tsx # Vitest + Testing Library tests
├── Button.stories.tsx # Storybook stories
├── Button.module.css # CSS modules styling
├── index.ts # Re-exports
└── README.md # Component documentation
```
---
## Generated Files
### 1. Button.tsx (Implementation)
```typescript
import React from 'react';
import styles from './Button.module.css';
export interface ButtonProps {
/** Button label */
label: string;
/** Button variant */
variant?: 'primary' | 'secondary' | 'danger';
/** Button size */
size?: 'small' | 'medium' | 'large';
/** Disabled state */
disabled?: boolean;
/** Click handler */
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
label,
variant = 'primary',
size = 'medium',
disabled = false,
onClick,
}) => {
const className = [
styles.button,
styles[variant],
styles[size],
disabled && styles.disabled,
].filter(Boolean).join(' ');
return (
<button
className={className}
disabled={disabled}
onClick={onClick}
type="button"
>
{label}
</button>
);
};
```
### 2. Button.test.tsx (Tests)
```typescript
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders with label', () => {
render(<Button label="Click me" />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button label="Click" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledOnce();
});
it('does not call onClick when disabled', () => {
const handleClick = vi.fn();
render(<Button label="Click" onClick={handleClick} disabled />);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).not.toHaveBeenCalled();
});
it('applies variant classes correctly', () => {
const { container } = render(<Button label="Test" variant="danger" />);
expect(container.firstChild).toHaveClass('danger');
});
it('applies size classes correctly', () => {
const { container } = render(<Button label="Test" size="large" />);
expect(container.firstChild).toHaveClass('large');
});
});
```
### 3. Button.stories.tsx (Storybook)
```typescript
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
},
size: {
control: 'select',
options: ['small', 'medium', 'large'],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
label: 'Primary Button',
variant: 'primary',
},
};
export const Secondary: Story = {
args: {
label: 'Secondary Button',
variant: 'secondary',
},
};
export const Danger: Story = {
args: {
label: 'Danger Button',
variant: 'danger',
},
};
export const Small: Story = {
args: {
label: 'Small Button',
size: 'small',
},
};
export const Large: Story = {
args: {
label: 'Large Button',
size: 'large',
},
};
export const Disabled: Story = {
args: {
label: 'Disabled Button',
disabled: true,
},
};
```
### 4. Button.module.css (Styles)
```css
.button {
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: all 0.2s ease;
font-family: inherit;
}
.button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.button:active:not(:disabled) {
transform: translateY(0);
}
/* Variants */
.primary {
background-color: #0070f3;
color: white;
}
.primary:hover:not(:disabled) {
background-color: #0051cc;
}
.secondary {
background-color: #eaeaea;
color: #000;
}
.secondary:hover:not(:disabled) {
background-color: #d0d0d0;
}
.danger {
background-color: #e00;
color: white;
}
.danger:hover:not(:disabled) {
background-color: #c00;
}
/* Sizes */
.small {
padding: 6px 12px;
font-size: 14px;
}
.medium {
padding: 10px 20px;
font-size: 16px;
}
.large {
padding: 14px 28px;
font-size: 18px;
}
/* States */
.disabled {
opacity: 0.5;
cursor: not-allowed;
}
```
### 5. index.ts (Exports)
```typescript
export { Button } from './Button';
export type { ButtonProps } from './Button';
```
### 6. README.md (Documentation)
```markdown
# Button Component
Reusable button component with variants, sizes, and accessibility features.
## Usage
\`\`\`tsx
import { Button } from '@/components/Button';
function App() {
return (
<Button
label="Click me"
variant="primary"
size="medium"
onClick={() => console.log('Clicked!')}
/>
);
}
\`\`\`
## Props
- `label` (string, required) - Button text
- `variant` ('primary' | 'secondary' | 'danger', default: 'primary') - Visual style
- `size` ('small' | 'medium' | 'large', default: 'medium') - Button size
- `disabled` (boolean, default: false) - Disabled state
- `onClick` (function, optional) - Click handler
## Variants
- **Primary**: Main call-to-action buttons
- **Secondary**: Less prominent actions
- **Danger**: Destructive actions (delete, remove)
## Accessibility
- Semantic `<button>` element
- Proper ARIA attributes
- Keyboard navigation support
- Disabled state handling
```
---
## Scaffold Command
```bash
# Generate component
npx create-component --name Button --path src/components
# Or manually
mkdir -p src/components/Button
cd src/components/Button
# Create files
touch Button.tsx Button.test.tsx Button.stories.tsx Button.module.css index.ts README.md
```
---
## Testing
```bash
# Run tests
npm test Button.test.tsx
# With coverage
npm test -- --coverage Button.test.tsx
# Watch mode
npm test -- --watch
```
---
## Storybook
```bash
# Start Storybook
npm run storybook
# View at http://localhost:6006
# Navigate to Components > Button
```
---
**Metrics**:
- Files: 6
- LOC: ~120
- Test Coverage: 100%
- Storybook Stories: 6 variants
- Accessibility: WCAG 2.1 AA compliant

View File

@@ -0,0 +1,203 @@
# Project Scaffolder Reference
Complete reference for Grey Haven project scaffolding - conventions, specifications, and tooling.
---
## Navigation
| Reference | Description |
|-----------|-------------|
| [Grey Haven Conventions](grey-haven-conventions.md) | Naming, structure, and code standards |
| [Scaffold Specifications](scaffold-specifications.md) | Technical specs for each scaffold type |
| [Tooling Configurations](tooling-configurations.md) | Config files for Vite, Wrangler, pytest, etc. |
---
## Quick Reference
### Grey Haven Stack
| Layer | Technology | Use Case |
|-------|------------|----------|
| **Frontend** | React + Vite + TanStack | Web applications |
| **Backend** | Cloudflare Workers + Hono | REST/GraphQL APIs |
| **Database** | PlanetScale PostgreSQL (or D1) | Relational data |
| **Validation** | Zod (TS), Pydantic (Python) | Type-safe validation |
| **Testing** | Vitest (TS), pytest (Python) | Unit & integration tests |
| **Deployment** | Cloudflare Pages + Workers | Global edge deployment |
### Naming Conventions
```
Components: PascalCase (Button, UserProfile)
Files: kebab-case (user-profile.tsx, api-client.ts)
Variables: camelCase (userId, isActive)
Constants: UPPER_SNAKE (API_URL, MAX_RETRIES)
Database: snake_case (user_profiles, api_keys)
Routes: kebab-case (/api/user-profiles)
```
### Folder Structure
```
src/
├── routes/ # API endpoints or page routes
├── components/ # Reusable UI components
├── services/ # Business logic
├── utils/ # Pure helper functions
├── types/ # TypeScript type definitions
└── lib/ # Third-party integrations
tests/ # Mirror src/ structure
├── routes/
├── components/
└── services/
```
### Configuration Standards
**TypeScript Projects**:
- Strict mode enabled
- ESLint with recommended rules
- Prettier for formatting
- Vitest for testing
- Path aliases (`@/` for src/)
**Python Projects**:
- Python 3.11+
- uv for package management
- Ruff for linting
- mypy for type checking
- pytest for testing
---
## Scaffold Templates
### Minimum Files Required
**Cloudflare Worker**:
- wrangler.toml
- package.json
- tsconfig.json
- src/index.ts
- tests/
**React App**:
- package.json
- vite.config.ts
- tsconfig.json
- src/main.tsx
- src/routes/
- tests/
**Python API**:
- pyproject.toml
- app/main.py
- app/schemas/
- app/models/
- tests/
---
## Tooling Versions
### Recommended Versions (2024)
```json
{
"typescript": "^5.3.0",
"vite": "^5.0.0",
"react": "^18.2.0",
"hono": "^4.0.0",
"wrangler": "^3.25.0",
"vitest": "^1.2.0"
}
```
```toml
# Python (pyproject.toml)
[project]
requires-python = ">=3.11"
dependencies = [
"fastapi[standard]>=0.109.0",
"pydantic>=2.5.0",
"uvicorn>=0.27.0"
]
```
---
## Best Practices
### DO
- ✅ Use TypeScript strict mode
- ✅ Include tests in scaffold
- ✅ Configure linting and formatting
- ✅ Add .gitignore
- ✅ Include README with setup instructions
- ✅ Add CI/CD configuration
- ✅ Use environment variables for secrets
- ✅ Include health check endpoint
### DON'T
- ❌ Commit node_modules or .venv
- ❌ Hard-code secrets or API keys
- ❌ Skip type definitions
- ❌ Omit error handling
- ❌ Forget database migrations
- ❌ Skip documentation
---
## Deployment Checklist
### Pre-Deployment
- [ ] All tests passing
- [ ] TypeScript/mypy checks pass
- [ ] Linting passes
- [ ] Environment variables configured
- [ ] Database migrations applied
- [ ] Secrets set in production
- [ ] Build succeeds
- [ ] Health check endpoint working
### Post-Deployment
- [ ] Health check returns 200
- [ ] API endpoints accessible
- [ ] Database connections working
- [ ] Authentication functioning
- [ ] Monitoring enabled
- [ ] Error tracking active
- [ ] Logs accessible
---
## Common Issues
### Issue: TypeScript errors after scaffold
**Solution**: Run `npm install` and ensure tsconfig.json is correct
### Issue: Wrangler fails to deploy
**Solution**: Check wrangler.toml config and Cloudflare authentication
### Issue: Database connection fails
**Solution**: Verify connection string and database credentials
### Issue: Tests fail after scaffold
**Solution**: Check test setup and mock configuration
---
**Total References**: 3 comprehensive guides
**Coverage**: Conventions, specifications, configurations
**Standards**: Production-ready Grey Haven stack

View File

@@ -0,0 +1,373 @@
# Grey Haven Conventions
Complete style guide for Grey Haven projects - naming, structure, patterns, and standards.
---
## Naming Conventions
### Code Elements
| Element | Convention | Examples |
|---------|------------|----------|
| **Components** | PascalCase | `Button`, `UserProfile`, `DataTable` |
| **Functions** | camelCase | `getUserById`, `calculateTotal` |
| **Variables** | camelCase | `userId`, `isActive`, `firstName` |
| **Constants** | UPPER_SNAKE_CASE | `API_URL`, `MAX_RETRIES`, `DEFAULT_TIMEOUT` |
| **Types/Interfaces** | PascalCase | `User`, `ApiResponse`, `Config` |
| **Enums** | PascalCase | `Status`, `UserRole`, `HttpMethod` |
### Files and Directories
| Type | Convention | Examples |
|------|------------|----------|
| **Components** | PascalCase | `Button.tsx`, `UserProfile.tsx` |
| **Routes** | kebab-case | `user-profile.tsx`, `api-client.ts` |
| **Utilities** | kebab-case | `string-utils.ts`, `date-helpers.ts` |
| **Tests** | Match source + `.test` | `Button.test.tsx`, `api-client.test.ts` |
| **Stories** | Match source + `.stories` | `Button.stories.tsx` |
| **Directories** | kebab-case | `user-management/`, `api-routes/` |
### Database Schema
```sql
-- Tables: snake_case (plural)
CREATE TABLE user_profiles (...);
CREATE TABLE api_keys (...);
-- Columns: snake_case
user_id, first_name, created_at
-- Indexes: table_column_idx
CREATE INDEX user_profiles_email_idx ON user_profiles(email);
-- Foreign keys: table_column_fkey
FOREIGN KEY (user_id) REFERENCES users(id)
```
### API Endpoints
```
GET /api/users # List (plural)
GET /api/users/:id # Get single
POST /api/users # Create
PUT /api/users/:id # Update
DELETE /api/users/:id # Delete
GET /api/user-profiles # kebab-case for multi-word
POST /api/auth/login # Nested resources
```
---
## Project Structure
### Frontend (React + Vite)
```
frontend/
├── src/
│ ├── main.tsx # Entry point
│ ├── App.tsx # Root component
│ ├── routes/ # TanStack Router routes
│ │ ├── index.tsx # Home route
│ │ ├── users/
│ │ │ ├── index.tsx # /users
│ │ │ └── $id.tsx # /users/:id
│ │ └── __root.tsx # Root layout
│ ├── components/ # Reusable components
│ │ ├── ui/ # Generic UI (Button, Input)
│ │ ├── forms/ # Form components
│ │ └── layout/ # Layout components
│ ├── services/ # API clients, integrations
│ │ ├── api.ts # API client
│ │ └── auth.ts # Auth service
│ ├── lib/ # Third-party setup
│ │ ├── query-client.ts # TanStack Query config
│ │ └── router.ts # Router config
│ ├── utils/ # Pure utility functions
│ │ ├── string-utils.ts
│ │ └── date-utils.ts
│ ├── types/ # TypeScript types
│ │ ├── api.ts # API types
│ │ └── models.ts # Domain models
│ └── assets/ # Static assets
│ ├── images/
│ └── styles/
├── tests/ # Mirror src/ structure
│ ├── routes/
│ ├── components/
│ └── services/
├── public/ # Static files (favicon, etc.)
├── package.json
├── vite.config.ts
├── tsconfig.json
└── .env.example
```
### Backend (Cloudflare Worker)
```
backend/
├── src/
│ ├── index.ts # Entry point
│ ├── routes/ # API route handlers
│ │ ├── health.ts # Health check
│ │ ├── users.ts # User routes
│ │ └── auth.ts # Auth routes
│ ├── middleware/ # Request middleware
│ │ ├── auth.ts # Authentication
│ │ ├── cors.ts # CORS config
│ │ ├── logger.ts # Logging
│ │ └── error-handler.ts # Error handling
│ ├── services/ # Business logic
│ │ ├── user-service.ts
│ │ └── auth-service.ts
│ ├── utils/ # Helper functions
│ │ ├── db.ts # Database helpers
│ │ └── jwt.ts # JWT utilities
│ └── types/ # TypeScript types
│ └── environment.d.ts # Env types
├── tests/ # Mirror src/
├── wrangler.toml # Cloudflare config
├── package.json
└── tsconfig.json
```
### Python API (FastAPI)
```
backend/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app
│ ├── config.py # Configuration
│ ├── dependencies.py # Dependency injection
│ ├── api/ # API routes
│ │ ├── __init__.py
│ │ ├── health.py
│ │ └── users.py
│ ├── models/ # SQLAlchemy models
│ │ ├── __init__.py
│ │ └── user.py
│ ├── schemas/ # Pydantic schemas
│ │ ├── __init__.py
│ │ └── user.py
│ ├── services/ # Business logic
│ │ ├── __init__.py
│ │ └── user_service.py
│ └── db/ # Database
│ ├── __init__.py
│ ├── base.py
│ └── session.py
├── tests/ # Mirror app/
├── alembic/ # Migrations
├── pyproject.toml # uv config
└── .env.example
```
---
## Code Style
### TypeScript
```typescript
// Imports: grouped and sorted
import { useState, useEffect } from 'react'; // React
import { useQuery } from '@tanstack/react-query'; // Third-party
import { Button } from '@/components/ui/Button'; // Internal
import type { User } from '@/types/models'; // Types
// Interfaces: PascalCase, descriptive
export interface UserProfileProps {
userId: string;
onUpdate?: (user: User) => void;
}
// Components: PascalCase, typed props
export const UserProfile: React.FC<UserProfileProps> = ({ userId, onUpdate }) => {
// Hooks first
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => api.users.get(userId),
});
// Early returns
if (isLoading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
// JSX
return (
<div>
<h1>{user.name}</h1>
<Button onClick={() => onUpdate?.(user)}>Update</Button>
</div>
);
};
```
### Python
```python
# Imports: grouped and sorted
from datetime import datetime # Standard library
from uuid import UUID
from fastapi import APIRouter, Depends # Third-party
from pydantic import BaseModel, EmailStr
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db # Internal
from app.services.user_service import UserService
# Type hints: Always use
router = APIRouter()
# Functions: snake_case, typed
@router.get("/users/{user_id}")
async def get_user(
user_id: UUID,
db: AsyncSession = Depends(get_db),
) -> dict:
"""Get user by ID.
Args:
user_id: User UUID
db: Database session
Returns:
User data dictionary
Raises:
HTTPException: If user not found
"""
service = UserService(db)
user = await service.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user.model_dump()
```
---
## Testing Conventions
### Test File Organization
```
tests/
├── unit/ # Unit tests (isolated)
│ ├── components/
│ ├── services/
│ └── utils/
├── integration/ # Integration tests
│ ├── api/
│ └── database/
└── e2e/ # End-to-end tests
└── user-flow.test.ts
```
### Test Naming
```typescript
// Describe: Component/function name
describe('Button', () => {
// It: Should + behavior
it('should render with label', () => {
render(<Button label="Click me" />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('should call onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button label="Click" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledOnce();
});
});
```
```python
# Class: Test + ClassName
class TestUserService:
"""Test suite for UserService."""
# Method: test_ + behavior
async def test_get_user_returns_user_when_exists(self, db_session):
"""Should return user when user exists."""
service = UserService(db_session)
user = await service.get_user(user_id)
assert user is not None
assert user.email == "test@example.com"
async def test_get_user_returns_none_when_not_exists(self, db_session):
"""Should return None when user doesn't exist."""
service = UserService(db_session)
user = await service.get_user(UUID4())
assert user is None
```
---
## Git Conventions
### Branch Naming
```
feature/user-authentication # New feature
fix/login-button-crash # Bug fix
refactor/api-client # Code refactoring
docs/api-documentation # Documentation
chore/update-dependencies # Maintenance
```
### Commit Messages
```
feat: add user authentication system
fix: resolve login button crash on mobile
refactor: extract API client into separate module
docs: add API endpoint documentation
chore: update dependencies to latest versions
# Format: type: description (lowercase, no period)
# Types: feat, fix, refactor, docs, chore, test, style
```
---
## Environment Variables
### Naming
```bash
# Format: UPPER_SNAKE_CASE with prefix
DATABASE_URL=postgresql://...
API_URL=https://api.example.com
JWT_SECRET=...
# Cloudflare-specific
CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ACCOUNT_ID=...
# Feature flags
FEATURE_NEW_UI=true
```
### Management
- Use `.env.example` for template
- Never commit `.env` files
- Use Wrangler secrets for Cloudflare
- Use environment-specific configs
---
**Version**: 1.0
**Last Updated**: 2024-01-15

View File

@@ -0,0 +1,138 @@
# Scaffold Specifications
Technical specifications for each Grey Haven scaffold type.
---
## Cloudflare Worker API Specification
**Purpose**: Production REST/GraphQL API on Cloudflare edge network
**Minimum Files** (18):
- wrangler.toml, package.json, tsconfig.json
- src/index.ts (entry), routes/ (3 files), middleware/ (4 files)
- services/ (2 files), types/ (1 file), utils/ (1 file)
- tests/ (3 files), .github/workflows/, README.md
**Dependencies**:
```json
{
"dependencies": {
"hono": "^4.0.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240117.0",
"typescript": "^5.3.3",
"vitest": "^1.2.0",
"wrangler": "^3.25.0"
}
}
```
**Features**:
- Hono framework
- D1 database binding
- JWT authentication middleware
- CORS configuration
- Request logging
- Error handling
- Health check endpoint
- Vitest tests
---
## React Component Specification
**Purpose**: Reusable UI component with tests and stories
**Minimum Files** (6):
- Component.tsx, Component.test.tsx, Component.stories.tsx
- Component.module.css, index.ts, README.md
**Dependencies**:
```json
{
"devDependencies": {
"@testing-library/react": "^14.1.0",
"@testing-library/user-event": "^14.5.0",
"@storybook/react-vite": "^7.6.0",
"vitest": "^1.2.0"
}
}
```
**Features**:
- TypeScript prop types
- CSS modules styling
- Vitest + Testing Library tests
- Storybook stories
- Accessible markup
- JSDoc documentation
---
## Python API Specification
**Purpose**: Production-ready FastAPI with async PostgreSQL
**Minimum Files** (22):
- pyproject.toml, alembic.ini, .env.example
- app/main.py, config.py, dependencies.py
- app/api/ (3 files), models/ (2 files), schemas/ (2 files)
- app/services/ (2 files), db/ (3 files)
- tests/ (4 files), alembic/versions/, README.md
**Dependencies**:
```toml
[project]
dependencies = [
"fastapi[standard]>=0.109.0",
"pydantic>=2.5.0",
"sqlalchemy[asyncio]>=2.0.25",
"alembic>=1.13.0",
"asyncpg>=0.29.0",
]
```
**Features**:
- FastAPI with async
- Pydantic v2 validation
- SQLAlchemy 2.0 async
- Alembic migrations
- pytest with asyncio
- uv package manager
- Ruff linting
- mypy type checking
---
## Full-Stack Specification
**Purpose**: Complete monorepo with frontend and backend
**Minimum Files** (35):
- package.json (root), pnpm-workspace.yaml
- frontend/ (15 files), backend/ (12 files)
- packages/types/ (4 files), docs/ (4 files)
**Structure**:
```
my-app/
├── frontend/ # React + Vite + TanStack
├── backend/ # Cloudflare Worker + D1
├── packages/ # Shared TypeScript types
└── docs/ # Architecture docs
```
**Features**:
- pnpm workspaces
- Shared types package
- TanStack Router + Query
- Cloudflare deployment
- CI/CD pipeline
- Monorepo scripts
---
**Total Specs**: 4 scaffold types
**Coverage**: Frontend, backend, component, full-stack

View File

@@ -0,0 +1,110 @@
#!/bin/bash
# Cloudflare Worker API Scaffold Template
# Usage: ./cloudflare-worker-template.sh my-api
PROJECT_NAME="${1:-my-worker-api}"
echo "Scaffolding Cloudflare Worker: $PROJECT_NAME"
# Create directory structure
mkdir -p "$PROJECT_NAME"/{src/{routes,middleware,services,types,utils},tests,.github/workflows}
cd "$PROJECT_NAME" || exit
# package.json
cat > package.json << 'EOF'
{
"name": "PROJECT_NAME",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"test": "vitest"
},
"dependencies": {
"hono": "^4.0.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240117.0",
"typescript": "^5.3.3",
"vitest": "^1.2.0",
"wrangler": "^3.25.0"
}
}
EOF
sed -i '' "s/PROJECT_NAME/$PROJECT_NAME/g" package.json
# wrangler.toml
cat > wrangler.toml << 'EOF'
name = "PROJECT_NAME"
main = "src/index.ts"
compatibility_date = "2024-01-15"
[[d1_databases]]
binding = "DB"
database_name = "PROJECT_NAME-db"
database_id = ""
EOF
sed -i '' "s/PROJECT_NAME/$PROJECT_NAME/g" wrangler.toml
# tsconfig.json
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"strict": true,
"types": ["@cloudflare/workers-types"]
}
}
EOF
# src/index.ts
cat > src/index.ts << 'EOF'
import { Hono } from 'hono';
import { cors } from 'hono/cors';
const app = new Hono();
app.use('*', cors());
app.get('/health', (c) => c.json({ status: 'healthy' }));
export default app;
EOF
# .gitignore
cat > .gitignore << 'EOF'
node_modules/
dist/
.wrangler/
.env
.DS_Store
EOF
# README.md
cat > README.md << 'EOF'
# PROJECT_NAME
Cloudflare Workers API
## Setup
\`\`\`bash
npm install
npm run dev
\`\`\`
## Deploy
\`\`\`bash
npm run deploy
\`\`\`
EOF
sed -i '' "s/PROJECT_NAME/$PROJECT_NAME/g" README.md
echo "✅ Scaffold complete! Run: cd $PROJECT_NAME && npm install && npm run dev"

View File

@@ -0,0 +1,339 @@
#!/bin/bash
# Python API Scaffold Generator
# Generates a production-ready FastAPI + Pydantic v2 + PostgreSQL project
set -e
PROJECT_NAME="${1:-my-python-api}"
echo "🐍 Creating Python API scaffold: $PROJECT_NAME"
# Create directory structure
mkdir -p "$PROJECT_NAME"/{app/{api/{endpoints,deps},core,db,models,schemas,services,utils},tests/{unit,integration},alembic/versions,.github/workflows}
# Create pyproject.toml
cat > "$PROJECT_NAME/pyproject.toml" <<'EOF'
[project]
name = "my-python-api"
version = "0.1.0"
description = "FastAPI + Pydantic v2 + PostgreSQL API"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.110.0",
"pydantic>=2.6.0",
"pydantic-settings>=2.1.0",
"uvicorn[standard]>=0.27.0",
"sqlalchemy>=2.0.25",
"asyncpg>=0.29.0",
"alembic>=1.13.0",
"python-jose[cryptography]>=3.3.0",
"passlib[bcrypt]>=1.7.4",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"pytest-cov>=4.1.0",
"httpx>=0.26.0",
"ruff>=0.2.0",
"mypy>=1.8.0",
]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP", "B", "A", "C4", "DTZ", "T10", "EM", "ISC", "ICN", "PIE", "PT", "RET", "SIM", "ARG", "PTH", "PD", "PGH", "PL", "TRY", "RUF"]
ignore = ["E501"]
[tool.mypy]
python_version = "3.11"
strict = true
plugins = ["pydantic.mypy"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
EOF
# Create main application
cat > "$PROJECT_NAME/app/main.py" <<'EOF'
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.endpoints import users, health
from app.core.config import settings
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
openapi_url=f"{settings.API_V1_STR}/openapi.json",
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Routes
app.include_router(health.router, prefix="/health", tags=["health"])
app.include_router(users.router, prefix=f"{settings.API_V1_STR}/users", tags=["users"])
@app.get("/")
async def root():
return {"message": f"Welcome to {settings.PROJECT_NAME}"}
EOF
# Create config
cat > "$PROJECT_NAME/app/core/config.py" <<'EOF'
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import List
class Settings(BaseSettings):
PROJECT_NAME: str = "My Python API"
VERSION: str = "0.1.0"
API_V1_STR: str = "/api/v1"
DATABASE_URL: str
SECRET_KEY: str
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
ALLOWED_ORIGINS: List[str] = ["http://localhost:3000"]
model_config = SettingsConfigDict(env_file=".env")
settings = Settings()
EOF
# Create database session
cat > "$PROJECT_NAME/app/db/session.py" <<'EOF'
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from app.core.config import settings
engine = create_async_engine(settings.DATABASE_URL, echo=True)
AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db():
async with AsyncSessionLocal() as session:
yield session
EOF
# Create User model
cat > "$PROJECT_NAME/app/models/user.py" <<'EOF'
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from datetime import datetime
from uuid import UUID, uuid4
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
hashed_password: Mapped[str] = mapped_column(String(255))
name: Mapped[str] = mapped_column(String(100))
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, onupdate=datetime.utcnow)
EOF
# Create Pydantic schemas
cat > "$PROJECT_NAME/app/schemas/user.py" <<'EOF'
from pydantic import BaseModel, EmailStr, Field, ConfigDict
from datetime import datetime
from uuid import UUID
class UserBase(BaseModel):
email: EmailStr
name: str = Field(min_length=1, max_length=100)
class UserCreate(UserBase):
password: str = Field(min_length=12, max_length=100)
class UserUpdate(BaseModel):
email: EmailStr | None = None
name: str | None = Field(None, min_length=1, max_length=100)
class UserResponse(UserBase):
id: UUID
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
EOF
# Create health endpoint
cat > "$PROJECT_NAME/app/api/endpoints/health.py" <<'EOF'
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from app.db.session import get_db
router = APIRouter()
@router.get("")
async def health_check(db: AsyncSession = Depends(get_db)):
try:
await db.execute(text("SELECT 1"))
return {"status": "healthy", "database": "connected"}
except Exception as e:
return {"status": "unhealthy", "database": "disconnected", "error": str(e)}
EOF
# Create users endpoint
cat > "$PROJECT_NAME/app/api/endpoints/users.py" <<'EOF'
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.db.session import get_db
from app.models.user import User
from app.schemas.user import UserCreate, UserResponse
from typing import List
from uuid import UUID
router = APIRouter()
@router.get("", response_model=List[UserResponse])
async def list_users(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User))
users = result.scalars().all()
return users
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: UUID, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.post("", response_model=UserResponse, status_code=201)
async def create_user(user_in: UserCreate, db: AsyncSession = Depends(get_db)):
# TODO: Hash password
user = User(email=user_in.email, name=user_in.name, hashed_password="hashed")
db.add(user)
await db.commit()
await db.refresh(user)
return user
EOF
# Create test
cat > "$PROJECT_NAME/tests/unit/test_users.py" <<'EOF'
import pytest
from httpx import AsyncClient
from app.main import app
@pytest.mark.asyncio
async def test_create_user():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.post(
"/api/v1/users",
json={"email": "test@example.com", "name": "Test User", "password": "SecurePass123!"}
)
assert response.status_code == 201
data = response.json()
assert data["email"] == "test@example.com"
assert "id" in data
EOF
# Create .env.example
cat > "$PROJECT_NAME/.env.example" <<'EOF'
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/mydb
SECRET_KEY=your-secret-key-here
ALLOWED_ORIGINS=["http://localhost:3000"]
EOF
# Create .gitignore
cat > "$PROJECT_NAME/.gitignore" <<'EOF'
__pycache__/
*.py[cod]
*$py.class
.env
.venv
venv/
.pytest_cache/
.coverage
.mypy_cache/
.ruff_cache/
*.db
*.sqlite3
EOF
# Create README
cat > "$PROJECT_NAME/README.md" <<'EOF'
# My Python API
FastAPI + Pydantic v2 + PostgreSQL production-ready API.
## Setup
```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install dependencies
pip install -e ".[dev]"
# Setup environment
cp .env.example .env
# Edit .env with your database credentials
# Run database migrations
alembic upgrade head
# Run development server
uvicorn app.main:app --reload --port 8000
```
## Testing
```bash
pytest --cov=app --cov-report=term-missing tests/
```
## API Documentation
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
## Deployment
See deployment documentation for production setup.
EOF
# Create GitHub Actions workflow
cat > "$PROJECT_NAME/.github/workflows/test.yml" <<'EOF'
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -e ".[dev]"
- run: ruff check .
- run: mypy app
- run: pytest --cov=app --cov-report=term-missing tests/
EOF
echo "✅ Python API scaffold created: $PROJECT_NAME"
echo ""
echo "Next steps:"
echo " cd $PROJECT_NAME"
echo " python -m venv venv && source venv/bin/activate"
echo " pip install -e \".[dev]\""
echo " cp .env.example .env # Edit with your database credentials"
echo " uvicorn app.main:app --reload"

View File

@@ -0,0 +1,308 @@
#!/bin/bash
# React Component Scaffold Generator
# Generates a production-ready React component with tests, Storybook, and CSS modules
set -e
COMPONENT_NAME="${1}"
if [ -z "$COMPONENT_NAME" ]; then
echo "Usage: $0 ComponentName"
echo "Example: $0 Button"
exit 1
fi
# Convert to kebab-case for file names
KEBAB_NAME=$(echo "$COMPONENT_NAME" | sed 's/\([A-Z]\)/-\1/g' | sed 's/^-//' | tr '[:upper:]' '[:lower:]')
echo "🎨 Creating React component: $COMPONENT_NAME ($KEBAB_NAME)"
# Create directory structure
mkdir -p "src/components/$COMPONENT_NAME"
cd "src/components/$COMPONENT_NAME"
# Create component file
cat > "$COMPONENT_NAME.tsx" <<EOF
import React from 'react';
import styles from './$COMPONENT_NAME.module.css';
export interface ${COMPONENT_NAME}Props {
/** The content to display */
children?: React.ReactNode;
/** Additional CSS class names */
className?: string;
/** Whether the component is disabled */
disabled?: boolean;
/** Click handler */
onClick?: () => void;
}
/**
* $COMPONENT_NAME component
*
* A reusable component for...
*
* @example
* \`\`\`tsx
* <$COMPONENT_NAME onClick={() => console.log('clicked')}>
* Click me
* </$COMPONENT_NAME>
* \`\`\`
*/
export const $COMPONENT_NAME: React.FC<${COMPONENT_NAME}Props> = ({
children,
className = '',
disabled = false,
onClick,
}) => {
const handleClick = () => {
if (!disabled && onClick) {
onClick();
}
};
return (
<div
className={\`\${styles.${KEBAB_NAME}} \${className} \${disabled ? styles.disabled : ''}\`}
onClick={handleClick}
role="button"
tabIndex={disabled ? -1 : 0}
aria-disabled={disabled}
>
{children}
</div>
);
};
$COMPONENT_NAME.displayName = '$COMPONENT_NAME';
EOF
# Create CSS module
cat > "$COMPONENT_NAME.module.css" <<EOF
.$KEBAB_NAME {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
border: 1px solid var(--border-color, #ccc);
border-radius: 0.25rem;
background-color: var(--bg-color, #fff);
color: var(--text-color, #333);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
.$KEBAB_NAME:hover:not(.disabled) {
background-color: var(--bg-hover-color, #f5f5f5);
border-color: var(--border-hover-color, #999);
}
.$KEBAB_NAME:focus-visible {
outline: 2px solid var(--focus-color, #0066ff);
outline-offset: 2px;
}
.$KEBAB_NAME.disabled {
opacity: 0.5;
cursor: not-allowed;
}
EOF
# Create index file
cat > "index.ts" <<EOF
export { $COMPONENT_NAME } from './$COMPONENT_NAME';
export type { ${COMPONENT_NAME}Props } from './$COMPONENT_NAME';
EOF
# Create test file
cat > "$COMPONENT_NAME.test.tsx" <<EOF
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { $COMPONENT_NAME } from './$COMPONENT_NAME';
describe('$COMPONENT_NAME', () => {
it('renders children correctly', () => {
render(<$COMPONENT_NAME>Test Content</$COMPONENT_NAME>);
expect(screen.getByText('Test Content')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<$COMPONENT_NAME onClick={handleClick}>Click me</$COMPONENT_NAME>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('does not call onClick when disabled', () => {
const handleClick = vi.fn();
render(
<$COMPONENT_NAME onClick={handleClick} disabled>
Click me
</$COMPONENT_NAME>
);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).not.toHaveBeenCalled();
});
it('applies custom className', () => {
const { container } = render(
<$COMPONENT_NAME className="custom-class">Test</$COMPONENT_NAME>
);
const element = container.firstChild;
expect(element).toHaveClass('custom-class');
});
it('has correct accessibility attributes', () => {
render(<$COMPONENT_NAME>Test</$COMPONENT_NAME>);
const element = screen.getByRole('button');
expect(element).toHaveAttribute('tabIndex', '0');
expect(element).toHaveAttribute('aria-disabled', 'false');
});
it('has correct accessibility attributes when disabled', () => {
render(<$COMPONENT_NAME disabled>Test</$COMPONENT_NAME>);
const element = screen.getByRole('button');
expect(element).toHaveAttribute('tabIndex', '-1');
expect(element).toHaveAttribute('aria-disabled', 'true');
});
});
EOF
# Create Storybook story
cat > "$COMPONENT_NAME.stories.tsx" <<EOF
import type { Meta, StoryObj } from '@storybook/react';
import { $COMPONENT_NAME } from './$COMPONENT_NAME';
const meta = {
title: 'Components/$COMPONENT_NAME',
component: $COMPONENT_NAME,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
onClick: { action: 'clicked' },
disabled: { control: 'boolean' },
className: { control: 'text' },
},
} satisfies Meta<typeof $COMPONENT_NAME>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'Default $COMPONENT_NAME',
},
};
export const Disabled: Story = {
args: {
children: 'Disabled $COMPONENT_NAME',
disabled: true,
},
};
export const WithClick: Story = {
args: {
children: 'Click me',
onClick: () => console.log('Clicked!'),
},
};
export const CustomClass: Story = {
args: {
children: 'Custom Styled',
className: 'custom-component-class',
},
};
EOF
# Create README
cat > "README.md" <<EOF
# $COMPONENT_NAME
A reusable React component.
## Usage
\`\`\`tsx
import { $COMPONENT_NAME } from './components/$COMPONENT_NAME';
function MyApp() {
return (
<$COMPONENT_NAME onClick={() => console.log('clicked')}>
Click me
</$COMPONENT_NAME>
);
}
\`\`\`
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| \`children\` | \`React.ReactNode\` | - | The content to display |
| \`className\` | \`string\` | \`''\` | Additional CSS class names |
| \`disabled\` | \`boolean\` | \`false\` | Whether the component is disabled |
| \`onClick\` | \`() => void\` | - | Click handler |
## Testing
\`\`\`bash
npm test -- $COMPONENT_NAME.test.tsx
\`\`\`
## Storybook
\`\`\`bash
npm run storybook
# View at http://localhost:6006/?path=/story/components-${KEBAB_NAME}
\`\`\`
## Accessibility
- Uses semantic HTML with \`role="button"\`
- Keyboard accessible with \`tabIndex\`
- Screen reader friendly with \`aria-disabled\`
- Focus visible with outline
## CSS Variables
Customize the component by overriding CSS variables:
\`\`\`css
.$KEBAB_NAME {
--bg-color: #fff;
--bg-hover-color: #f5f5f5;
--border-color: #ccc;
--border-hover-color: #999;
--text-color: #333;
--focus-color: #0066ff;
}
\`\`\`
EOF
cd ../../..
echo "✅ React component created: src/components/$COMPONENT_NAME"
echo ""
echo "Files created:"
echo " - $COMPONENT_NAME.tsx (component)"
echo " - $COMPONENT_NAME.module.css (styles)"
echo " - $COMPONENT_NAME.test.tsx (tests)"
echo " - $COMPONENT_NAME.stories.tsx (Storybook)"
echo " - index.ts (exports)"
echo " - README.md (documentation)"
echo ""
echo "Next steps:"
echo " 1. Import: import { $COMPONENT_NAME } from './components/$COMPONENT_NAME';"
echo " 2. Test: npm test -- $COMPONENT_NAME.test.tsx"
echo " 3. View in Storybook: npm run storybook"