
Senior Fullstack
- 247 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Own end-to-end features spanning REST or GraphQL APIs, database access, auth, and UI wiring when a single senior engineer must deliver vertical slices.
About
Senior full-stack persona for shipping complete features: API design, database modeling, authentication, frontend integration, and pragmatic tradeoffs across the entire web application stack.
- Designs cohesive API and UI contracts per feature
- Implements auth, persistence, and business logic together
- Wires third-party payments, email, or webhooks safely
- Balances server and client validation responsibilities
- Delivers deployable vertical slices incrementally
Senior Fullstack by the numbers
- 247 all-time installs (skills.sh)
- Ranked #1,533 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill senior-fullstackAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 247 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Own end-to-end features spanning REST or GraphQL APIs, database access, auth, and UI wiring when a single senior engineer must deliver vertical slices.
Files
Senior Fullstack
Fullstack development skill that scaffolds production-ready project structures (Next.js, FastAPI+React, MERN, Django+React) and runs static code quality analysis across security, complexity, dependency health, test coverage, and documentation — paired with reference guides for architecture patterns, development workflows, and stack selection.
Core Capabilities
- Project scaffolding — generate complete Next.js, FastAPI+React, MERN, or Django+React structures with TypeScript, Docker/docker-compose, env templates, and package configs.
- Code quality analysis — static scan for security issues, cyclomatic complexity, dependency CVEs, test coverage estimate, and documentation scoring, with an overall score/grade and prioritized P0/P1/P2 recommendations.
- Stack selection — decision matrix and trade-off guides for frameworks, databases, ORMs, auth, and deployment platforms by use case (MVP, SaaS, Enterprise).
- Architecture patterns — frontend component design, backend clean architecture, API design (REST/GraphQL), caching, and authentication.
- Lifecycle workflows — local setup, git, CI/CD, testing, code review, deployment, and observability.
When to Use
Use this skill when you hear:
- "scaffold a new project" / "set up a fullstack project" / "generate project boilerplate"
- "create a Next.js app" / "set up FastAPI with React"
- "analyze code quality" / "check for security issues in codebase"
- "what stack should I use"
Clarify First
Before scaffolding, confirm these inputs. If any is unknown or vague, ASK — do not assume:
- [ ] Stack — Next.js / FastAPI+React / MERN / Django+React (selects the scaffold template and entire file tree)
- [ ] App name & output path — where the project is written (
project_scaffolderpositional args) - [ ] Task — scaffold a new project vs analyze an existing codebase's quality (selects
project_scaffoldervscode_quality_analyzer)
Stop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact.
Tools
| Tool | Purpose | Command |
|---|---|---|
project_scaffolder.py | Scaffold a fullstack project structure with boilerplate, Docker, and env config | python scripts/project_scaffolder.py nextjs my-app --output ./projects |
code_quality_analyzer.py | Static-analyze a codebase for security, complexity, deps, coverage, and docs | python scripts/code_quality_analyzer.py . --verbose --json --output audit.json |
References
Load the reference that matches the task — keep this file lean and pull detail on demand:
- [references/tooling-workflows-and-quality.md](references/tooling-workflows-and-quality.md) — full tool docs (templates, flags, sample output, output formats), the three end-to-end workflows, the stack decision matrix / common issues, the troubleshooting table, and the success-criteria bar. Read when running the tools in depth or checking generated work before shipping.
- [references/architecture_patterns.md](references/architecture_patterns.md) — frontend component architecture, backend clean architecture / repository pattern, REST & GraphQL API design, database patterns, caching strategies, and authentication architecture. Read when designing or reviewing system structure.
- [references/development_workflows.md](references/development_workflows.md) — local dev setup, git workflows, CI/CD pipelines, testing strategies, code review process, deployment strategies, and observability. Read when standing up the development lifecycle.
- [references/tech_stack_guide.md](references/tech_stack_guide.md) — frontend/backend framework comparisons, database selection, ORMs, auth solutions, deployment platforms, and stack recommendations by use case. Read when choosing a stack.
Scope & Limitations
What this skill covers:
- Project scaffolding for Next.js, FastAPI+React, MERN, and Django+React stacks with Docker, TypeScript, and environment configuration
- Static code quality analysis including complexity metrics, security pattern detection, dependency vulnerability checks, test coverage estimation, and documentation scoring
- Stack selection guidance via the tech stack decision matrix and reference guides
- Fullstack architecture patterns (frontend component design, backend clean architecture, API design, caching, auth)
What this skill does NOT cover:
- Runtime performance profiling, load testing, or APM instrumentation -- see
senior-devopsfor observability tooling - Infrastructure provisioning, Terraform/Pulumi, or cloud deployment automation -- see
aws-solution-architectandsenior-devops - Comprehensive CVE scanning against live vulnerability databases -- use
npm audit,pip-audit, orsenior-secopsfor deep security analysis - Mobile or native desktop application scaffolding -- this skill targets web-based fullstack architectures only
Integration Points
| Skill | Integration | Data Flow |
|---|---|---|
senior-devops | CI/CD pipeline setup for scaffolded projects | Scaffolder output directory feeds into DevOps pipeline configuration and Docker deployment workflows |
senior-secops | Deep security audit after initial quality scan | Code quality analyzer P0/P1 security findings hand off to SecOps for remediation tracking and penetration testing |
senior-qa | Test strategy for scaffolded projects | Test coverage estimation from the analyzer informs QA test plan gaps; scaffolded test infrastructure provides the harness |
code-reviewer | Automated review of generated and existing code | Quality analyzer JSON report provides structured input for code review checklists and PR approval criteria |
senior-architect | Architecture validation of stack choices | Tech stack guide recommendations feed into architecture decision records; complexity metrics validate design compliance |
aws-solution-architect | Cloud deployment of scaffolded applications | Docker Compose configurations from the scaffolder translate into ECS/EKS task definitions and infrastructure blueprints |
Fullstack Architecture Patterns
Proven architectural patterns for scalable fullstack applications covering frontend, backend, and their integration.
---
Table of Contents
- Frontend Architecture
- Backend Architecture
- API Design Patterns
- Database Patterns
- Caching Strategies
- Authentication Architecture
---
Frontend Architecture
Component Architecture
Atomic Design Pattern
Organize components in hierarchical levels:
src/components/
├── atoms/ # Button, Input, Icon
├── molecules/ # SearchInput, FormField
├── organisms/ # Header, Footer, Sidebar
├── templates/ # PageLayout, DashboardLayout
└── pages/ # Home, Profile, SettingsWhen to use: Large applications with design systems and multiple teams.
Container/Presentational Pattern
// Presentational - pure rendering, no state
function UserCard({ name, email, avatar }: UserCardProps) {
return (
<div className="card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// Container - handles data fetching and state
function UserCardContainer({ userId }: { userId: string }) {
const { data, loading } = useUser(userId);
if (loading) return <Skeleton />;
return <UserCard {...data} />;
}When to use: When you need clear separation between UI and logic.
State Management Patterns
Server State vs Client State
| Type | Examples | Tools |
|---|---|---|
| Server State | User data, API responses | React Query, SWR |
| Client State | UI toggles, form inputs | Zustand, Jotai |
| URL State | Filters, pagination | Next.js router |
React Query for Server State:
function useUsers(filters: Filters) {
return useQuery({
queryKey: ["users", filters],
queryFn: () => api.getUsers(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes
});
}
// Mutations with optimistic updates
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.updateUser,
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ["users"] });
const previous = queryClient.getQueryData(["users"]);
queryClient.setQueryData(["users"], (old) =>
old.map(u => u.id === newUser.id ? newUser : u)
);
return { previous };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(["users"], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
}---
Backend Architecture
Clean Architecture
src/
├── domain/ # Business entities, no dependencies
│ ├── entities/ # User, Order, Product
│ └── interfaces/ # Repository interfaces
├── application/ # Use cases, application logic
│ ├── use-cases/ # CreateOrder, UpdateUser
│ └── services/ # OrderService, AuthService
├── infrastructure/ # External concerns
│ ├── database/ # Repository implementations
│ ├── http/ # Controllers, middleware
│ └── external/ # Third-party integrations
└── shared/ # Cross-cutting concerns
├── errors/
└── utils/Dependency Flow: domain ← application ← infrastructure
Repository Pattern:
// Domain interface
interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<User>;
delete(id: string): Promise<void>;
}
// Infrastructure implementation
class PostgresUserRepository implements UserRepository {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const row = await this.db.query(
"SELECT * FROM users WHERE id = $1",
[id]
);
return row ? this.toEntity(row) : null;
}
private toEntity(row: UserRow): User {
return new User({
id: row.id,
email: row.email,
name: row.name,
createdAt: row.created_at,
});
}
}Middleware Pipeline
// Express middleware chain
app.use(cors());
app.use(helmet());
app.use(requestId());
app.use(logger());
app.use(authenticate());
app.use(rateLimit());
app.use("/api", routes);
app.use(errorHandler());
// Custom middleware example
function requestId() {
return (req: Request, res: Response, next: NextFunction) => {
req.id = req.headers["x-request-id"] || crypto.randomUUID();
res.setHeader("x-request-id", req.id);
next();
};
}
function errorHandler() {
return (err: Error, req: Request, res: Response, next: NextFunction) => {
const status = err instanceof AppError ? err.status : 500;
const message = status === 500 ? "Internal Server Error" : err.message;
logger.error({ err, requestId: req.id });
res.status(status).json({ error: message, requestId: req.id });
};
}---
API Design Patterns
REST Best Practices
Resource Naming:
- Use nouns, not verbs:
/usersnot/getUsers - Use plural:
/usersnot/user - Nest for relationships:
/users/{id}/orders
HTTP Methods:
| Method | Purpose | Idempotent |
|---|---|---|
| GET | Retrieve | Yes |
| POST | Create | No |
| PUT | Replace | Yes |
| PATCH | Partial update | No |
| DELETE | Remove | Yes |
Response Envelope:
// Success response
{
"data": { /* resource */ },
"meta": {
"requestId": "abc-123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
// Paginated response
{
"data": [/* items */],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8
}
}
// Error response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
},
"meta": { "requestId": "abc-123" }
}GraphQL Architecture
Schema-First Design:
type Query {
user(id: ID!): User
users(filter: UserFilter, page: PageInput): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): UserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UserPayload!
}
type User {
id: ID!
email: String!
profile: Profile
orders(first: Int, after: String): OrderConnection!
}
type UserPayload {
user: User
errors: [Error!]
}Resolver Pattern:
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.findById(id);
},
},
User: {
// Field resolver for related data
orders: async (user, { first, after }, { dataSources }) => {
return dataSources.orderAPI.findByUserId(user.id, { first, after });
},
},
};DataLoader for N+1 Prevention:
const userLoader = new DataLoader(async (userIds: string[]) => {
const users = await db.query(
"SELECT * FROM users WHERE id = ANY($1)",
[userIds]
);
// Return in same order as input
return userIds.map(id => users.find(u => u.id === id));
});---
Database Patterns
Connection Pooling
// PostgreSQL with connection pool
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20, // Maximum connections
idleTimeoutMillis: 30000, // Close idle connections
connectionTimeoutMillis: 2000,
});
// Prisma with connection pool
const prisma = new PrismaClient({
datasources: {
db: {
url: `${process.env.DATABASE_URL}?connection_limit=20&pool_timeout=10`,
},
},
});Transaction Patterns
// Unit of Work pattern
async function transferFunds(from: string, to: string, amount: number) {
return await prisma.$transaction(async (tx) => {
const sender = await tx.account.update({
where: { id: from },
data: { balance: { decrement: amount } },
});
if (sender.balance < 0) {
throw new InsufficientFundsError();
}
await tx.account.update({
where: { id: to },
data: { balance: { increment: amount } },
});
return tx.transaction.create({
data: { fromId: from, toId: to, amount },
});
});
}Read Replicas
// Route reads to replica
const readDB = new PrismaClient({
datasources: { db: { url: process.env.READ_DATABASE_URL } },
});
const writeDB = new PrismaClient({
datasources: { db: { url: process.env.WRITE_DATABASE_URL } },
});
class UserRepository {
async findById(id: string) {
return readDB.user.findUnique({ where: { id } });
}
async create(data: CreateUserData) {
return writeDB.user.create({ data });
}
}---
Caching Strategies
Cache Layers
Request → CDN Cache → Application Cache → Database Cache → DatabaseCache-Aside Pattern:
async function getUser(id: string): Promise<User> {
const cacheKey = `user:${id}`;
// 1. Try cache
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 2. Fetch from database
const user = await db.user.findUnique({ where: { id } });
if (!user) throw new NotFoundError();
// 3. Store in cache
await redis.set(cacheKey, JSON.stringify(user), "EX", 3600);
return user;
}
// Invalidate on update
async function updateUser(id: string, data: UpdateData): Promise<User> {
const user = await db.user.update({ where: { id }, data });
await redis.del(`user:${id}`);
return user;
}HTTP Cache Headers:
// Immutable assets (hashed filenames)
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
// API responses
res.setHeader("Cache-Control", "private, max-age=0, must-revalidate");
res.setHeader("ETag", generateETag(data));
// Static pages
res.setHeader("Cache-Control", "public, max-age=3600, stale-while-revalidate=86400");---
Authentication Architecture
JWT + Refresh Token Flow
1. User logs in → Server returns access token (15min) + refresh token (7d)
2. Client stores tokens (httpOnly cookie for refresh, memory for access)
3. Access token expires → Client uses refresh token to get new pair
4. Refresh token expires → User must log in againImplementation:
// Token generation
function generateTokens(user: User) {
const accessToken = jwt.sign(
{ sub: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
const refreshToken = jwt.sign(
{ sub: user.id, tokenVersion: user.tokenVersion },
process.env.REFRESH_SECRET,
{ expiresIn: "7d" }
);
return { accessToken, refreshToken };
}
// Refresh endpoint
app.post("/auth/refresh", async (req, res) => {
const refreshToken = req.cookies.refreshToken;
try {
const payload = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
const user = await db.user.findUnique({ where: { id: payload.sub } });
// Check token version (invalidation mechanism)
if (user.tokenVersion !== payload.tokenVersion) {
throw new Error("Token revoked");
}
const tokens = generateTokens(user);
setRefreshCookie(res, tokens.refreshToken);
res.json({ accessToken: tokens.accessToken });
} catch {
res.status(401).json({ error: "Invalid refresh token" });
}
});Session-Based Auth
// Redis session store
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
sameSite: "lax",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
},
}));
// Login
app.post("/auth/login", async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
req.session.userId = user.id;
res.json({ user });
});
// Middleware
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: "Authentication required" });
}
next();
}---
Decision Matrix
| Pattern | Complexity | Scalability | When to Use |
|---|---|---|---|
| Monolith | Low | Medium | MVPs, small teams |
| Modular Monolith | Medium | High | Growing teams |
| Microservices | High | Very High | Large orgs, diverse tech |
| REST | Low | High | CRUD APIs, public APIs |
| GraphQL | Medium | High | Complex data needs, mobile apps |
| JWT Auth | Low | High | Stateless APIs, microservices |
| Session Auth | Low | Medium | Traditional web apps |
Fullstack Development Workflows
Complete development lifecycle workflows from local setup to production deployment.
---
Table of Contents
- Local Development Setup
- Git Workflows
- CI/CD Pipelines
- Testing Strategies
- Code Review Process
- Deployment Strategies
- Monitoring and Observability
---
Local Development Setup
Docker Compose Development Environment
# docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/app
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:Multistage Dockerfile:
# Base stage
FROM node:20-alpine AS base
WORKDIR /app
RUN apk add --no-cache libc6-compat
# Development stage
FROM base AS development
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
# Builder stage
FROM base AS builder
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM base AS production
ENV NODE_ENV=production
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]Environment Configuration
# .env.local (development)
DATABASE_URL="postgresql://user:pass@localhost:5432/app_dev"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="development-secret-change-in-prod"
LOG_LEVEL="debug"
# .env.test
DATABASE_URL="postgresql://user:pass@localhost:5432/app_test"
LOG_LEVEL="error"
# .env.production (via secrets management)
DATABASE_URL="${DATABASE_URL}"
REDIS_URL="${REDIS_URL}"
JWT_SECRET="${JWT_SECRET}"Environment validation:
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
});
export const env = envSchema.parse(process.env);---
Git Workflows
Trunk-Based Development
main (protected)
│
├── feature/user-auth (short-lived, 1-2 days max)
│ └── squash merge → main
│
├── feature/payment-flow
│ └── squash merge → main
│
└── release/v1.2.0 (cut from main for hotfixes)Branch naming:
feature/description- New featuresfix/description- Bug fixeschore/description- Maintenance tasksrelease/vX.Y.Z- Release branches
Commit Standards
Conventional Commits:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]Types:
feat: New featurefix: Bug fixdocs: Documentationstyle: Formattingrefactor: Code restructuringtest: Adding testschore: Maintenance
Examples:
feat(auth): add password reset flow
Implement password reset with email verification.
Tokens expire after 1 hour.
Closes #123
---
fix(api): handle null response in user endpoint
The API was returning 500 when user profile was incomplete.
Now returns partial data with null fields.
---
chore(deps): update Next.js to 14.1.0
Breaking changes addressed:
- Updated Image component usage
- Migrated to new metadata APIPre-commit Hooks
// package.json
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
# .husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit $1---
CI/CD Pipelines
GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run type-check
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run test:unit
- run: npm run test:integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test
- uses: codecov/codecov-action@v3
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy-preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
# Deploy to preview environment
- name: Deploy Preview
run: |
# Deploy logic here
echo "Deployed to preview-${{ github.event.pull_request.number }}"
deploy-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: build
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy Production
run: |
# Production deployment
echo "Deployed to production"Database Migrations in CI
# Part of deploy job
- name: Run Migrations
run: |
npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Verify Migration
run: |
npx prisma migrate status---
Testing Strategies
Testing Pyramid
/\
/ \ E2E Tests (10%)
/ \ - Critical user journeys
/──────\
/ \ Integration Tests (20%)
/ \ - API endpoints
/────────────\ - Database operations
/ \
/ \ Unit Tests (70%)
/──────────────────\ - Components, hooks, utilitiesUnit Testing
// Component test with React Testing Library
import { render, screen, fireEvent } from "@testing-library/react";
import { UserForm } from "./UserForm";
describe("UserForm", () => {
it("submits form with valid data", async () => {
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: "test@example.com" },
});
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: "John Doe" },
});
fireEvent.click(screen.getByRole("button", { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: "test@example.com",
name: "John Doe",
});
});
});
it("shows validation error for invalid email", async () => {
render(<UserForm onSubmit={vi.fn()} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: "invalid" },
});
fireEvent.click(screen.getByRole("button", { name: /submit/i }));
expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
});
});Integration Testing
// API integration test
import { createTestClient } from "./test-utils";
import { db } from "@/lib/db";
describe("POST /api/users", () => {
beforeEach(async () => {
await db.user.deleteMany();
});
it("creates user with valid data", async () => {
const client = createTestClient();
const response = await client.post("/api/users", {
email: "new@example.com",
name: "New User",
});
expect(response.status).toBe(201);
expect(response.data.user.email).toBe("new@example.com");
// Verify in database
const user = await db.user.findUnique({
where: { email: "new@example.com" },
});
expect(user).toBeTruthy();
});
it("returns 409 for duplicate email", async () => {
await db.user.create({
data: { email: "existing@example.com", name: "Existing" },
});
const client = createTestClient();
const response = await client.post("/api/users", {
email: "existing@example.com",
name: "Duplicate",
});
expect(response.status).toBe(409);
expect(response.data.error.code).toBe("EMAIL_EXISTS");
});
});E2E Testing with Playwright
// e2e/auth.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Authentication", () => {
test("user can log in and access dashboard", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "user@example.com");
await page.fill('[name="password"]', "password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/dashboard");
await expect(page.locator("h1")).toHaveText("Welcome back");
});
test("shows error for invalid credentials", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "wrong@example.com");
await page.fill('[name="password"]', "wrongpassword");
await page.click('button[type="submit"]');
await expect(page.locator('[role="alert"]')).toHaveText(
"Invalid email or password"
);
});
});---
Code Review Process
PR Template
## Summary
<!-- Brief description of changes -->
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Changes Made
<!-- List specific changes -->
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed
## Screenshots
<!-- If applicable -->
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new warningsReview Checklist
Functionality:
- Does the code do what it's supposed to?
- Are edge cases handled?
- Is error handling appropriate?
Code Quality:
- Is the code readable and maintainable?
- Are there any code smells?
- Is there unnecessary duplication?
Performance:
- Are there N+1 queries?
- Is caching used appropriately?
- Are there memory leaks?
Security:
- Is user input validated?
- Are there injection vulnerabilities?
- Is sensitive data protected?
---
Deployment Strategies
Blue-Green Deployment
Load Balancer
│
┌────────────┴────────────┐
│ │
┌────┴────┐ ┌─────┴────┐
│ Blue │ │ Green │
│ (Live) │ │ (Idle) │
└─────────┘ └──────────┘
1. Deploy new version to Green
2. Run smoke tests on Green
3. Switch traffic to Green
4. Blue becomes idle (rollback target)Canary Deployment
Load Balancer
│
┌────────────┴────────────┐
│ │
│ 95% 5% │
▼ ▼
┌─────────┐ ┌──────────┐
│ Stable │ │ Canary │
│ v1.0.0 │ │ v1.1.0 │
└─────────┘ └──────────┘
1. Deploy canary with small traffic %
2. Monitor error rates, latency
3. Gradually increase traffic
4. Full rollout or rollbackFeature Flags
// Feature flag service
const flags = {
newCheckoutFlow: {
enabled: true,
rolloutPercentage: 25,
allowedUsers: ["beta-testers"],
},
};
function isFeatureEnabled(flag: string, userId: string): boolean {
const config = flags[flag];
if (!config?.enabled) return false;
// Check allowed users
if (config.allowedUsers?.includes(userId)) return true;
// Check rollout percentage
const hash = hashUserId(userId);
return hash < config.rolloutPercentage;
}
// Usage
if (isFeatureEnabled("newCheckoutFlow", user.id)) {
return <NewCheckout />;
}
return <LegacyCheckout />;---
Monitoring and Observability
Structured Logging
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL || "info",
formatters: {
level: (label) => ({ level: label }),
},
});
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
const requestId = req.headers["x-request-id"] || crypto.randomUUID();
res.on("finish", () => {
logger.info({
type: "request",
requestId,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: Date.now() - start,
userAgent: req.headers["user-agent"],
});
});
next();
});
// Application logging
logger.info({ userId: user.id, action: "login" }, "User logged in");
logger.error({ err, orderId }, "Failed to process order");Metrics Collection
import { Counter, Histogram } from "prom-client";
const httpRequestsTotal = new Counter({
name: "http_requests_total",
help: "Total HTTP requests",
labelNames: ["method", "path", "status"],
});
const httpRequestDuration = new Histogram({
name: "http_request_duration_seconds",
help: "HTTP request duration",
labelNames: ["method", "path"],
buckets: [0.1, 0.3, 0.5, 1, 3, 5, 10],
});
// Middleware
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer({
method: req.method,
path: req.route?.path || req.path,
});
res.on("finish", () => {
httpRequestsTotal.inc({
method: req.method,
path: req.route?.path || req.path,
status: res.statusCode,
});
end();
});
next();
});Health Checks
app.get("/health", async (req, res) => {
const checks = {
database: await checkDatabase(),
redis: await checkRedis(),
memory: checkMemory(),
};
const healthy = Object.values(checks).every((c) => c.status === "healthy");
res.status(healthy ? 200 : 503).json({
status: healthy ? "healthy" : "unhealthy",
checks,
timestamp: new Date().toISOString(),
});
});
async function checkDatabase() {
try {
await db.$queryRaw`SELECT 1`;
return { status: "healthy" };
} catch (error) {
return { status: "unhealthy", error: error.message };
}
}
function checkMemory() {
const used = process.memoryUsage();
const heapUsedMB = Math.round(used.heapUsed / 1024 / 1024);
const heapTotalMB = Math.round(used.heapTotal / 1024 / 1024);
return {
status: heapUsedMB < heapTotalMB * 0.9 ? "healthy" : "warning",
heapUsedMB,
heapTotalMB,
};
}---
Quick Reference
Daily Workflow
# 1. Start work
git checkout main && git pull
git checkout -b feature/my-feature
# 2. Develop with hot reload
docker-compose up -d
npm run dev
# 3. Test changes
npm run test
npm run lint
# 4. Commit
git add -A
git commit -m "feat(scope): description"
# 5. Push and create PR
git push -u origin feature/my-feature
gh pr createRelease Workflow
# 1. Ensure main is stable
git checkout main
npm run test:all
# 2. Create release
npm version minor # or major/patch
git push --follow-tags
# 3. Verify deployment
# CI/CD deploys automatically
# Monitor dashboardsFullstack Tech Stack Guide
Technology selection guide with trade-offs, use cases, and integration patterns for modern fullstack development.
---
Table of Contents
- Frontend Frameworks
- Backend Frameworks
- Databases
- ORMs and Query Builders
- Authentication Solutions
- Deployment Platforms
- Stack Recommendations
---
Frontend Frameworks
Next.js
Best for: Production React apps, SEO-critical sites, full-stack applications
| Pros | Cons |
|---|---|
| Server components, streaming | Learning curve for advanced features |
| Built-in routing, API routes | Vercel lock-in concerns |
| Excellent DX and performance | Bundle size can grow |
| Strong TypeScript support | Complex mental model (client/server) |
When to choose:
- Need SSR/SSG for SEO
- Building a product that may scale
- Want full-stack in one framework
- Team familiar with React
// App Router pattern
// app/users/page.tsx
async function UsersPage() {
const users = await db.user.findMany(); // Server component
return <UserList users={users} />;
}
// app/users/[id]/page.tsx
export async function generateStaticParams() {
const users = await db.user.findMany();
return users.map((user) => ({ id: user.id }));
}React + Vite
Best for: SPAs, dashboards, internal tools
| Pros | Cons |
|---|---|
| Fast development with HMR | No SSR out of the box |
| Simple mental model | Manual routing setup |
| Flexible architecture | No built-in API routes |
| Smaller bundle potential | Need separate backend |
When to choose:
- Building internal dashboards
- SEO not important
- Need maximum flexibility
- Prefer decoupled frontend/backend
Vue 3
Best for: Teams transitioning from jQuery, progressive enhancement
| Pros | Cons |
|---|---|
| Gentle learning curve | Smaller ecosystem than React |
| Excellent documentation | Fewer enterprise adoptions |
| Single-file components | Composition API learning curve |
| Good TypeScript support | Two paradigms (Options/Composition) |
When to choose:
- Team new to modern frameworks
- Progressive enhancement needed
- Prefer official solutions (Pinia, Vue Router)
Comparison Matrix
| Feature | Next.js | React+Vite | Vue 3 | Svelte |
|---|---|---|---|---|
| SSR | Built-in | Manual | Nuxt | SvelteKit |
| Bundle size | Medium | Small | Small | Smallest |
| Learning curve | Medium | Low | Low | Low |
| Enterprise adoption | High | High | Medium | Low |
| Job market | Large | Large | Medium | Small |
---
Backend Frameworks
Node.js Ecosystem
Express.js
import express from "express";
import { userRouter } from "./routes/users";
const app = express();
app.use(express.json());
app.use("/api/users", userRouter);
app.listen(3000);| Pros | Cons |
|---|---|
| Minimal, flexible | No structure opinions |
| Huge middleware ecosystem | Callback-based (legacy) |
| Well understood | Manual TypeScript setup |
Fastify
import Fastify from "fastify";
const app = Fastify({ logger: true });
app.get("/users/:id", {
schema: {
params: { type: "object", properties: { id: { type: "string" } } },
response: { 200: UserSchema },
},
handler: async (request) => {
return db.user.findUnique({ where: { id: request.params.id } });
},
});| Pros | Cons |
|---|---|
| High performance | Smaller ecosystem |
| Built-in validation | Different plugin model |
| TypeScript-first | Less community content |
NestJS
@Controller("users")
export class UsersController {
constructor(private usersService: UsersService) {}
@Get(":id")
findOne(@Param("id") id: string) {
return this.usersService.findOne(id);
}
@Post()
@UseGuards(AuthGuard)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}| Pros | Cons |
|---|---|
| Strong architecture | Steep learning curve |
| Full-featured (GraphQL, WebSockets) | Heavy for small projects |
| Enterprise-ready | Decorator complexity |
Python Ecosystem
FastAPI
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
app = FastAPI()
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404)
return user| Pros | Cons |
|---|---|
| Auto-generated docs | Python GIL limitations |
| Type hints → validation | Async ecosystem maturing |
| High performance | Smaller than Django ecosystem |
Django
| Pros | Cons |
|---|---|
| Batteries included | Monolithic |
| Admin panel | ORM limitations |
| Mature ecosystem | Async support newer |
Framework Selection Guide
| Use Case | Recommendation |
|---|---|
| API-first startup | FastAPI or Fastify |
| Enterprise backend | NestJS or Django |
| Microservices | Fastify or Go |
| Rapid prototype | Express or Django |
| Full-stack TypeScript | Next.js API routes |
---
Databases
PostgreSQL
Best for: Most applications, relational data, ACID compliance
-- JSON support
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
profile JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Full-text search
CREATE INDEX users_search_idx ON users
USING GIN (to_tsvector('english', email || ' ' || profile->>'name'));
SELECT * FROM users
WHERE to_tsvector('english', email || ' ' || profile->>'name')
@@ to_tsquery('john');| Feature | Rating |
|---|---|
| ACID compliance | Excellent |
| JSON support | Excellent |
| Full-text search | Good |
| Horizontal scaling | Requires setup |
| Managed options | Many (RDS, Supabase, Neon) |
MongoDB
Best for: Document-heavy apps, flexible schemas, rapid prototyping
// Flexible schema
const userSchema = new Schema({
email: { type: String, required: true, unique: true },
profile: {
name: String,
preferences: Schema.Types.Mixed, // Any structure
},
orders: [{ type: Schema.Types.ObjectId, ref: "Order" }],
});| Feature | Rating |
|---|---|
| Schema flexibility | Excellent |
| Horizontal scaling | Excellent |
| Transactions | Good (4.0+) |
| Joins | Limited |
| Managed options | Atlas |
Redis
Best for: Caching, sessions, real-time features, queues
// Session storage
await redis.set(`session:${sessionId}`, JSON.stringify(user), "EX", 3600);
// Rate limiting
const requests = await redis.incr(`rate:${ip}`);
if (requests === 1) await redis.expire(`rate:${ip}`, 60);
if (requests > 100) throw new TooManyRequestsError();
// Pub/Sub
redis.publish("notifications", JSON.stringify({ userId, message }));Database Selection Matrix
| Requirement | PostgreSQL | MongoDB | MySQL |
|---|---|---|---|
| Complex queries | Best | Limited | Good |
| Schema flexibility | Good (JSONB) | Best | Limited |
| Transactions | Best | Good | Good |
| Horizontal scale | Manual | Built-in | Manual |
| Cloud managed | Many | Atlas | Many |
---
ORMs and Query Builders
Prisma
Best for: TypeScript projects, schema-first development
// schema.prisma
model User {
id String @id @default(cuid())
email String @unique
posts Post[]
profile Profile?
createdAt DateTime @default(now())
}
// Usage - fully typed
const user = await prisma.user.findUnique({
where: { email: "user@example.com" },
include: { posts: true, profile: true },
});
// user.posts is Post[] - TypeScript knows| Pros | Cons |
|---|---|
| Excellent TypeScript | Generated client size |
| Schema migrations | Limited raw SQL support |
| Visual studio | Some edge case limitations |
Drizzle
Best for: SQL-first TypeScript, performance-critical apps
// Schema definition
const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});
// Query - SQL-like syntax
const result = await db
.select()
.from(users)
.where(eq(users.email, "user@example.com"))
.leftJoin(posts, eq(posts.userId, users.id));| Pros | Cons |
|---|---|
| Lightweight | Newer, smaller community |
| SQL-like syntax | Fewer integrations |
| Fast runtime | Manual migrations |
SQLAlchemy (Python)
# Model definition
class User(Base):
__tablename__ = "users"
id = Column(UUID, primary_key=True, default=uuid4)
email = Column(String(255), unique=True, nullable=False)
posts = relationship("Post", back_populates="author")
# Query
users = session.query(User)\
.filter(User.email.like("%@example.com"))\
.options(joinedload(User.posts))\
.all()---
Authentication Solutions
Auth.js (NextAuth)
Best for: Next.js apps, social logins
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub,
Credentials({
credentials: { email: {}, password: {} },
authorize: async (credentials) => {
const user = await verifyCredentials(credentials);
return user;
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) token.role = user.role;
return token;
},
},
});| Pros | Cons |
|---|---|
| Many providers | Next.js focused |
| Session management | Complex customization |
| Database adapters | Breaking changes between versions |
Clerk
Best for: Rapid development, hosted solution
// Middleware
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
// Usage
import { auth } from "@clerk/nextjs/server";
export async function GET() {
const { userId } = await auth();
if (!userId) return new Response("Unauthorized", { status: 401 });
// ...
}| Pros | Cons |
|---|---|
| Beautiful UI components | Vendor lock-in |
| Managed infrastructure | Cost at scale |
| Multi-factor auth | Data residency concerns |
Custom JWT
Best for: Full control, microservices
// Token generation
function generateTokens(user: User) {
const accessToken = jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
const refreshToken = jwt.sign(
{ sub: user.id, version: user.tokenVersion },
process.env.REFRESH_SECRET,
{ expiresIn: "7d" }
);
return { accessToken, refreshToken };
}
// Middleware
function authenticate(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "No token" });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: "Invalid token" });
}
}---
Deployment Platforms
Vercel
Best for: Next.js, frontend-focused teams
| Pros | Cons |
|---|---|
| Zero-config Next.js | Expensive at scale |
| Edge functions | Vendor lock-in |
| Preview deployments | Limited backend options |
| Global CDN | Cold starts |
Railway
Best for: Full-stack apps, databases included
| Pros | Cons |
|---|---|
| Simple deployment | Smaller community |
| Built-in databases | Limited regions |
| Good pricing | Fewer integrations |
AWS (ECS/Lambda)
Best for: Enterprise, complex requirements
| Pros | Cons |
|---|---|
| Full control | Complex setup |
| Cost-effective at scale | Steep learning curve |
| Any technology | Requires DevOps knowledge |
Deployment Selection
| Requirement | Platform |
|---|---|
| Next.js simplicity | Vercel |
| Full-stack + DB | Railway, Render |
| Enterprise scale | AWS, GCP |
| Container control | Fly.io, Railway |
| Budget startup | Railway, Render |
---
Stack Recommendations
Startup MVP
Frontend: Next.js 14 (App Router)
Backend: Next.js API Routes
Database: PostgreSQL (Neon/Supabase)
Auth: Auth.js or Clerk
Deploy: Vercel
Cache: Vercel KV or Upstash RedisWhy: Fastest time to market, single deployment, good scaling path.
SaaS Product
Frontend: Next.js 14
Backend: Separate API (FastAPI or NestJS)
Database: PostgreSQL (RDS)
Auth: Custom JWT + Auth.js
Deploy: Vercel (frontend) + AWS ECS (backend)
Cache: Redis (ElastiCache)
Queue: SQS or BullMQWhy: Separation allows independent scaling, team specialization.
Enterprise Application
Frontend: Next.js or React + Vite
Backend: NestJS or Go
Database: PostgreSQL (Aurora)
Auth: Keycloak or Auth0
Deploy: Kubernetes (EKS/GKE)
Cache: Redis Cluster
Queue: Kafka or RabbitMQ
Observability: Datadog or Grafana StackWhy: Maximum control, compliance requirements, team expertise.
Internal Tool
Frontend: React + Vite + Tailwind
Backend: Express or FastAPI
Database: PostgreSQL or SQLite
Auth: OIDC with corporate IdP
Deploy: Docker on internal infrastructureWhy: Simple, low maintenance, integrates with existing systems.
---
Quick Decision Guide
| Question | If Yes → | If No → |
|---|---|---|
| Need SEO? | Next.js SSR | React SPA |
| Complex backend? | Separate API | Next.js routes |
| Team knows Python? | FastAPI | Node.js |
| Need real-time? | Add WebSockets | REST is fine |
| Enterprise compliance? | Self-hosted | Managed services |
| Budget constrained? | Railway/Render | Vercel/AWS |
| Schema changes often? | MongoDB | PostgreSQL |
Tooling, Workflows & Quality Bar
Read this when running the scaffolder or quality analyzer in depth, executing an end-to-end project workflow, looking up the stack decision matrix / common issues, troubleshooting tool behavior, or checking generated work against the quality bar.
Tools
Project Scaffolder
Generates fullstack project structures with boilerplate code.
Supported Templates:
nextjs- Next.js 14+ with App Router, TypeScript, Tailwind CSSfastapi-react- FastAPI backend + React frontend + PostgreSQLmern- MongoDB, Express, React, Node.js with TypeScriptdjango-react- Django REST Framework + React frontend
Usage:
# List available templates
python scripts/project_scaffolder.py --list-templates
# Create Next.js project
python scripts/project_scaffolder.py nextjs my-app
# Create FastAPI + React project
python scripts/project_scaffolder.py fastapi-react my-api
# Create MERN stack project
python scripts/project_scaffolder.py mern my-project
# Create Django + React project
python scripts/project_scaffolder.py django-react my-app
# Specify output directory
python scripts/project_scaffolder.py nextjs my-app --output ./projects
# JSON output
python scripts/project_scaffolder.py nextjs my-app --jsonParameters:
| Parameter | Description |
|---|---|
template | Template name (nextjs, fastapi-react, mern, django-react) |
project_name | Name for the new project directory |
--output, -o | Output directory (default: current directory) |
--list-templates, -l | List all available templates |
--json | Output in JSON format |
Output includes:
- Project structure with all necessary files
- Package configurations (package.json, requirements.txt)
- TypeScript configuration
- Docker and docker-compose setup
- Environment file templates
- Next steps for running the project
---
Code Quality Analyzer
Analyzes fullstack codebases for quality issues.
Analysis Categories:
- Security vulnerabilities (hardcoded secrets, injection risks)
- Code complexity metrics (cyclomatic complexity, nesting depth)
- Dependency health (outdated packages, known CVEs)
- Test coverage estimation
- Documentation quality
Usage:
# Analyze current directory
python scripts/code_quality_analyzer.py .
# Analyze specific project
python scripts/code_quality_analyzer.py /path/to/project
# Verbose output with detailed findings
python scripts/code_quality_analyzer.py . --verbose
# JSON output
python scripts/code_quality_analyzer.py . --json
# Save report to file
python scripts/code_quality_analyzer.py . --output report.jsonParameters:
| Parameter | Description |
|---|---|
project_path | Path to project directory (default: current directory) |
--verbose, -v | Show detailed findings |
--json | Output in JSON format |
--output, -o | Write report to file |
Output includes:
- Overall score (0-100) with letter grade
- Security issues by severity (critical, high, medium, low)
- High complexity files
- Vulnerable dependencies with CVE references
- Test coverage estimate
- Documentation completeness
- Prioritized recommendations
Sample Output:
============================================================
CODE QUALITY ANALYSIS REPORT
============================================================
Overall Score: 75/100 (Grade: C)
Files Analyzed: 45
Total Lines: 12,500
--- SECURITY ---
Critical: 1
High: 2
Medium: 5
--- COMPLEXITY ---
Average Complexity: 8.5
High Complexity Files: 3
--- RECOMMENDATIONS ---
1. [P0] SECURITY
Issue: Potential hardcoded secret detected
Action: Remove or secure sensitive data at line 42---
Workflows
Workflow 1: Start New Project
1. Choose appropriate stack based on requirements 2. Scaffold project structure 3. Run initial quality check 4. Set up development environment
# 1. Scaffold project
python scripts/project_scaffolder.py nextjs my-saas-app
# 2. Navigate and install
cd my-saas-app
npm install
# 3. Configure environment
cp .env.example .env.local
# 4. Run quality check
python ../scripts/code_quality_analyzer.py .
# 5. Start development
npm run devWorkflow 2: Audit Existing Codebase
1. Run code quality analysis 2. Review security findings 3. Address critical issues first 4. Plan improvements
# 1. Full analysis
python scripts/code_quality_analyzer.py /path/to/project --verbose
# 2. Generate detailed report
python scripts/code_quality_analyzer.py /path/to/project --json --output audit.json
# 3. Address P0 issues immediately
# 4. Create tickets for P1/P2 issuesWorkflow 3: Stack Selection
Use the tech stack guide to evaluate options:
1. SEO Required? → Next.js with SSR 2. API-heavy backend? → Separate FastAPI or NestJS 3. Real-time features? → Add WebSocket layer 4. Team expertise → Match stack to team skills
See references/tech_stack_guide.md for detailed comparison.
---
Quick Reference
Stack Decision Matrix
| Requirement | Recommendation |
|---|---|
| SEO-critical site | Next.js with SSR |
| Internal dashboard | React + Vite |
| API-first backend | FastAPI or Fastify |
| Enterprise scale | NestJS + PostgreSQL |
| Rapid prototype | Next.js API routes |
| Document-heavy data | MongoDB |
| Complex queries | PostgreSQL |
Common Issues
| Issue | Solution |
|---|---|
| N+1 queries | Use DataLoader or eager loading |
| Slow builds | Check bundle size, lazy load |
| Auth complexity | Use Auth.js or Clerk |
| Type errors | Enable strict mode in tsconfig |
| CORS issues | Configure middleware properly |
---
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Scaffolder creates empty files | Template name misspelled or unsupported | Run python project_scaffolder.py --list-templates to verify available templates |
| Quality analyzer reports 0 files analyzed | Project path points to wrong directory or contains only non-code files | Confirm the path contains .ts, .tsx, .js, .jsx, .py, .go, .java, .rb, .php, or .cs files outside node_modules/, .git/, dist/, and other skip directories |
| False-positive hardcoded secret warnings | Regex matches long strings assigned to variables named password, secret, token, etc. | Review flagged lines manually; suppress by renaming variables or extracting values to .env files |
| Cyclomatic complexity score seems inflated | Analyzer counts all decision points (if, else, for, while, &&, `\ | \ |
| Dependency vulnerability check misses packages | Only a built-in subset of known CVEs is checked (lodash, axios, minimist, jsonwebtoken) | Supplement with npm audit or pip-audit for comprehensive CVE coverage |
| Docker Compose fails after scaffolding | Port 5432 already in use by a local PostgreSQL instance | Stop the local instance or remap the port in docker-compose.yml |
Scaffolded Next.js project fails npm install | Node.js version below 18 or conflicting global packages | Use Node.js 18+ and run npm install in a clean shell without global next conflicts |
---
Success Criteria
- Quality score >= 80/100 (Grade B or higher) on the code quality analyzer for all production codebases
- Zero P0 (critical) security findings before merging to main branch
- Test file ratio >= 70% of source files (estimated coverage target reported by the analyzer)
- Average cyclomatic complexity < 15 across all analyzed files
- No high-complexity files with nesting depth > 4 without documented justification
- Scaffolded projects build and start successfully on first run after
npm install/pip install - Documentation score >= 75/100 (README, LICENSE, and either CONTRIBUTING or API docs present)
---
Tool Reference
project_scaffolder.py
Purpose: Generates complete fullstack project structures with boilerplate code, configuration files, Docker setup, and environment templates for four supported stack templates.
Usage:
python scripts/project_scaffolder.py <template> <project_name> [options]
python scripts/project_scaffolder.py --list-templatesFlags:
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
template | -- | positional | (required) | Template name: nextjs, fastapi-react, mern, or django-react |
project_name | -- | positional | (required) | Name for the new project directory |
--output | -o | string | . (current directory) | Output directory where the project folder is created |
--list-templates | -l | flag | false | List all available templates and exit |
--json | -- | flag | false | Output result in JSON format |
Example:
# Scaffold a FastAPI + React project in a custom directory
python scripts/project_scaffolder.py fastapi-react my-api --output ./projects --jsonOutput Formats:
- Human-readable (default): Prints project name, template used, location on disk, file count, and numbered next steps for getting started.
- JSON (`--json`): Returns a structured object with keys:
success,project_name,template,description,location,files_created,directories_created,next_steps. On failure, returnssuccess: falsewith anerrormessage andavailabletemplates list.
---
code_quality_analyzer.py
Purpose: Performs comprehensive static analysis of fullstack codebases, reporting on security vulnerabilities, cyclomatic complexity, dependency health, test coverage estimation, documentation quality, and an overall quality score with prioritized recommendations.
Usage:
python scripts/code_quality_analyzer.py [project_path] [options]Flags:
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
project_path | -- | positional | . (current directory) | Path to the project directory to analyze |
--verbose | -v | flag | false | Show detailed findings including individual security issue locations |
--json | -- | flag | false | Output full analysis in JSON format |
--output | -o | string | (none) | Write the report to a file (writes JSON regardless of --json flag when used with human-readable mode) |
Example:
# Full verbose analysis with JSON report saved to disk
python scripts/code_quality_analyzer.py /path/to/project --verbose --json --output audit.jsonOutput Formats:
- Human-readable (default): Prints a formatted report with sections for overall score/grade, language breakdown, security issue counts by severity, complexity metrics, dependency status, test coverage estimate, documentation checklist, and up to 10 prioritized recommendations. Use
--verboseto expand individual security findings with file paths and line numbers. - JSON (`--json`): Returns a structured object with keys:
summary,languages,security(categorized by severity),complexity,code_smells,dependencies,tests,documentation,overall_score,grade,recommendations. Each recommendation includespriority(P0/P1/P2),category,issue, andaction.
#!/usr/bin/env python3
"""
Code Quality Analyzer
Analyzes fullstack codebases for quality issues including:
- Code complexity metrics (cyclomatic complexity, cognitive complexity)
- Security vulnerabilities (hardcoded secrets, injection patterns)
- Dependency health (outdated packages, known vulnerabilities)
- Test coverage estimation
- Documentation quality
Usage:
python code_quality_analyzer.py /path/to/project
python code_quality_analyzer.py . --json
python code_quality_analyzer.py /path/to/project --output report.json
"""
import argparse
import json
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# File extensions to analyze
FRONTEND_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"}
BACKEND_EXTENSIONS = {".py", ".go", ".java", ".rb", ".php", ".cs"}
CONFIG_EXTENSIONS = {".json", ".yaml", ".yml", ".toml", ".env"}
ALL_CODE_EXTENSIONS = FRONTEND_EXTENSIONS | BACKEND_EXTENSIONS
# Skip patterns
SKIP_DIRS = {"node_modules", "vendor", ".git", "__pycache__", "dist", "build",
".next", ".venv", "venv", "env", "coverage", ".pytest_cache"}
# Security patterns to detect
SECURITY_PATTERNS = {
"hardcoded_secret": {
"pattern": r"(?:password|secret|api_key|apikey|token|auth)[\s]*[=:][\s]*['\"][^'\"]{8,}['\"]",
"severity": "critical",
"message": "Potential hardcoded secret detected"
},
"sql_injection": {
"pattern": r"(?:execute|query|raw)\s*\(\s*[f'\"].*\{.*\}|%s|%d|\$\d",
"severity": "high",
"message": "Potential SQL injection vulnerability"
},
"xss_vulnerable": {
"pattern": r"innerHTML\s*=|v-html",
"severity": "medium",
"message": "Potential XSS vulnerability - unescaped HTML rendering"
},
"unsafe_react_html": {
"pattern": r"__html",
"severity": "medium",
"message": "React unsafe HTML pattern detected - ensure content is sanitized"
},
"insecure_protocol": {
"pattern": r"http://(?!localhost|127\.0\.0\.1)",
"severity": "medium",
"message": "Insecure HTTP protocol used"
},
"debug_code": {
"pattern": r"console\.log|print\(|debugger|DEBUG\s*=\s*True",
"severity": "low",
"message": "Debug code should be removed in production"
},
"todo_fixme": {
"pattern": r"(?:TODO|FIXME|HACK|XXX):",
"severity": "info",
"message": "Unresolved TODO/FIXME comment"
}
}
# Code smell patterns
CODE_SMELL_PATTERNS = {
"long_function": {
"description": "Function exceeds recommended length",
"threshold": 50
},
"deep_nesting": {
"description": "Excessive nesting depth",
"threshold": 4
},
"large_file": {
"description": "File exceeds recommended size",
"threshold": 500
},
"magic_number": {
"pattern": r"(?<![a-zA-Z_])\b(?:[2-9]\d{2,}|\d{4,})\b(?![a-zA-Z_])",
"description": "Magic number should be named constant"
}
}
# Dependency vulnerability patterns (simplified - in production use a CVE database)
KNOWN_VULNERABLE_DEPS = {
"lodash": {"vulnerable_below": "4.17.21", "cve": "CVE-2021-23337"},
"axios": {"vulnerable_below": "0.21.2", "cve": "CVE-2021-3749"},
"minimist": {"vulnerable_below": "1.2.6", "cve": "CVE-2021-44906"},
"jsonwebtoken": {"vulnerable_below": "9.0.0", "cve": "CVE-2022-23529"},
}
def should_skip(path: Path) -> bool:
"""Check if path should be skipped."""
return any(skip in path.parts for skip in SKIP_DIRS)
def count_lines(filepath: Path) -> Tuple[int, int, int]:
"""Count total lines, code lines, and comment lines."""
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
lines = f.readlines()
except Exception:
return 0, 0, 0
total = len(lines)
code = 0
comments = 0
in_block_comment = False
for line in lines:
stripped = line.strip()
if not stripped:
continue
# Block comments
if "/*" in stripped:
in_block_comment = True
if in_block_comment:
comments += 1
if "*/" in stripped:
in_block_comment = False
continue
# Line comments
if stripped.startswith(("//", "#", "--", "'")):
comments += 1
else:
code += 1
return total, code, comments
def calculate_complexity(content: str, language: str) -> Dict:
"""Calculate cyclomatic complexity estimate."""
# Count decision points
decision_patterns = [
r"\bif\b", r"\belse\b", r"\belif\b", r"\bfor\b", r"\bwhile\b",
r"\bcase\b", r"\bcatch\b", r"\b\?\b", r"\b&&\b", r"\b\|\|\b",
r"\band\b", r"\bor\b"
]
complexity = 1 # Base complexity
for pattern in decision_patterns:
complexity += len(re.findall(pattern, content, re.IGNORECASE))
# Count nesting depth
max_depth = 0
current_depth = 0
for char in content:
if char == "{":
current_depth += 1
max_depth = max(max_depth, current_depth)
elif char == "}":
current_depth = max(0, current_depth - 1)
return {
"cyclomatic": complexity,
"max_nesting": max_depth,
"rating": "low" if complexity < 10 else "medium" if complexity < 20 else "high"
}
def analyze_security(filepath: Path, content: str) -> List[Dict]:
"""Scan for security issues."""
issues = []
lines = content.split("\n")
for pattern_name, pattern_info in SECURITY_PATTERNS.items():
regex = re.compile(pattern_info["pattern"], re.IGNORECASE)
for line_num, line in enumerate(lines, 1):
if regex.search(line):
issues.append({
"file": str(filepath),
"line": line_num,
"type": pattern_name,
"severity": pattern_info["severity"],
"message": pattern_info["message"]
})
return issues
def analyze_dependencies(project_path: Path) -> Dict:
"""Analyze project dependencies for issues."""
findings = {
"package_managers": [],
"total_deps": 0,
"outdated": [],
"vulnerable": [],
"recommendations": []
}
# Check package.json
package_json = project_path / "package.json"
if package_json.exists():
findings["package_managers"].append("npm")
try:
with open(package_json) as f:
pkg = json.load(f)
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
findings["total_deps"] += len(deps)
for dep, version in deps.items():
# Check against known vulnerabilities
if dep in KNOWN_VULNERABLE_DEPS:
vuln = KNOWN_VULNERABLE_DEPS[dep]
# Simplified version check
clean_version = re.sub(r"[^\d.]", "", version)
if clean_version and clean_version < vuln["vulnerable_below"]:
findings["vulnerable"].append({
"package": dep,
"current": version,
"fix_version": vuln["vulnerable_below"],
"cve": vuln["cve"]
})
except Exception:
pass
# Check requirements.txt
requirements = project_path / "requirements.txt"
if requirements.exists():
findings["package_managers"].append("pip")
try:
with open(requirements) as f:
lines = [l.strip() for l in f if l.strip() and not l.startswith("#")]
findings["total_deps"] += len(lines)
except Exception:
pass
# Check go.mod
go_mod = project_path / "go.mod"
if go_mod.exists():
findings["package_managers"].append("go")
return findings
def analyze_test_coverage(project_path: Path) -> Dict:
"""Estimate test coverage based on file analysis."""
test_files = []
source_files = []
for filepath in project_path.rglob("*"):
if should_skip(filepath) or not filepath.is_file():
continue
if filepath.suffix in ALL_CODE_EXTENSIONS:
name = filepath.stem.lower()
if "test" in name or "spec" in name or "_test" in name:
test_files.append(filepath)
elif not name.startswith("_"):
source_files.append(filepath)
source_count = len(source_files)
test_count = len(test_files)
# Estimate coverage ratio
if source_count == 0:
ratio = 0
else:
ratio = min(100, int((test_count / source_count) * 100))
return {
"source_files": source_count,
"test_files": test_count,
"estimated_coverage": ratio,
"rating": "good" if ratio >= 70 else "adequate" if ratio >= 40 else "poor",
"recommendation": None if ratio >= 70 else f"Consider adding more tests ({70 - ratio}% gap to target)"
}
def analyze_documentation(project_path: Path) -> Dict:
"""Analyze documentation quality."""
docs = {
"has_readme": False,
"has_contributing": False,
"has_license": False,
"has_changelog": False,
"api_docs": [],
"score": 0
}
readme_patterns = ["README.md", "README.rst", "README.txt", "readme.md"]
for pattern in readme_patterns:
if (project_path / pattern).exists():
docs["has_readme"] = True
docs["score"] += 30
break
if (project_path / "CONTRIBUTING.md").exists():
docs["has_contributing"] = True
docs["score"] += 15
license_patterns = ["LICENSE", "LICENSE.md", "LICENSE.txt"]
for pattern in license_patterns:
if (project_path / pattern).exists():
docs["has_license"] = True
docs["score"] += 15
break
changelog_patterns = ["CHANGELOG.md", "HISTORY.md", "CHANGES.md"]
for pattern in changelog_patterns:
if (project_path / pattern).exists():
docs["has_changelog"] = True
docs["score"] += 10
break
# Check for API docs
api_doc_dirs = ["docs", "documentation", "api-docs"]
for doc_dir in api_doc_dirs:
doc_path = project_path / doc_dir
if doc_path.is_dir():
docs["api_docs"].append(str(doc_path))
docs["score"] += 30
break
return docs
def analyze_project(project_path: Path) -> Dict:
"""Perform full project analysis."""
results = {
"summary": {
"files_analyzed": 0,
"total_lines": 0,
"code_lines": 0,
"comment_lines": 0
},
"languages": defaultdict(lambda: {"files": 0, "lines": 0}),
"security": {
"critical": [],
"high": [],
"medium": [],
"low": [],
"info": []
},
"complexity": {
"high_complexity_files": [],
"average_complexity": 0
},
"code_smells": [],
"dependencies": {},
"tests": {},
"documentation": {},
"overall_score": 100
}
complexity_scores = []
security_issues = []
# Analyze source files
for filepath in project_path.rglob("*"):
if should_skip(filepath) or not filepath.is_file():
continue
if filepath.suffix not in ALL_CODE_EXTENSIONS:
continue
results["summary"]["files_analyzed"] += 1
# Count lines
total, code, comments = count_lines(filepath)
results["summary"]["total_lines"] += total
results["summary"]["code_lines"] += code
results["summary"]["comment_lines"] += comments
# Track by language
lang = "typescript" if filepath.suffix in {".ts", ".tsx"} else \
"javascript" if filepath.suffix in {".js", ".jsx"} else \
"python" if filepath.suffix == ".py" else \
"go" if filepath.suffix == ".go" else "other"
results["languages"][lang]["files"] += 1
results["languages"][lang]["lines"] += code
# Read file content
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
except Exception:
continue
# Complexity analysis
complexity = calculate_complexity(content, lang)
complexity_scores.append(complexity["cyclomatic"])
if complexity["rating"] == "high":
results["complexity"]["high_complexity_files"].append({
"file": str(filepath.relative_to(project_path)),
"complexity": complexity["cyclomatic"],
"nesting": complexity["max_nesting"]
})
# Security analysis
issues = analyze_security(filepath.relative_to(project_path), content)
security_issues.extend(issues)
# Code smell: large file
if total > CODE_SMELL_PATTERNS["large_file"]["threshold"]:
results["code_smells"].append({
"file": str(filepath.relative_to(project_path)),
"type": "large_file",
"details": f"{total} lines (threshold: {CODE_SMELL_PATTERNS['large_file']['threshold']})"
})
# Categorize security issues
for issue in security_issues:
severity = issue["severity"]
results["security"][severity].append(issue)
# Calculate average complexity
if complexity_scores:
results["complexity"]["average_complexity"] = round(
sum(complexity_scores) / len(complexity_scores), 1
)
# Dependency analysis
results["dependencies"] = analyze_dependencies(project_path)
# Test coverage analysis
results["tests"] = analyze_test_coverage(project_path)
# Documentation analysis
results["documentation"] = analyze_documentation(project_path)
# Calculate overall score
score = 100
# Deduct for security issues
score -= len(results["security"]["critical"]) * 15
score -= len(results["security"]["high"]) * 10
score -= len(results["security"]["medium"]) * 5
score -= len(results["security"]["low"]) * 2
# Deduct for high complexity
score -= len(results["complexity"]["high_complexity_files"]) * 3
# Deduct for code smells
score -= len(results["code_smells"]) * 2
# Deduct for vulnerable dependencies
score -= len(results["dependencies"].get("vulnerable", [])) * 10
# Deduct for poor test coverage
if results["tests"].get("estimated_coverage", 0) < 50:
score -= 15
elif results["tests"].get("estimated_coverage", 0) < 70:
score -= 5
# Deduct for missing documentation
doc_score = results["documentation"].get("score", 0)
if doc_score < 50:
score -= 10
elif doc_score < 75:
score -= 5
results["overall_score"] = max(0, min(100, score))
results["grade"] = (
"A" if score >= 90 else
"B" if score >= 80 else
"C" if score >= 70 else
"D" if score >= 60 else "F"
)
# Generate recommendations
results["recommendations"] = generate_recommendations(results)
# Convert defaultdict to regular dict for JSON serialization
results["languages"] = dict(results["languages"])
return results
def generate_recommendations(analysis: Dict) -> List[Dict]:
"""Generate prioritized recommendations."""
recs = []
# Critical security issues
for issue in analysis["security"]["critical"][:3]:
recs.append({
"priority": "P0",
"category": "security",
"issue": issue["message"],
"file": issue["file"],
"action": f"Remove or secure sensitive data at line {issue['line']}"
})
# Vulnerable dependencies
for vuln in analysis["dependencies"].get("vulnerable", [])[:3]:
recs.append({
"priority": "P0",
"category": "security",
"issue": f"Vulnerable dependency: {vuln['package']} ({vuln['cve']})",
"action": f"Update to version {vuln['fix_version']} or later"
})
# High security issues
for issue in analysis["security"]["high"][:3]:
recs.append({
"priority": "P1",
"category": "security",
"issue": issue["message"],
"file": issue["file"],
"action": "Review and fix security vulnerability"
})
# Test coverage
tests = analysis.get("tests", {})
if tests.get("estimated_coverage", 0) < 50:
recs.append({
"priority": "P1",
"category": "quality",
"issue": f"Low test coverage: {tests.get('estimated_coverage', 0)}%",
"action": "Add unit tests to improve coverage to at least 70%"
})
# High complexity files
for cplx in analysis["complexity"]["high_complexity_files"][:2]:
recs.append({
"priority": "P2",
"category": "maintainability",
"issue": f"High complexity in {cplx['file']}",
"action": "Refactor to reduce cyclomatic complexity"
})
# Documentation
docs = analysis.get("documentation", {})
if not docs.get("has_readme"):
recs.append({
"priority": "P2",
"category": "documentation",
"issue": "Missing README.md",
"action": "Add README with project overview and setup instructions"
})
return recs[:10]
def print_report(analysis: Dict, verbose: bool = False) -> None:
"""Print human-readable report."""
print("=" * 60)
print("CODE QUALITY ANALYSIS REPORT")
print("=" * 60)
print()
# Summary
summary = analysis["summary"]
print(f"Overall Score: {analysis['overall_score']}/100 (Grade: {analysis['grade']})")
print(f"Files Analyzed: {summary['files_analyzed']}")
print(f"Total Lines: {summary['total_lines']:,}")
print(f"Code Lines: {summary['code_lines']:,}")
print(f"Comment Lines: {summary['comment_lines']:,}")
print()
# Languages
print("--- LANGUAGES ---")
for lang, stats in analysis["languages"].items():
print(f" {lang}: {stats['files']} files, {stats['lines']:,} lines")
print()
# Security
sec = analysis["security"]
total_sec = sum(len(sec[s]) for s in ["critical", "high", "medium", "low"])
print("--- SECURITY ---")
print(f" Critical: {len(sec['critical'])}")
print(f" High: {len(sec['high'])}")
print(f" Medium: {len(sec['medium'])}")
print(f" Low: {len(sec['low'])}")
if total_sec > 0 and verbose:
print(" Issues:")
for severity in ["critical", "high", "medium"]:
for issue in sec[severity][:3]:
print(f" [{severity.upper()}] {issue['file']}:{issue['line']} - {issue['message']}")
print()
# Complexity
cplx = analysis["complexity"]
print("--- COMPLEXITY ---")
print(f" Average Complexity: {cplx['average_complexity']}")
print(f" High Complexity Files: {len(cplx['high_complexity_files'])}")
print()
# Dependencies
deps = analysis["dependencies"]
print("--- DEPENDENCIES ---")
print(f" Package Managers: {', '.join(deps.get('package_managers', ['none']))}")
print(f" Total Dependencies: {deps.get('total_deps', 0)}")
print(f" Vulnerable: {len(deps.get('vulnerable', []))}")
print()
# Tests
tests = analysis["tests"]
print("--- TEST COVERAGE ---")
print(f" Source Files: {tests.get('source_files', 0)}")
print(f" Test Files: {tests.get('test_files', 0)}")
print(f" Estimated Coverage: {tests.get('estimated_coverage', 0)}% ({tests.get('rating', 'unknown')})")
print()
# Documentation
docs = analysis["documentation"]
print("--- DOCUMENTATION ---")
print(f" README: {'Yes' if docs.get('has_readme') else 'No'}")
print(f" LICENSE: {'Yes' if docs.get('has_license') else 'No'}")
print(f" CONTRIBUTING: {'Yes' if docs.get('has_contributing') else 'No'}")
print(f" CHANGELOG: {'Yes' if docs.get('has_changelog') else 'No'}")
print(f" Score: {docs.get('score', 0)}/100")
print()
# Recommendations
if analysis["recommendations"]:
print("--- RECOMMENDATIONS ---")
for i, rec in enumerate(analysis["recommendations"][:10], 1):
print(f"\n{i}. [{rec['priority']}] {rec['category'].upper()}")
print(f" Issue: {rec['issue']}")
print(f" Action: {rec['action']}")
print()
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description="Analyze fullstack codebase for quality issues",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s /path/to/project
%(prog)s . --verbose
%(prog)s /path/to/project --json --output report.json
"""
)
parser.add_argument(
"project_path",
nargs="?",
default=".",
help="Path to project directory (default: current directory)"
)
parser.add_argument(
"--json",
action="store_true",
help="Output in JSON format"
)
parser.add_argument(
"--output", "-o",
help="Write output to file"
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Show detailed findings"
)
args = parser.parse_args()
project_path = Path(args.project_path).resolve()
if not project_path.exists():
print(f"Error: Path does not exist: {project_path}", file=sys.stderr)
sys.exit(1)
analysis = analyze_project(project_path)
if args.json:
output = json.dumps(analysis, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Report written to {args.output}")
else:
print(output)
else:
print_report(analysis, args.verbose)
if args.output:
with open(args.output, "w") as f:
json.dump(analysis, f, indent=2)
print(f"\nDetailed JSON report written to {args.output}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Fullstack Project Scaffolder
Generates project structure and boilerplate for various fullstack architectures.
Supports Next.js, FastAPI+React, MERN, Django+React, and more.
Usage:
python project_scaffolder.py nextjs my-app
python project_scaffolder.py fastapi-react my-api --with-docker
python project_scaffolder.py mern my-project --with-auth
python project_scaffolder.py --list-templates
"""
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional
# Project templates with file structures
TEMPLATES = {
"nextjs": {
"name": "Next.js Full Stack",
"description": "Next.js 14+ with App Router, TypeScript, Tailwind CSS",
"structure": {
"src/app": ["layout.tsx", "page.tsx", "globals.css", "api/health/route.ts"],
"src/components/ui": ["Button.tsx", "Card.tsx", "Input.tsx"],
"src/components/layout": ["Header.tsx", "Footer.tsx"],
"src/lib": ["utils.ts", "db.ts"],
"src/types": ["index.ts"],
"public": [],
"": ["package.json", "tsconfig.json", "tailwind.config.ts", "next.config.js",
".env.example", ".gitignore", "README.md"]
}
},
"fastapi-react": {
"name": "FastAPI + React",
"description": "FastAPI backend with React frontend, PostgreSQL",
"structure": {
"backend/app": ["__init__.py", "main.py", "config.py", "database.py"],
"backend/app/api": ["__init__.py", "routes.py", "deps.py"],
"backend/app/models": ["__init__.py", "user.py"],
"backend/app/schemas": ["__init__.py", "user.py"],
"backend": ["requirements.txt", "alembic.ini", "Dockerfile"],
"frontend/src": ["App.tsx", "main.tsx", "index.css"],
"frontend/src/components": ["Layout.tsx"],
"frontend/src/hooks": ["useApi.ts"],
"frontend": ["package.json", "tsconfig.json", "vite.config.ts", "Dockerfile"],
"": ["docker-compose.yml", ".env.example", ".gitignore", "README.md"]
}
},
"mern": {
"name": "MERN Stack",
"description": "MongoDB, Express, React, Node.js with TypeScript",
"structure": {
"server/src": ["index.ts", "config.ts", "database.ts"],
"server/src/routes": ["index.ts", "users.ts"],
"server/src/models": ["User.ts"],
"server/src/middleware": ["auth.ts", "error.ts"],
"server": ["package.json", "tsconfig.json", "Dockerfile"],
"client/src": ["App.tsx", "main.tsx"],
"client/src/components": ["Layout.tsx"],
"client/src/services": ["api.ts"],
"client": ["package.json", "tsconfig.json", "vite.config.ts", "Dockerfile"],
"": ["docker-compose.yml", ".env.example", ".gitignore", "README.md"]
}
},
"django-react": {
"name": "Django + React",
"description": "Django REST Framework backend with React frontend",
"structure": {
"backend/config": ["__init__.py", "settings.py", "urls.py", "wsgi.py"],
"backend/apps/users": ["__init__.py", "models.py", "serializers.py", "views.py", "urls.py"],
"backend": ["manage.py", "requirements.txt", "Dockerfile"],
"frontend/src": ["App.tsx", "main.tsx"],
"frontend/src/components": ["Layout.tsx"],
"frontend": ["package.json", "tsconfig.json", "vite.config.ts", "Dockerfile"],
"": ["docker-compose.yml", ".env.example", ".gitignore", "README.md"]
}
}
}
def get_file_content(template: str, filepath: str, project_name: str) -> str:
"""Generate file content based on template and file type."""
filename = Path(filepath).name
contents = {
# Next.js files
"layout.tsx": f'''import type {{ Metadata }} from "next";
import "./globals.css";
export const metadata: Metadata = {{
title: "{project_name}",
description: "Generated by project scaffolder",
}};
export default function RootLayout({{
children,
}}: {{
children: React.ReactNode;
}}) {{
return (
<html lang="en">
<body>{{children}}</body>
</html>
);
}}
''',
"page.tsx": f'''export default function Home() {{
return (
<main className="min-h-screen p-8">
<h1 className="text-4xl font-bold">{project_name}</h1>
<p className="mt-4 text-gray-600">Welcome to your new project.</p>
</main>
);
}}
''',
"globals.css": '''@tailwind base;
@tailwind components;
@tailwind utilities;
''',
"route.ts": '''import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({
status: "healthy",
timestamp: new Date().toISOString(),
});
}
''',
"Button.tsx": '''import { ButtonHTMLAttributes, forwardRef } from "react";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "outline";
size?: "sm" | "md" | "lg";
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className = "", variant = "primary", size = "md", ...props }, ref) => {
const base = "font-medium rounded-lg transition-colors";
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
outline: "border border-gray-300 hover:bg-gray-50",
};
const sizes = { sm: "px-3 py-1.5 text-sm", md: "px-4 py-2", lg: "px-6 py-3 text-lg" };
return <button ref={ref} className={`${base} ${variants[variant]} ${sizes[size]} ${className}`} {...props} />;
}
);
Button.displayName = "Button";
''',
"Card.tsx": '''interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
export function Card({ className = "", children, ...props }: CardProps) {
return (
<div className={`rounded-lg border bg-white p-6 shadow-sm ${className}`} {...props}>
{children}
</div>
);
}
''',
"Input.tsx": '''import { InputHTMLAttributes, forwardRef } from "react";
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, className = "", ...props }, ref) => (
<div className="space-y-1">
{label && <label className="text-sm font-medium text-gray-700">{label}</label>}
<input
ref={ref}
className={`w-full rounded-md border px-3 py-2 ${error ? "border-red-500" : "border-gray-300"} ${className}`}
{...props}
/>
{error && <p className="text-sm text-red-500">{error}</p>}
</div>
)
);
Input.displayName = "Input";
''',
"Header.tsx": f'''import Link from "next/link";
export function Header() {{
return (
<header className="border-b">
<nav className="mx-auto flex max-w-7xl items-center justify-between p-4">
<Link href="/" className="text-xl font-bold">{project_name}</Link>
<div className="flex gap-4">
<Link href="/about" className="hover:text-blue-600">About</Link>
</div>
</nav>
</header>
);
}}
''',
"Footer.tsx": '''export function Footer() {
return (
<footer className="border-t py-8 text-center text-sm text-gray-500">
<p>© {new Date().getFullYear()} All rights reserved.</p>
</footer>
);
}
''',
"utils.ts": '''export function cn(...classes: (string | undefined | false)[]): string {
return classes.filter(Boolean).join(" ");
}
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat("en-US", {
year: "numeric", month: "long", day: "numeric",
}).format(date);
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
''',
"db.ts": '''// Database connection (Prisma example)
// import { PrismaClient } from "@prisma/client";
// export const db = new PrismaClient();
export const db = {
// Placeholder - configure your database
};
''',
"index.ts": '''export interface User {
id: string;
email: string;
name: string | null;
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
error: string | null;
success: boolean;
}
''',
# FastAPI files
"main.py": f'''from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.routes import router
from app.config import settings
app = FastAPI(title="{project_name}", openapi_url="/api/openapi.json")
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(router, prefix="/api")
@app.get("/health")
async def health_check():
return {{"status": "healthy"}}
''',
"config.py": '''from pydantic_settings import BaseSettings
from typing import List
class Settings(BaseSettings):
DATABASE_URL: str = "postgresql://user:pass@localhost:5432/db"
ALLOWED_ORIGINS: List[str] = ["http://localhost:3000", "http://localhost:5173"]
SECRET_KEY: str = "change-me-in-production"
class Config:
env_file = ".env"
settings = Settings()
''',
"database.py": '''from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.config import settings
engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
''',
"routes.py": '''from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
router = APIRouter()
@router.get("/users")
async def get_users(db: Session = Depends(get_db)):
return {"users": []}
@router.post("/users")
async def create_user(db: Session = Depends(get_db)):
return {"message": "User created"}
''',
"deps.py": '''from typing import Generator
from fastapi import Depends
from sqlalchemy.orm import Session
from app.database import get_db
def get_current_user():
# Implement authentication
pass
''',
"user.py": '''from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from app.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, nullable=False)
name = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
''',
"requirements.txt": '''fastapi>=0.104.0
uvicorn[standard]>=0.24.0
sqlalchemy>=2.0.0
pydantic>=2.0.0
pydantic-settings>=2.0.0
alembic>=1.12.0
psycopg2-binary>=2.9.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.0
''',
"alembic.ini": '''[alembic]
script_location = alembic
sqlalchemy.url = driver://user:pass@localhost/dbname
''',
# Express files
"index.ts": '''import express from "express";
import cors from "cors";
import helmet from "helmet";
import routes from "./routes";
const app = express();
const PORT = process.env.PORT || 8000;
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use("/api", routes);
app.get("/health", (_, res) => res.json({ status: "healthy" }));
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
''',
"config.ts": '''export const config = {
PORT: parseInt(process.env.PORT || "8000"),
MONGODB_URI: process.env.MONGODB_URI || "mongodb://localhost:27017/app",
JWT_SECRET: process.env.JWT_SECRET || "change-me",
};
''',
"database.ts": '''import mongoose from "mongoose";
import { config } from "./config";
export async function connectDatabase(): Promise<void> {
await mongoose.connect(config.MONGODB_URI);
console.log("Connected to MongoDB");
}
''',
"users.ts": '''import { Router } from "express";
const router = Router();
router.get("/", async (req, res) => {
res.json({ users: [] });
});
router.post("/", async (req, res) => {
res.status(201).json({ message: "User created" });
});
export default router;
''',
"User.ts": '''import mongoose, { Schema, Document } from "mongoose";
export interface IUser extends Document {
email: string;
name?: string;
createdAt: Date;
}
const userSchema = new Schema<IUser>({
email: { type: String, required: true, unique: true },
name: String,
}, { timestamps: true });
export const User = mongoose.model<IUser>("User", userSchema);
''',
"auth.ts": '''import { Request, Response, NextFunction } from "express";
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Authentication required" });
// Verify token
next();
}
''',
"error.ts": '''import { Request, Response, NextFunction } from "express";
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
console.error(err.stack);
res.status(500).json({ error: "Internal server error" });
}
''',
# React/Vite files
"App.tsx": f'''function App() {{
return (
<div className="min-h-screen bg-gray-50">
<main className="container mx-auto p-4">
<h1 className="text-3xl font-bold">{project_name}</h1>
<p className="mt-4 text-gray-600">Welcome to your new project.</p>
</main>
</div>
);
}}
export default App;
''',
"main.tsx": '''import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode><App /></React.StrictMode>
);
''',
"index.css": '''@tailwind base;
@tailwind components;
@tailwind utilities;
''',
"Layout.tsx": f'''import {{ ReactNode }} from "react";
export function Layout({{ children }}: {{ children: ReactNode }}) {{
return (
<div className="min-h-screen">
<header className="border-b p-4">
<a href="/" className="text-xl font-bold">{project_name}</a>
</header>
<main>{{children}}</main>
</div>
);
}}
''',
"useApi.ts": '''import { useState, useCallback } from "react";
export function useApi<T>(baseUrl = "/api") {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const request = useCallback(async (endpoint: string, options?: RequestInit) => {
setLoading(true);
setError(null);
try {
const res = await fetch(`${baseUrl}${endpoint}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
setData(json);
return json;
} catch (e) {
setError(e instanceof Error ? e.message : "Unknown error");
throw e;
} finally {
setLoading(false);
}
}, [baseUrl]);
return { data, loading, error, request };
}
''',
"api.ts": '''const API_BASE = import.meta.env.VITE_API_URL || "/api";
async function request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(`${API_BASE}${endpoint}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
export const api = {
get: <T>(endpoint: string) => request<T>(endpoint),
post: <T>(endpoint: string, body: unknown) => request<T>(endpoint, { method: "POST", body: JSON.stringify(body) }),
put: <T>(endpoint: string, body: unknown) => request<T>(endpoint, { method: "PUT", body: JSON.stringify(body) }),
delete: <T>(endpoint: string) => request<T>(endpoint, { method: "DELETE" }),
};
''',
"vite.config.ts": '''import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: { "/api": { target: "http://localhost:8000", changeOrigin: true } },
},
});
''',
# Django files
"settings.py": f'''from pathlib import Path
import os
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me")
DEBUG = os.environ.get("DEBUG", "True") == "True"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"corsheaders",
"apps.users",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
DATABASES = {{
"default": {{
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("DB_NAME", "app"),
"USER": os.environ.get("DB_USER", "user"),
"PASSWORD": os.environ.get("DB_PASSWORD", "password"),
"HOST": os.environ.get("DB_HOST", "localhost"),
"PORT": "5432",
}}
}}
CORS_ALLOWED_ORIGINS = ["http://localhost:5173"]
''',
"urls.py": '''from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("apps.users.urls")),
]
''',
"wsgi.py": '''import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
''',
"models.py": '''from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
''',
"serializers.py": '''from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "username", "date_joined"]
''',
"views.py": '''from rest_framework import viewsets
from .models import User
from .serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
''',
"manage.py": '''#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
''',
# Config files
"package.json": f'''{{"name": "{project_name}", "version": "0.1.0", "private": true,
"scripts": {{"dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint"}},
"dependencies": {{"next": "^14.0.0", "react": "^18.2.0", "react-dom": "^18.2.0"}},
"devDependencies": {{"@types/node": "^20.0.0", "@types/react": "^18.2.0", "typescript": "^5.0.0", "tailwindcss": "^3.3.0", "autoprefixer": "^10.4.0", "postcss": "^8.4.0"}}
}}''',
"tsconfig.json": '''{"compilerOptions": {"target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "baseUrl": ".", "paths": {"@/*": ["./src/*"]}}, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules"]}''',
"tailwind.config.ts": '''import type { Config } from "tailwindcss";
const config: Config = { content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], theme: { extend: {} }, plugins: [] };
export default config;
''',
"next.config.js": '''/** @type {import('next').NextConfig} */
module.exports = { reactStrictMode: true };
''',
".env.example": '''DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
SECRET_KEY="your-secret-here"
''',
".gitignore": '''node_modules/
.next/
dist/
build/
.env
.env.local
__pycache__/
*.pyc
.venv/
.DS_Store
''',
"docker-compose.yml": f'''version: "3.8"
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: {project_name}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
''',
"Dockerfile": '''FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
''',
"README.md": f'''# {project_name}
Generated by Fullstack Project Scaffolder.
## Getting Started
```bash
npm install
npm run dev
```
Open http://localhost:3000.
''',
"__init__.py": "",
}
# Handle special cases
if "routes" in filepath and filename == "index.ts":
return '''import { Router } from "express";
import usersRouter from "./users";
const router = Router();
router.use("/users", usersRouter);
export default router;
'''
if "schemas" in filepath and filename == "user.py":
return '''from pydantic import BaseModel, EmailStr
from datetime import datetime
from typing import Optional
class UserBase(BaseModel):
email: EmailStr
name: Optional[str] = None
class UserCreate(UserBase):
password: str
class UserResponse(UserBase):
id: int
created_at: datetime
class Config:
from_attributes = True
'''
if "users" in filepath and filename == "urls.py":
return '''from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import UserViewSet
router = DefaultRouter()
router.register("users", UserViewSet)
urlpatterns = [path("", include(router.urls))]
'''
return contents.get(filename, f"// {filename}\n")
def create_project(template_name: str, project_name: str, output_dir: Path) -> Dict:
"""Create project from template."""
if template_name not in TEMPLATES:
return {
"success": False,
"error": f"Unknown template: {template_name}",
"available": list(TEMPLATES.keys())
}
template = TEMPLATES[template_name]
project_dir = output_dir / project_name
if project_dir.exists():
return {"success": False, "error": f"Directory exists: {project_dir}"}
created_files = []
created_dirs = []
for dir_path, files in template["structure"].items():
if dir_path:
full_dir = project_dir / dir_path
else:
full_dir = project_dir
full_dir.mkdir(parents=True, exist_ok=True)
created_dirs.append(str(full_dir))
for filename in files:
filepath = full_dir / filename
filepath.parent.mkdir(parents=True, exist_ok=True)
content = get_file_content(template_name, str(dir_path / filename), project_name)
filepath.write_text(content)
created_files.append(str(filepath))
return {
"success": True,
"project_name": project_name,
"template": template_name,
"description": template["description"],
"location": str(project_dir),
"files_created": len(created_files),
"directories_created": len(created_dirs),
"next_steps": get_next_steps(template_name, project_name)
}
def get_next_steps(template: str, name: str) -> List[str]:
"""Get setup instructions for template."""
steps = {
"nextjs": [f"cd {name}", "npm install", "cp .env.example .env.local", "npm run dev"],
"fastapi-react": [
f"cd {name}",
"docker-compose up -d db",
"cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload",
"cd frontend && npm install && npm run dev"
],
"mern": [
f"cd {name}",
"docker-compose up -d mongo",
"cd server && npm install && npm run dev",
"cd client && npm install && npm run dev"
],
"django-react": [
f"cd {name}",
"docker-compose up -d db",
"cd backend && pip install -r requirements.txt && python manage.py migrate && python manage.py runserver",
"cd frontend && npm install && npm run dev"
]
}
return steps.get(template, [f"cd {name}"])
def list_templates() -> Dict:
"""List available templates."""
return {
"templates": [
{"name": k, "display_name": v["name"], "description": v["description"]}
for k, v in TEMPLATES.items()
]
}
def print_result(result: Dict, as_json: bool = False) -> None:
"""Print result."""
if as_json:
print(json.dumps(result, indent=2))
return
if "templates" in result:
print("\nAvailable Templates:")
print("=" * 50)
for t in result["templates"]:
print(f"\n {t['name']}")
print(f" {t['description']}")
return
if not result.get("success"):
print(f"Error: {result.get('error')}")
return
print("\n" + "=" * 50)
print("Project Created Successfully")
print("=" * 50)
print(f"Project: {result['project_name']}")
print(f"Template: {result['template']}")
print(f"Location: {result['location']}")
print(f"Files: {result['files_created']}")
print("\nNext Steps:")
for i, step in enumerate(result["next_steps"], 1):
print(f" {i}. {step}")
print()
def main():
parser = argparse.ArgumentParser(
description="Generate fullstack project scaffolding",
epilog="Examples:\n %(prog)s nextjs my-app\n %(prog)s fastapi-react my-api\n %(prog)s --list-templates",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("template", nargs="?", help="Project template")
parser.add_argument("project_name", nargs="?", help="Project name")
parser.add_argument("--output", "-o", default=".", help="Output directory")
parser.add_argument("--list-templates", "-l", action="store_true", help="List templates")
parser.add_argument("--json", action="store_true", help="JSON output")
args = parser.parse_args()
if args.list_templates:
print_result(list_templates(), args.json)
return
if not args.template or not args.project_name:
parser.print_help()
sys.exit(1)
result = create_project(args.template, args.project_name, Path(args.output).resolve())
print_result(result, args.json)
if not result.get("success"):
sys.exit(1)
if __name__ == "__main__":
main()