diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..939f20614 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,257 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What is Open SaaS? + +**Open SaaS** is a free, open-source SaaS starter template built on top of the Wasp framework. It provides a production-ready foundation for building SaaS applications with essential features already implemented: + +- **Authentication** - Email verification + OAuth (Google, GitHub, Discord) +- **Payments** - Stripe and Lemon Squeezy integration with subscription management +- **Admin Dashboard** - User management and analytics +- **Email** - SendGrid, Mailgun, or SMTP support +- **File Uploads** - AWS S3 integration +- **Analytics** - Plausible or Google Analytics +- **AI Ready** - Example OpenAI integration with function calling +- **Documentation** - Astro Starlight blog and docs site +- **Testing** - Playwright E2E tests +- **Deployment** - One-command deploy to Railway or Fly.io + +Open SaaS follows best practices and conventions specific to building SaaS applications on Wasp. The template is designed to be customized and extended for your specific product needs. + +## Repository Structure + +This is the **Open SaaS** monorepo, a full-stack SaaS template built with Wasp, React, Node.js, and Prisma. + +The repository is organized into three main directories: + +- **`template/`** - The core Open SaaS template distributed via `wasp new -t saas` + - `template/app/` - The main Wasp application + - `template/blog/` - Astro-based documentation and blog + - `template/e2e-tests/` - Playwright end-to-end tests + +- **`opensaas-sh/`** - Production deployment at https://opensaas.sh (built with Open SaaS itself) + - `opensaas-sh/app/` - Generated via `dope.sh` from template + diffs + - `opensaas-sh/app_diff/` - Versioned diffs applied to template + - `opensaas-sh/blog/` - Production docs and landing page + +- **`tools/`** - Development tooling including `dope.sh` for managing derived projects + +### Derived Project Management + +The `opensaas-sh` app is not directly versioned. Instead, only the **diffs** from the template are stored in `opensaas-sh/app_diff/`. Use `tools/dope.sh` to manage this: + +```bash +# Regenerate opensaas-sh/app/ from template + diffs (after template changes): +./tools/dope.sh template/app opensaas-sh/app patch + +# Update diffs after modifying opensaas-sh/app/: +./tools/dope.sh template/app opensaas-sh/app diff +``` + +## Working with the Wasp App + +### Project Structure + +Wasp uses a declarative configuration file (`main.wasp`) to generate a full-stack application: + +- **`main.wasp`** - App configuration defining routes, pages, auth, operations, and more +- **`schema.prisma`** - Prisma database schema (defines entities) +- **`src/`** - Custom application code organized by feature: + - `src/auth/` - Authentication logic and pages + - `src/client/` - Shared client components and layout + - `src/payment/` - Stripe/Lemon Squeezy integration + - `src/user/` - User profile and settings + - `src/demo-ai-app/` - Example OpenAI integration + - `src/file-upload/` - AWS S3 file upload example + - `src/landing-page/` - Marketing landing page components + - `src/admin/` - Admin dashboard + - `src/server/` - Server utilities and scripts + - `src/shared/` - Code shared between client and server + +### Essential Commands + +```bash +# Development +wasp start # Start dev server (runs DB, server, and client) +wasp start db # Start only the managed development database +wasp db migrate-dev # Create and apply database migrations +wasp db studio # Open Prisma Studio to inspect database +wasp db seed # Run seed functions defined in main.wasp + +# Testing & Quality +wasp test # Run tests +npm run prettier:check # Check code formatting (root level) +npm run prettier:format # Format code (root level) + +# Production +wasp build # Generate production build +wasp deploy # Deploy to Railway or Fly.io + +# Maintenance +wasp clean # Delete generated code and caches (fixes most issues) +``` + +### Import Conventions (CRITICAL) + +**In TypeScript/TSX files (`.ts`/`.tsx`):** +- ✅ Wasp imports: `import { User } from 'wasp/entities'` +- ✅ Wasp operations: `import { useQuery } from 'wasp/client/operations'` +- ✅ Wasp types: `import type { GetTasks } from 'wasp/server/operations'` +- ✅ Prisma enum values: `import { SubscriptionStatus } from '@prisma/client'` +- ✅ Local code: Use relative paths like `import { Component } from './Component'` +- ❌ NEVER use `@wasp/...` prefix +- ❌ NEVER use `@src/...` in TypeScript files + +**In main.wasp file:** +- ✅ Your code: `fn: import { getTasks } from "@src/tasks/operations.ts"` +- ❌ NEVER use relative paths like `"../src/..."` + +### Wasp Operations Pattern + +Wasp operations (queries and actions) are the primary way to communicate between client and server. + +**Define in main.wasp:** +```wasp +query getTasks { + fn: import { getTasks } from "@src/tasks/operations.ts", + entities: [Task] // Grants access to Task entity +} + +action createTask { + fn: import { createTask } from "@src/tasks/operations.ts", + entities: [Task] +} +``` + +**Implement in `src/{feature}/operations.ts`:** +```typescript +import { HttpError } from 'wasp/server' +import type { GetTasks, CreateTask } from 'wasp/server/operations' +import type { Task } from 'wasp/entities' + +export const getTasks: GetTasks = async (_args, context) => { + if (!context.user) throw new HttpError(401) + return context.entities.Task.findMany({ where: { userId: context.user.id } }) +} + +export const createTask: CreateTask<{ description: string }, Task> = async (args, context) => { + if (!context.user) throw new HttpError(401) + return context.entities.Task.create({ + data: { description: args.description, userId: context.user.id } + }) +} +``` + +**Use on client:** +```typescript +// Queries: use useQuery hook +import { getTasks, useQuery } from 'wasp/client/operations' +const { data: tasks, isLoading } = useQuery(getTasks) + +// Actions: call directly with async/await (DO NOT use useAction unless optimistic updates needed) +import { createTask } from 'wasp/client/operations' +await createTask({ description: 'New task' }) +``` + +### Database Workflow + +1. Modify `schema.prisma` to add/change models +2. Run `wasp db migrate-dev` to generate and apply migrations +3. Wasp automatically generates TypeScript types in `wasp/entities` +4. Reference entities in operations via `context.entities.ModelName` + +### AI-Assisted Development + +The template includes comprehensive Cursor rules in `template/app/.cursor/rules/`: +- `wasp-overview.mdc` - Wasp framework fundamentals +- `project-conventions.mdc` - Import rules and common patterns +- `database-operations.mdc` - Database and operations patterns +- `authentication.mdc` - Auth setup and customization +- `ui-components.mdc` - ShadCN UI component usage +- `deployment.mdc` - Production deployment guides +- `advanced-troubleshooting.mdc` - Custom API endpoints, background jobs + +## Tech Stack + +- **Framework:** Wasp 0.19+ (React + Node.js + Prisma) +- **Frontend:** React, TypeScript, TailwindCSS, ShadCN UI +- **Backend:** Node.js, Express +- **Database:** PostgreSQL (production) / SQLite (local dev) +- **Auth:** Email + OAuth (Google, GitHub, Discord) +- **Payments:** Stripe or Lemon Squeezy +- **Email:** SendGrid, Mailgun, or SMTP +- **Storage:** AWS S3 +- **Testing:** Playwright (E2E) +- **Docs:** Astro Starlight + +## Common Development Patterns + +### Feature Organization +Group related code by feature in `src/{featureName}/`: +- `operations.ts` - Wasp queries and actions +- `{FeatureName}Page.tsx` - Page components +- Components, utilities, and types as needed + +Group feature config in `main.wasp` using regions: +```wasp +// #region Tasks Feature +route TasksRoute { path: "/tasks", to: TasksPage } +page TasksPage { component: import { TasksPage } from "@src/tasks/TasksPage" } +query getTasks { ... } +action createTask { ... } +// #endregion +``` + +### Error Handling +Use `HttpError` from `wasp/server` for structured errors: +```typescript +import { HttpError } from 'wasp/server' +throw new HttpError(404, 'Resource not found') +``` + +### Testing +E2E tests live in `template/e2e-tests/` and use Playwright: +```bash +cd template/e2e-tests +npm install +npm run test +``` + +## Contributing + +1. Work in the `template/` directory (changes auto-flow to derived projects) +2. Update `template/e2e-tests/` if adding/changing features +3. Update `opensaas-sh/blog/src/content/docs/` if changing documented behavior +4. Run `npm run prettier:format` before committing +5. Test changes work with `wasp clean && wasp start` + +## Troubleshooting + +**"Cannot find module 'wasp/...'"** +- Check import prefix is `wasp/`, not `@wasp/` +- Restart Wasp dev server: `wasp clean && wasp start` + +**"Cannot find module '@src/...'" in TypeScript** +- Use relative imports in `.ts`/`.tsx` files +- Only use `@src/...` in `main.wasp` file + +**Database changes not applied** +- Run `wasp db migrate-dev` after changing `schema.prisma` +- Check PostgreSQL is running if using `postgresql` provider + +**Generated code out of sync** +- Run `wasp clean` to delete all generated code and caches +- Restart with `wasp start` + +**macOS dope.sh errors** +- Install: `brew install coreutils gpatch diffutils` + +## LLM-optimized Documentation + +If needed, ground yourself using the Wasp & Open SaaS documentation: +- https://wasp.sh/llms.txt +- https://docs.opensaas.sh/llms.txt + +## MCP Documentation Lookup +- For specific lookups: Use `mcp__wasp-docs__find_docs` to search Wasp/OpenSaaS docs diff --git a/template/.claude/commands/deploy.md b/template/.claude/commands/deploy.md new file mode 100644 index 000000000..64033febd --- /dev/null +++ b/template/.claude/commands/deploy.md @@ -0,0 +1,21 @@ +--- +description: Run pre-deployment checks then deploy to Railway or Fly.io. +--- + +# Deploy Open SaaS App + +This command guides you through validating your app configuration and deploying to production. + +## Process + +1. **Pre-Deployment Validation** - Run the [pre-deployment skill](../skills/pre-deployment/SKILL.md) to check: + - Wasp config metadata (app name, OG tags, analytics) + - Environment variables + - Database migrations + - Optional: production build test + +2. **Deployment** - If validation passes, run the [deploying-app skill](../skills/deploying-app/SKILL.md) to deploy to Railway or Fly.io. + +Begin with: "I'll help you deploy your Open SaaS app. First, let me run some pre-deployment checks to catch any issues." + +Then execute the pre-deployment skill. After it completes successfully, ask the user if they want to proceed with deployment and execute the deploying-app skill. diff --git a/template/.claude/commands/open-saas-setup-wizard.md b/template/.claude/commands/open-saas-setup-wizard.md new file mode 100644 index 000000000..bc0f3adbf --- /dev/null +++ b/template/.claude/commands/open-saas-setup-wizard.md @@ -0,0 +1,9 @@ +--- +description: Initialize a guided setup for configuring a new Open SaaS project. +--- + +# Open SaaS Setup Wizard + +Run the [setup-wizard skill](../skills/setup-wizard/SKILL.md) to guide the user through configuring their Open SaaS project. + +Begin with: "Welcome to Open SaaS setup! I'll guide you through configuring your project. Let's start with your app branding." diff --git a/template/.claude/commands/review-ai-slop.md b/template/.claude/commands/review-ai-slop.md new file mode 100644 index 000000000..02811cb96 --- /dev/null +++ b/template/.claude/commands/review-ai-slop.md @@ -0,0 +1,15 @@ +--- +description: Review current code changes for poorly generated code. +--- + +# Review AI Slop + +Check the diff against main, and remove all AI generated slop introduced in this branch. + +This includes: +- Extra comments that a human wouldn't add or is inconsistent with the rest of the file +- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) +- Casts to any (or as) to get around type issues +- Any other style that is inconsistent with the file + +Report at the end with only a 1-3 sentence summary of what you changed. \ No newline at end of file diff --git a/template/.claude/skills/adding-feature/SKILL.md b/template/.claude/skills/adding-feature/SKILL.md new file mode 100644 index 000000000..400b78354 --- /dev/null +++ b/template/.claude/skills/adding-feature/SKILL.md @@ -0,0 +1,62 @@ +--- +name: adding-feature +description: plan and scaffold a new feature for the Open SaaS app following Wasp conventions. +--- + +# adding-feature + +## Planning Requirement + +**IMPORTANT:** Before implementing any feature, use the EnterPlanMode tool to: +1. Explore existing code patterns in the codebase +2. Design the implementation approach +3. Write a plan file for user approval and/or add tasks to the todo list with TodoWrite tool. + +Only proceed with implementation after the user approves the plan. + +## Feature Planning + +### Requirements Gathering + +When gathering requirements (either before or during plan mode), ask about: +1. ask the user what feature they want to add +2. gather requirements: + - **Access:** public, authenticated, or admin-only? + - **Subscription tier:** available to all users or gated by plan (hobby/pro)? + - **Usage limits:** should it consume credits or have rate limits? + - **Data model:** what entities are needed? relate to User? + - **Operations:** what can users do? (CRUD, custom actions) + - **Admin visibility:** should admins see this data in the dashboard? + - **Background work:** needs scheduled jobs or async processing? + - **External APIs:** any third-party integrations? +3. determine feature name in kebab-case (e.g., `todo-list`, `user-notes`) + +## Implementation Steps + +Prompt the user if they'd like to follow the [Kaizen approach](./kaizen-approach.md) when implementing a feature, especially for complex ones. + +Here is an example process for implementing a feature: + +1. **[Database Setup](./database.md)** - if feature needs entities +2. **[Operations](./operations.md)** - backend queries and actions +3. **Wasp Configuration** - define feature in wasp config file +4. **[Pages](./pages.md)** - frontend components +5. **[Background Jobs](./background-jobs.md)** - scheduled or async background jobs + +## Verification + +1. ensure Wasp dev server is running (`wasp start`) +2. if type errors occur, restart the Wasp dev server +3. test all operations work correctly +4. verify authentication works as expected + +## Troubleshooting + +see [troubleshooting](./troubleshooting.md) + +## Documentation + +Fetch guide URLs directly: +- https://wasp.sh/llms.txt + +If you need more specific info, use mcp__wasp-docs__find_docs to search. diff --git a/template/.claude/skills/adding-feature/background-jobs.md b/template/.claude/skills/adding-feature/background-jobs.md new file mode 100644 index 000000000..e13410bcd --- /dev/null +++ b/template/.claude/skills/adding-feature/background-jobs.md @@ -0,0 +1,66 @@ +# Background Jobs + +Complete job guide URLs are available in the [Wasp LLM-optimized documentation](https://wasp.sh/llms.txt). + +## Existing Example + +Reference the `dailyStatsJob` in this template: +- Declaration: [`../../../app/main.wasp`](../../../app/main.wasp) (search for `job dailyStatsJob`) +- Implementation: [`../../../app/src/analytics/stats.ts`](../../../app/src/analytics/stats.ts) + +## Adding a Job + +### 1. Declare in main.wasp + +```wasp +job myJob { + executor: PgBoss, + perform: { + fn: import { myJobFn } from "@src/jobs/myJob" + }, + schedule: { + cron: "0 * * * *" // hourly + }, + entities: [EntityName] // entities accessed in job +} +``` + +### 2. Implement the Job + +Create `src/jobs/myJob.ts`: + +```typescript +import type { MyJob } from 'wasp/server/jobs' + +export const myJobFn: MyJob = async (_args, context) => { + // Access entities via context.entities.EntityName + // Use try/catch and log errors +} +``` + +## Common Cron Patterns + +| Schedule | Cron | +|----------|------| +| Every minute | `* * * * *` | +| Every hour | `0 * * * *` | +| Daily at midnight | `0 0 * * *` | +| Weekly (Sunday) | `0 0 * * 0` | + +## Manual Invocation + +Jobs can be triggered manually from operations: + +```typescript +import { myJob } from 'wasp/server/jobs' + +await myJob.submit({}) // or with args +``` + +## Troubleshooting + +**Job not running:** Verify PostgreSQL is running (jobs use pg-boss queue) + +**Entities undefined:** Add to `entities: [...]` array in job declaration + +**Debugging:** Use `* * * * *` cron for every-minute execution during development diff --git a/template/.claude/skills/adding-feature/database.md b/template/.claude/skills/adding-feature/database.md new file mode 100644 index 000000000..f225db42a --- /dev/null +++ b/template/.claude/skills/adding-feature/database.md @@ -0,0 +1,26 @@ +# Database Setup + +## Adding/Modifying Models + +1. Read [`../../../app/schema.prisma`](../../../app/schema.prisma) for existing patterns +2. Add or modify Prisma model +3. Run migration: `wasp db migrate-dev --name describe-change` + +Wasp automatically generates TypeScript types in `wasp/entities`. + +## Useful Commands + +```bash +wasp db migrate-dev --name # Create and apply migration +wasp db studio # Open GUI to inspect database +wasp db reset # Reset database (WARNING: deletes data) +wasp db seed # Run seed functions +``` + +## Troubleshooting + +**Schema changes not applied:** Run `wasp db migrate-dev --name describe-change` + +**Database out of sync:** Run `wasp db reset` (deletes all data) + +**Migration conflicts:** Check [`../../../app/migrations`](../../../app/migrations) directory for existing migrations diff --git a/template/.claude/skills/adding-feature/kaizen-approach.md b/template/.claude/skills/adding-feature/kaizen-approach.md new file mode 100644 index 000000000..602eddf14 --- /dev/null +++ b/template/.claude/skills/adding-feature/kaizen-approach.md @@ -0,0 +1,728 @@ +# Kaizen: Continuous Improvement + +Apply a continuous improvement mindset - suggest small iterative improvements, error-proof designs, follow established patterns, avoid over-engineering; automatically applied to guide quality and simplicity + +## Overview + +Small improvements, continuously. Error-proof by design. Follow what works. Build only what's needed. + +**Core principle:** Many small improvements beat one big change. Prevent errors at design time, not with fixes. + +**Philosophy:** Quality through incremental progress and prevention, not perfection through massive effort. + +## The Four Pillars + +### 1. Continuous Improvement (Kaizen) + +Small, frequent improvements compound into major gains. + +#### Principles + +**Incremental over revolutionary:** + +- Make smallest viable change that improves quality +- One improvement at a time +- Verify each change before next +- Build momentum through small wins + +**Always leave code better:** + +- Fix small issues as you encounter them +- Refactor while you work (within scope) +- Update outdated comments +- Remove dead code when you see it + +**Iterative refinement:** + +- First version: make it work +- Second pass: make it clear +- Third pass: make it efficient +- Don't try all three at once + + + +```typescript +// Iteration 1: Make it work +const calculateTotal = (items: Item[]) => { + let total = 0; + for (let i = 0; i < items.length; i++) { + total += items[i].price * items[i].quantity; + } + return total; +}; + +// Iteration 2: Make it clear (refactor) +const calculateTotal = (items: Item[]): number => { + return items.reduce((total, item) => { + return total + (item.price * item.quantity); + }, 0); +}; + +// Iteration 3: Make it robust (add validation) +const calculateTotal = (items: Item[]): number => { + if (!items?.length) return 0; + + return items.reduce((total, item) => { + if (item.price < 0 || item.quantity < 0) { + throw new Error('Price and quantity must be non-negative'); + } + return total + (item.price * item.quantity); + }, 0); +}; + +``` + +Each step is complete, tested, and working + + + + +```typescript +// Trying to do everything at once +const calculateTotal = (items: Item[]): number => { + // Validate, optimize, add features, handle edge cases all together + if (!items?.length) return 0; + const validItems = items.filter(item => { + if (item.price < 0) throw new Error('Negative price'); + if (item.quantity < 0) throw new Error('Negative quantity'); + return item.quantity > 0; // Also filtering zero quantities + }); + // Plus caching, plus logging, plus currency conversion... + return validItems.reduce(...); // Too many concerns at once +}; +``` + +Overwhelming, error-prone, hard to verify + + +#### In Practice + +**When implementing features:** + +1. Start with simplest version that works +2. Add one improvement (error handling, validation, etc.) +3. Test and verify +4. Repeat if time permits +5. Don't try to make it perfect immediately + +**When refactoring:** + +- Fix one smell at a time +- Commit after each improvement +- Keep tests passing throughout +- Stop when "good enough" (diminishing returns) + +**When reviewing code:** + +- Suggest incremental improvements (not rewrites) +- Prioritize: critical → important → nice-to-have +- Focus on highest-impact changes first +- Accept "better than before" even if not perfect + +### 2. Poka-Yoke (Error Proofing) + +Design systems that prevent errors at compile/design time, not runtime. + +#### Principles + +**Make errors impossible:** + +- Type system catches mistakes +- Compiler enforces contracts +- Invalid states unrepresentable +- Errors caught early (left of production) + +**Design for safety:** + +- Fail fast and loudly +- Provide helpful error messages +- Make correct path obvious +- Make incorrect path difficult + +**Defense in layers:** + +1. Type system (compile time) +2. Validation (runtime, early) +3. Guards (preconditions) +4. Error boundaries (graceful degradation) + +#### Type System Error Proofing + + + +```typescript +// Error: string status can be any value +type OrderBad = { + status: string; // Can be "pending", "PENDING", "pnding", anything! + total: number; +}; + +// Good: Only valid states possible +type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered'; +type Order = { + status: OrderStatus; + total: number; +}; + +// Better: States with associated data +type Order = + | { status: 'pending'; createdAt: Date } + | { status: 'processing'; startedAt: Date; estimatedCompletion: Date } + | { status: 'shipped'; trackingNumber: string; shippedAt: Date } + | { status: 'delivered'; deliveredAt: Date; signature: string }; + +// Now impossible to have shipped without trackingNumber + +``` +Type system prevents entire classes of errors + + + + +```typescript +// Make invalid states unrepresentable +type NonEmptyArray = [T, ...T[]]; + +const firstItem = (items: NonEmptyArray): T => { + return items[0]; // Always safe, never undefined! +}; + +// Caller must prove array is non-empty +const items: number[] = [1, 2, 3]; +if (items.length > 0) { + firstItem(items as NonEmptyArray); // Safe +} +``` + +Function signature guarantees safety + + +#### Validation Error Proofing + + + +```typescript +// Error: Validation after use +const processPayment = (amount: number) => { + const fee = amount * 0.03; // Used before validation! + if (amount <= 0) throw new Error('Invalid amount'); + // ... +}; + +// Good: Validate immediately +const processPayment = (amount: number) => { + if (amount <= 0) { + throw new Error('Payment amount must be positive'); + } + if (amount > 10000) { + throw new Error('Payment exceeds maximum allowed'); + } + + const fee = amount * 0.03; + // ... now safe to use +}; + +// Better: Validation at boundary with branded type +type PositiveNumber = number & { readonly __brand: 'PositiveNumber' }; + +const validatePositive = (n: number): PositiveNumber => { + if (n <= 0) throw new Error('Must be positive'); + return n as PositiveNumber; +}; + +const processPayment = (amount: PositiveNumber) => { + // amount is guaranteed positive, no need to check + const fee = amount * 0.03; +}; + +// Validate at system boundary +const handlePaymentRequest = (req: Request) => { + const amount = validatePositive(req.body.amount); // Validate once + processPayment(amount); // Use everywhere safely +}; + +``` +Validate once at boundary, safe everywhere else + + +#### Guards and Preconditions + + + +```typescript +// Early returns prevent deeply nested code +const processUser = (user: User | null) => { + if (!user) { + logger.error('User not found'); + return; + } + + if (!user.email) { + logger.error('User email missing'); + return; + } + + if (!user.isActive) { + logger.info('User inactive, skipping'); + return; + } + + // Main logic here, guaranteed user is valid and active + sendEmail(user.email, 'Welcome!'); +}; + +``` + +Guards make assumptions explicit and enforced + + +#### Configuration Error Proofing + + + +```typescript +// Error: Optional config with unsafe defaults +type ConfigBad = { + apiKey?: string; + timeout?: number; +}; + +const client = new APIClient({ timeout: 5000 }); // apiKey missing! + +// Good: Required config, fails early +type Config = { + apiKey: string; + timeout: number; +}; + +const loadConfig = (): Config => { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error('API_KEY environment variable required'); + } + + return { + apiKey, + timeout: 5000, + }; +}; + +// App fails at startup if config invalid, not during request +const config = loadConfig(); +const client = new APIClient(config); + +Fail at startup, not in production + +``` + + +#### In Practice + +**When designing APIs:** +- Use types to constrain inputs +- Make invalid states unrepresentable +- Return Result instead of throwing +- Document preconditions in types + +**When handling errors:** +- Validate at system boundaries +- Use guards for preconditions +- Fail fast with clear messages +- Log context for debugging + +**When configuring:** +- Required over optional with defaults +- Validate all config at startup +- Fail deployment if config invalid +- Don't allow partial configurations + +### 3. Standardized Work + +Follow established patterns. Document what works. Make good practices easy to follow. + +#### Principles + +**Consistency over cleverness:** +- Follow existing codebase patterns +- Don't reinvent solved problems +- New pattern only if significantly better +- Team agreement on new patterns + +**Documentation lives with code:** +- README for setup and architecture +- CLAUDE.md for AI coding conventions +- Comments for "why", not "what" +- Examples for complex patterns + +**Automate standards:** +- Linters enforce style +- Type checks enforce contracts +- Tests verify behavior +- CI/CD enforces quality gates + +#### Following Patterns + + + +```typescript +// Existing codebase pattern for API clients +class UserAPIClient { + async getUser(id: string): Promise { + return this.fetch(`/users/${id}`); + } +} + +// New code follows the same pattern +class OrderAPIClient { + async getOrder(id: string): Promise { + return this.fetch(`/orders/${id}`); + } +} +``` + +Consistency makes codebase predictable + + + + +```typescript +// Existing pattern uses classes +class UserAPIClient { /* ... */ } + +// New code introduces different pattern without discussion +const getOrder = async (id: string): Promise => { + // Breaking consistency "because I prefer functions" +}; + +``` +Inconsistency creates confusion + + +#### Error Handling Patterns + + + +```typescript +// Project standard: Result type for recoverable errors +type Result = { ok: true; value: T } | { ok: false; error: E }; + +// All services follow this pattern +const fetchUser = async (id: string): Promise> => { + try { + const user = await db.users.findById(id); + if (!user) { + return { ok: false, error: new Error('User not found') }; + } + return { ok: true, value: user }; + } catch (err) { + return { ok: false, error: err as Error }; + } +}; + +// Callers use consistent pattern +const result = await fetchUser('123'); +if (!result.ok) { + logger.error('Failed to fetch user', result.error); + return; +} +const user = result.value; // Type-safe! +``` + +Standard pattern across codebase + + +#### Documentation Standards + + + +```typescript +/** + * Retries an async operation with exponential backoff. + * + * Why: Network requests fail temporarily; retrying improves reliability + * When to use: External API calls, database operations + * When not to use: User input validation, internal function calls + * + * @example + * const result = await retry( + * () => fetch('https://api.example.com/data'), + * { maxAttempts: 3, baseDelay: 1000 } + * ); + */ +const retry = async ( + operation: () => Promise, + options: RetryOptions +): Promise => { + // Implementation... +}; +``` +Documents why, when, and how + + +#### In Practice + +**Before adding new patterns:** + +- Search codebase for similar problems solved +- Check CLAUDE.md for project conventions +- Discuss with team if breaking from pattern +- Update docs when introducing new pattern + +**When writing code:** + +- Match existing file structure +- Use same naming conventions +- Follow same error handling approach +- Import from same locations + +**When reviewing:** + +- Check consistency with existing code +- Point to examples in codebase +- Suggest aligning with standards +- Update CLAUDE.md if new standard emerges + +### 4. Just-In-Time (JIT) + +Build what's needed now. No more, no less. Avoid premature optimization and over-engineering. + +#### Principles + +**YAGNI (You Aren't Gonna Need It):** + +- Implement only current requirements +- No "just in case" features +- No "we might need this later" code +- Delete speculation + +**Simplest thing that works:** + +- Start with straightforward solution +- Add complexity only when needed +- Refactor when requirements change +- Don't anticipate future needs + +**Optimize when measured:** + +- No premature optimization +- Profile before optimizing +- Measure impact of changes +- Accept "good enough" performance + +#### YAGNI in Action + + + +```typescript +// Current requirement: Log errors to console +const logError = (error: Error) => { + console.error(error.message); +}; +``` +Simple, meets current need + + + + +```typescript +// Over-engineered for "future needs" +interface LogTransport { + write(level: LogLevel, message: string, meta?: LogMetadata): Promise; +} + +class ConsoleTransport implements LogTransport { /*... */ } +class FileTransport implements LogTransport { /* ... */ } +class RemoteTransport implements LogTransport { /* ...*/ } + +class Logger { + private transports: LogTransport[] = []; + private queue: LogEntry[] = []; + private rateLimiter: RateLimiter; + private formatter: LogFormatter; + + // 200 lines of code for "maybe we'll need it" +} + +const logError = (error: Error) => { + Logger.getInstance().log('error', error.message); +}; + +``` +Building for imaginary future requirements + + +**When to add complexity:** +- Current requirement demands it +- Pain points identified through use +- Measured performance issues +- Multiple use cases emerged + + + +```typescript +// Start simple +const formatCurrency = (amount: number): string => { + return `$${amount.toFixed(2)}`; +}; + +// Requirement evolves: support multiple currencies +const formatCurrency = (amount: number, currency: string): string => { + const symbols = { USD: '$', EUR: '€', GBP: '£' }; + return `${symbols[currency]}${amount.toFixed(2)}`; +}; + +// Requirement evolves: support localization +const formatCurrency = (amount: number, locale: string): string => { + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: locale === 'en-US' ? 'USD' : 'EUR', + }).format(amount); +}; +``` + +Complexity added only when needed + + +#### Premature Abstraction + + + +```typescript +// One use case, but building generic framework +abstract class BaseCRUDService { + abstract getAll(): Promise; + abstract getById(id: string): Promise; + abstract create(data: Partial): Promise; + abstract update(id: string, data: Partial): Promise; + abstract delete(id: string): Promise; +} + +class GenericRepository { /*300 lines */ } +class QueryBuilder { /* 200 lines*/ } +// ... building entire ORM for single table + +``` +Massive abstraction for uncertain future + + + + +```typescript +// Simple functions for current needs +const getUsers = async (): Promise => { + return db.query('SELECT * FROM users'); +}; + +const getUserById = async (id: string): Promise => { + return db.query('SELECT * FROM users WHERE id = $1', [id]); +}; + +// When pattern emerges across multiple entities, then abstract +``` + +Abstract only when pattern proven across 3+ cases + + +#### Performance Optimization + + + +```typescript +// Current: Simple approach +const filterActiveUsers = (users: User[]): User[] => { + return users.filter(user => user.isActive); +}; + +// Benchmark shows: 50ms for 1000 users (acceptable) +// ✓ Ship it, no optimization needed + +// Later: After profiling shows this is bottleneck +// Then optimize with indexed lookup or caching + +``` +Optimize based on measurement, not assumptions + + + + +```typescript +// Premature optimization +const filterActiveUsers = (users: User[]): User[] => { + // "This might be slow, so let's cache and index" + const cache = new WeakMap(); + const indexed = buildBTreeIndex(users, 'isActive'); + // 100 lines of optimization code + // Adds complexity, harder to maintain + // No evidence it was needed +}; +``` + +Complex solution for unmeasured problem + + +#### In Practice + +**When implementing:** + +- Solve the immediate problem +- Use straightforward approach +- Resist "what if" thinking +- Delete speculative code + +**When optimizing:** + +- Profile first, optimize second +- Measure before and after +- Document why optimization needed +- Keep simple version in tests + +**When abstracting:** + +- Wait for 3+ similar cases (Rule of Three) +- Make abstraction as simple as possible +- Prefer duplication over wrong abstraction +- Refactor when pattern clear + +## Red Flags + +**Violating Continuous Improvement:** + +- "I'll refactor it later" (never happens) +- Leaving code worse than you found it +- Big bang rewrites instead of incremental + +**Violating Poka-Yoke:** + +- "Users should just be careful" +- Validation after use instead of before +- Optional config with no validation + +**Violating Standardized Work:** + +- "I prefer to do it my way" +- Not checking existing patterns +- Ignoring project conventions + +**Violating Just-In-Time:** + +- "We might need this someday" +- Building frameworks before using them +- Optimizing without measuring + +## Remember + +**Kaizen is about:** + +- Small improvements continuously +- Preventing errors by design +- Following proven patterns +- Building only what's needed + +**Not about:** + +- Perfection on first try +- Massive refactoring projects +- Clever abstractions +- Premature optimization + +**Mindset:** Good enough today, better tomorrow. Repeat. diff --git a/template/.claude/skills/adding-feature/operations.md b/template/.claude/skills/adding-feature/operations.md new file mode 100644 index 000000000..0997afe1f --- /dev/null +++ b/template/.claude/skills/adding-feature/operations.md @@ -0,0 +1,17 @@ +# Operations (Backend Logic) + +## Setup + +1. create `app/src/{feature-name}/operations.ts` + +## Guidelines + +- **Queries:** for reading data +- **Actions:** for create/update/delete +- check `if (!context.user)` for authenticated operations +- check user subscription status before granting access to premium features +- verify ownership before modifying resources +- access database via `context.entities.EntityName` +- use `HttpError` from `wasp/server` for errors + +Import conventions are in project CLAUDE.md. diff --git a/template/.claude/skills/adding-feature/pages.md b/template/.claude/skills/adding-feature/pages.md new file mode 100644 index 000000000..7db2b17bf --- /dev/null +++ b/template/.claude/skills/adding-feature/pages.md @@ -0,0 +1,14 @@ +# Pages (Frontend Components) + +## Setup + +1. create `app/src/{feature-name}/{FeatureName}Page.tsx` + +## Guidelines + +- **Queries:** use `useQuery` hook, handle `isLoading`/`error` states +- **Actions:** call directly with `await` (no `useAction` unless optimistic updates needed) +- **Auth:** use `useAuth` hook, or receive `AuthUser` via props on protected pages +- **Styling:** use Tailwind + ShadCN components from [`src/client/components/ui`](../../../app/src/client/components/ui/) + +Import conventions are in project CLAUDE.md. diff --git a/template/.claude/skills/adding-feature/troubleshooting.md b/template/.claude/skills/adding-feature/troubleshooting.md new file mode 100644 index 000000000..06b396f72 --- /dev/null +++ b/template/.claude/skills/adding-feature/troubleshooting.md @@ -0,0 +1,13 @@ +# Troubleshooting - Adding Features + +**Type errors after adding operations:** re-run `wasp start`, restart TypeScript server + +**Entity not in context:** add to `entities: [...]` array in main.wasp + +**Migration fails:** check schema.prisma syntax, ensure DB running (`wasp start db`) + +**401 errors:** verify `authRequired: true` in main.wasp + +**Page 404:** verify route defined in main.wasp, restart Wasp server + +For more issues, see [debugging-wasp skill](../debugging-wasp/SKILL.md). diff --git a/template/.claude/skills/configuring-payments/SKILL.md b/template/.claude/skills/configuring-payments/SKILL.md new file mode 100644 index 000000000..f301eb733 --- /dev/null +++ b/template/.claude/skills/configuring-payments/SKILL.md @@ -0,0 +1,68 @@ +--- +name: configuring-payments +description: configure payment integration for Open SaaS (Stripe, Lemon Squeezy, or Polar). +--- + +# configuring-payments + +## Setup Methods + +**Automated Stripe Setup via CLI:** For faster Stripe setup via command line, follow the instructions in [stripe-automated-setup.md](./stripe-automated-setup.md) + +**Manual Setup:** Follow steps below for any provider. + +## Manual Setup Steps + +### 1. Select Provider + +In [`../../../app/src/payment/paymentProcessor.ts`](../../../app/src/payment/paymentProcessor.ts), uncomment chosen provider, comment out others. + +### 2. Environment Variables + +Set in `.env.server`: + +| Provider | Variables | +|----------|-----------| +| Stripe | `STRIPE_API_KEY`, `STRIPE_WEBHOOK_SECRET` | +| Lemon Squeezy | `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_STORE_ID`, `LEMONSQUEEZY_WEBHOOK_SECRET` | +| Polar | `POLAR_ORGANIZATION_ACCESS_TOKEN`, `POLAR_WEBHOOK_SECRET`, `POLAR_SANDBOX_MODE=true` | + +Include specifics, e.g. scopes, from the guide URLs for the chosen provider: https://docs.opensaas.sh/llms.txt + +### 3. Create Products + +Create in provider dashboard: 2 subscriptions (Hobby, Pro) + 1 one-time (Credits). + +Set IDs in `.env.server`: +``` +PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=... +PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=... +PAYMENTS_CREDITS_10_PLAN_ID=... +``` + +Update `src/payment/plans.ts` if changing product names. + +### 4. Configure Webhooks + +**Stripe:** `stripe listen --forward-to localhost:3001/payments-webhook` + +**Lemon Squeezy / Polar:** use ngrok (`ngrok http 3001`), set webhook URL to `https://[ngrok-url]/payments-webhook` + +Include specifics, e.g. events, secrets, etc., from the guide URLs for the chosen provider: https://docs.opensaas.sh/llms.txt + +### 5. Cleanup + +Remove unused provider code by following [cleanup.md](./cleanup.md) + +## Testing Payments and Troubleshooting + +See [troubleshooting](./troubleshooting.md) + +## Documentation + +Fetch guide URLs directly: +- https://docs.opensaas.sh/llms.txt +- https://wasp.sh/llms.txt + +If you need more specific info, use mcp__wasp-docs__find_docs to search. + diff --git a/template/.claude/skills/configuring-payments/cleanup.md b/template/.claude/skills/configuring-payments/cleanup.md new file mode 100644 index 000000000..625793e61 --- /dev/null +++ b/template/.claude/skills/configuring-payments/cleanup.md @@ -0,0 +1,23 @@ +# Cleanup Unused Providers + +Optional cleanup after selecting payment provider. + +## Remove Unused Provider Directories + +Delete code for providers you're not using (e.g., `stripe/`, `lemonsqueezy/`, `polar/`) in the following directories: +- `app/src/payment//` +- `app/src/payment/paymentProcessor.ts` +- `app/src/server/scripts/dbSeeds.ts` +- `app/src/analytics/stats.ts` + +## Environment Variables + +Remove unused provider variables from `.env.server`. + +## Schema (Lemon Squeezy only) + +If not using Lemon Squeezy, remove `lemonSqueezyCustomerPortalUrl` from User model in schema.prisma, then run `wasp db migrate-dev`. + +## Verify + +Run `wasp clean && wasp start`, test payment flow. diff --git a/template/.claude/skills/configuring-payments/scripts/check-stripe-config.sh b/template/.claude/skills/configuring-payments/scripts/check-stripe-config.sh new file mode 100644 index 000000000..83a790a01 --- /dev/null +++ b/template/.claude/skills/configuring-payments/scripts/check-stripe-config.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Check Stripe configuration status for Open SaaS +# Usage: ./check-stripe-config.sh [path-to-env-file] + +set -e + +ENV_FILE="${1:-.env.server}" + +echo "🔍 Checking Stripe Configuration" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Check Stripe CLI +echo "1. Stripe CLI" +if command -v stripe &> /dev/null; then + echo " ✅ Stripe CLI installed" + + if stripe config --list &> /dev/null 2>&1; then + echo " ✅ Authenticated" + ACCOUNT=$(stripe config --list 2>/dev/null | grep "account_id" | awk '{print $2}') + echo " 📋 Account: $ACCOUNT" + else + echo " ⚠️ Not logged in. Run: stripe login" + fi +else + echo " ❌ Stripe CLI not installed" + echo " Install: brew install stripe/stripe-cli/stripe" +fi + +echo "" + +# Check if server is running +echo "2. Webhook Endpoint" +if curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/payments-webhook 2>/dev/null | grep -q "200\|404\|405"; then + echo " ✅ Server responding on port 3001" +else + echo " ⚠️ Server not responding on port 3001" + echo " Start with: wasp start" +fi + +echo "" + +# List products if CLI is available +if command -v stripe &> /dev/null && stripe config --list &> /dev/null 2>&1; then + echo "3. Stripe Products" + echo " Fetching products..." + PRODUCTS=$(stripe products list --limit 5 2>/dev/null) + + if [ $? -eq 0 ]; then + echo "$PRODUCTS" | grep -E "id|name" | head -10 + else + echo " ⚠️ Could not fetch products" + fi +fi + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "Quick commands:" +echo " • View products: stripe products list" +echo " • View prices: stripe prices list" +echo " • Test webhook: stripe trigger checkout.session.completed" +echo " • View events: stripe events list --limit 10" +echo " • Open dashboard: stripe dashboard" +echo "" diff --git a/template/.claude/skills/configuring-payments/scripts/setup-stripe-products.sh b/template/.claude/skills/configuring-payments/scripts/setup-stripe-products.sh new file mode 100644 index 000000000..638b0dbfc --- /dev/null +++ b/template/.claude/skills/configuring-payments/scripts/setup-stripe-products.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Setup Stripe products and prices for Open SaaS +# Usage: ./setup-stripe-products.sh [app-name] [hobby-price] [pro-price] [credits-price] +# Example: ./setup-stripe-products.sh "My SaaS" 999 1999 1499 + +set -e + +# Default values +APP_NAME="${1:-My Open SaaS App}" +HOBBY_PRICE="${2:-999}" # $9.99 in cents +PRO_PRICE="${3:-1999}" # $19.99 in cents +CREDITS_PRICE="${4:-1499}" # $14.99 in cents + +echo "🚀 Creating Stripe products for: $APP_NAME" +echo " Hobby: \$$(echo "scale=2; $HOBBY_PRICE/100" | bc)/month" +echo " Pro: \$$(echo "scale=2; $PRO_PRICE/100" | bc)/month" +echo " Credits: \$$(echo "scale=2; $CREDITS_PRICE/100" | bc)" +echo "" + +# Check if Stripe CLI is installed +if ! command -v stripe &> /dev/null; then + echo "❌ Stripe CLI not found. Install it first:" + echo " macOS: brew install stripe/stripe-cli/stripe" + echo " Other: https://stripe.com/docs/stripe-cli" + exit 1 +fi + +# Check if logged in +if ! stripe config --list &> /dev/null; then + echo "❌ Not logged in to Stripe. Run: stripe login" + exit 1 +fi + +echo "✅ Stripe CLI ready" +echo "" + +# Create Hobby subscription product +echo "Creating Hobby subscription..." +HOBBY_PRODUCT=$(stripe products create \ + --name "$APP_NAME - Hobby" \ + --description "Hobby tier subscription" \ + --format json | grep -o '"id": *"[^"]*"' | head -1 | grep -o 'prod_[^"]*') + +HOBBY_PRICE_ID=$(stripe prices create \ + --product "$HOBBY_PRODUCT" \ + --currency usd \ + --unit-amount "$HOBBY_PRICE" \ + --recurring[interval]=month \ + --format json | grep -o '"id": *"[^"]*"' | head -1 | grep -o 'price_[^"]*') + +echo " ✅ Hobby Plan Price ID: $HOBBY_PRICE_ID" + +# Create Pro subscription product +echo "Creating Pro subscription..." +PRO_PRODUCT=$(stripe products create \ + --name "$APP_NAME - Pro" \ + --description "Pro tier subscription with premium features" \ + --format json | grep -o '"id": *"[^"]*"' | head -1 | grep -o 'prod_[^"]*') + +PRO_PRICE_ID=$(stripe prices create \ + --product "$PRO_PRODUCT" \ + --currency usd \ + --unit-amount "$PRO_PRICE" \ + --recurring[interval]=month \ + --format json | grep -o '"id": *"[^"]*"' | head -1 | grep -o 'price_[^"]*') + +echo " ✅ Pro Plan Price ID: $PRO_PRICE_ID" + +# Create Credits one-time product +echo "Creating Credits package..." +CREDITS_PRODUCT=$(stripe products create \ + --name "$APP_NAME - 10 Credits" \ + --description "10 credits for one-time purchase" \ + --format json | grep -o '"id": *"[^"]*"' | head -1 | grep -o 'prod_[^"]*') + +CREDITS_PRICE_ID=$(stripe prices create \ + --product "$CREDITS_PRODUCT" \ + --currency usd \ + --unit-amount "$CREDITS_PRICE" \ + --format json | grep -o '"id": *"[^"]*"' | head -1 | grep -o 'price_[^"]*') + +echo " ✅ Credits Price ID: $CREDITS_PRICE_ID" +echo "" + +# Output summary +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✅ All products created successfully!" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "Add these to your .env.server file:" +echo "" +echo "PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=$HOBBY_PRICE_ID" +echo "PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=$PRO_PRICE_ID" +echo "PAYMENTS_CREDITS_10_PLAN_ID=$CREDITS_PRICE_ID" +echo "" +echo "Next steps:" +echo "1. Copy the above variables to your .env.server" +echo "2. Get your API key: stripe keys list" +echo "3. Add STRIPE_API_KEY to .env.server" +echo "4. Start webhook forwarding: stripe listen --forward-to localhost:3001/payments-webhook" +echo "5. Add the webhook secret to .env.server" +echo "" diff --git a/template/.claude/skills/configuring-payments/stripe-automated-setup.md b/template/.claude/skills/configuring-payments/stripe-automated-setup.md new file mode 100644 index 000000000..baf48d06f --- /dev/null +++ b/template/.claude/skills/configuring-payments/stripe-automated-setup.md @@ -0,0 +1,168 @@ +# Stripe Automated Setup via CLI + +Use these scripts and instructions to automate Stripe setup via CLI. +Important: never edit any .env files, always ask the user to add the values! + +## When to Use This + +Use CLI automation when: +- User explicitly asks for CLI/automated setup +- User is comfortable with command-line tools +- Setting up Stripe for the first time +- Need to quickly create test products + +Use manual setup (dashboard) when: +- User prefers GUI +- User needs custom product configurations +- Setting up production (review each setting) + +## Prerequisites + +### Step 1: Select Your Stripe Account/Workspace + +**Important:** If you have multiple Stripe business accounts or workspaces, you MUST select the correct one BEFORE logging in to CLI. This ensures test products are created in the right workspace. + +1. Go to https://dashboard.stripe.com +2. Click your account name in the **top left corner** +3. Select the correct business/workspace for this project +4. (Optional) If you need a new workspace, click **"+ New account"** and set it up +5. **Stay logged in** with this account selected + +### Step 2: Install Stripe CLI + +Check if Stripe CLI is installed: +```bash +stripe --version +``` + +If not installed: +```bash +# macOS +brew install stripe/stripe-cli/stripe + +# Linux/Windows +# Download from: https://stripe.com/docs/stripe-cli +``` + +### Step 3: Login to Stripe CLI + +```bash +stripe login +``` + +This will open your browser. Authorize the CLI with the account you selected in Step 1. + +### Step 4: Verify Correct Account is Connected + +After login, run: +```bash +stripe config --list +``` + +Prompt the user to confirm that the following are correct: +- [ ] Account ID +- [ ] Display Name +- [ ] Project Name +Prompt: "Does this match the Stripe workspace you selected in Step 1? If not, please log out and log back in with the correct project name." + +**⚠️ IMPORTANT:** The `test_mode_api_key` shown in `stripe config --list` is a **temporary CLI session key**, NOT your actual Stripe API key. Do NOT use this key in `.env.server`. You must get the real API key from the Stripe Dashboard (see Step 1 below). + +If the account is wrong, follow these steps: + +1. Log out: + ```bash + stripe logout + ``` + +2. Prompt user to go to https://dashboard.stripe.com and select the correct workspace (top left corner) + +3. Log back in: + ```bash + stripe login + ``` + +4. Verify again: + ```bash + stripe config --list + ``` + +## Automated Setup Process + +### Step 1: Get API Key from Dashboard + +**⚠️ CRITICAL:** You must get the API key from the Stripe Dashboard, NOT from `stripe config --list`. The CLI's `test_mode_api_key` is a temporary session key that won't work for your application. + +Open the Stripe API keys page: +```bash +stripe open dashboard/apikeys +``` + +This opens your browser to the API keys page. Look for the **Secret key** (starts with `sk_test_` for test mode). Click "Reveal test key" if needed, then copy it. + +Instruct the user to add to `.env.server`: +```bash +STRIPE_API_KEY=sk_test_51ABC... +``` + +### Step 2: Create Products + +Run the product setup script: +```bash +cd .claude/skills/configuring-payments +chmod +x setup-stripe-products.sh + +# Basic usage - use app name from main.wasp +./setup-stripe-products.sh "App Name" + +# Custom pricing (in cents): hobby pro credits +./setup-stripe-products.sh "App Name" 999 1999 1499 +``` + +The script will output Price IDs. Help user add to `.env.server`: +```bash +PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=price_1ABC... +PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=price_1DEF... +PAYMENTS_CREDITS_10_PLAN_ID=price_1GHI... +``` + +### Step 3: Setup Webhook + +Instruct user to open a **separate terminal** and run: +```bash +stripe listen --forward-to localhost:3001/payments-webhook +``` + +This outputs a webhook secret (starts with `whsec_`). Help user add to `.env.server`: +```bash +STRIPE_WEBHOOK_SECRET=whsec_ABC123... +``` + +**Important:** This terminal must stay open during development. + +### Step 4: Verify Configuration + +Run the verification script: +```bash +cd .claude/skills/configuring-payments +chmod +x check-stripe-config.sh +./check-stripe-config.sh +``` + +Review output with user and fix any issues. + +### Step 5: Cleanup + +- Remove the unused payment processor code from the `src/payment/` directory. +- Prompt the user to remove the unused provider variables from the `.env.server` file. + +### Step 6: Restart Wasp Server + +Server should auto-restart when `.env.server` changes. If not: +```bash +# Stop current server (Ctrl+C) and restart +wasp start +``` + +## Testing Payments and Troubleshooting + +See [troubleshooting](./troubleshooting.md) for common issues and how to fix them. diff --git a/template/.claude/skills/configuring-payments/troubleshooting.md b/template/.claude/skills/configuring-payments/troubleshooting.md new file mode 100644 index 000000000..c67120860 --- /dev/null +++ b/template/.claude/skills/configuring-payments/troubleshooting.md @@ -0,0 +1,35 @@ +# Troubleshooting - Payments + +## Webhooks Not Working + +**Stripe:** verify `stripe listen` running, secret matches CLI output + +**Lemon Squeezy / Polar:** verify ngrok running, URL includes `/payments-webhook`, secret matches + +**ngrok expired:** restart ngrok, update webhook URL in dashboard + +## Product IDs + +ID format varies by provider: +- Stripe: `price_...` +- Lemon Squeezy: `variant_...` +- Polar: product IDs + +Ensure products active in dashboard, IDs correct in `.env.server`. + +## Subscription Not Updating + +Check webhook events enabled: +- Lemon Squeezy: `order_created`, `subscription_*` +- Polar: `order.paid`, `subscription.updated` + +## Test Payments + +**Stripe:** test card `4242 4242 4242 4242`, API key starts with `sk_test_` +**Polar:** set `POLAR_SANDBOX_MODE=true` + +- test checkout, verify `subscriptionStatus` in `wasp db studio` + +## More Help + +See [Open SaaS payment docs](https://docs.opensaas.sh/llms.txt) diff --git a/template/.claude/skills/deploying-app/SKILL.md b/template/.claude/skills/deploying-app/SKILL.md new file mode 100644 index 000000000..ebc730362 --- /dev/null +++ b/template/.claude/skills/deploying-app/SKILL.md @@ -0,0 +1,44 @@ +--- +name: deploying-app +description: deploy the Open SaaS app to Railway or Fly.io using Wasp CLI. +--- + +# deploying-app + +## Pre-Deployment + +1. Run the [validating-pre-deployment skill](../validating-pre-deployment/SKILL.md) first to validate configuration. +2. confirm platform: Railway or Fly.io +3. verify CLI installed and logged in: + - Railway: `railway whoami` + - Fly.io: `fly auth whoami` +4. collect env vars: server secrets + client vars (prefixed `REACT_APP_`) + +## Deploy Commands + +| Platform | First Time | Subsequent | +|----------|------------|------------| +| Railway | `wasp deploy railway launch --server-secret KEY=val` | `REACT_APP_X=val wasp deploy railway deploy ` | +| Fly.io | `wasp deploy fly launch --server-secret KEY=val` | `REACT_APP_X=val wasp deploy fly deploy` | + +**Critical:** +- `launch` runs ONCE only +- Client vars required on EVERY deploy +- Don't interrupt deployment + +## Verify + +- Check logs: Railway dashboard, `wasp deploy railway variables` or `wasp deploy fly cmd logs --context=server` +- Test deployed URL + +## Troubleshooting + +See [troubleshooting](./troubleshooting.md) + +## Documentation + +Fetch guide URLs directly: +- https://docs.opensaas.sh/llms.txt +- https://wasp.sh/llms.txt + +If you need more specific info, use mcp__wasp-docs__find_docs to search. diff --git a/template/.claude/skills/deploying-app/troubleshooting.md b/template/.claude/skills/deploying-app/troubleshooting.md new file mode 100644 index 000000000..2f31f3b11 --- /dev/null +++ b/template/.claude/skills/deploying-app/troubleshooting.md @@ -0,0 +1,36 @@ +# Troubleshooting - Deployment + +## Client Env Vars Not Working + +Must be prefixed `REACT_APP_` and provided on EVERY deploy: +```bash +REACT_APP_X=val wasp deploy railway deploy my-app +``` + +## Deployment Interrupted + +Safe to rerun: `wasp deploy railway deploy` or `wasp deploy fly deploy` + +Do NOT rerun: `launch` commands (one-time only) + +## Add Server Secrets After Launch + +**Railway:** set in dashboard (Service > Variables) or rerun with `--server-secret` + +**Fly.io:** `wasp deploy fly cmd secrets set KEY=value --context=server` + +## Name Already Taken + +Railway names unique per account. Fly.io names globally unique. + +## Build Failures + +Test locally first: `wasp build` + +## Database Issues + +Both platforms auto-configure `DATABASE_URL`. Check database service is running in dashboard/status. + +## More Help + +See [Wasp deployment docs](https://wasp.sh/llms.txt) diff --git a/template/.claude/skills/seo-optimizer/SKILL.md b/template/.claude/skills/seo-optimizer/SKILL.md new file mode 100644 index 000000000..98624b79f --- /dev/null +++ b/template/.claude/skills/seo-optimizer/SKILL.md @@ -0,0 +1,97 @@ +--- +name: seo-optimizer +description: interactive SEO audit and fixes for your Open SaaS app. +--- + +# seo-optimizer + +Interactive SEO audit for your Open SaaS app. Finds issues and offers fixes one at a time. + +## Before Starting + +1. Read the wasp config file (`main.wasp` or `main.wasp.ts`) to check current meta tag configuration +2. If placeholders detected, ask user for their app name and production domain + +## Audit Sequence + +Run these checks in order. For each issue found: explain → ask user for input → generate fix → apply → move to next. + +### Step 1: Critical Meta Tags + +Check the wasp config file `app.head` for placeholder values: + +| Placeholder | Field | +|-------------|-------| +| "My Open SaaS App" or contains "Open SaaS" | title, og:title | +| "your-saas-app.com" | og:url | +| "Your (App) Name" | author | +| "me@example.com" | author email | +| Generic descriptions | description, og:description | + +For each placeholder found: +1. Ask user: "What should [field] be?" +2. Generate the corrected meta tag +3. Apply the fix to main.wasp +4. Move to next issue + +### Step 2: Open Graph Validation + +Verify Open Graph tags are complete and consistent: + +**Required OG tags:** +- `og:type` (should be "website") +- `og:title` (should match app title) +- `og:description` (should match meta description) +- `og:url` (should be production domain) +- `og:image` (should point to actual banner image) +- `og:site_name` + +**Twitter Card tags:** +- `twitter:card` (summary_large_image recommended) +- `twitter:image` + +If any are missing or inconsistent → offer to fix. + +### Step 3: Technical SEO + +Check [`../../../app/public/`](../../../app/public/) for: + +**robots.txt:** +- If missing → offer to create using [robots-txt.md](./robots-txt.md) template +- If exists → verify it allows search engine crawling + +**favicon:** +- Verify favicon.ico exists +- If missing → warn user + +### Step 4: Structured Data (Optional) + +Ask: "Would you like to add structured data (JSON-LD) for better search results?" + +If yes, offer to generate: +1. **Organization schema** - Company/app info +2. **FAQPage schema** - If FAQ section exists (reads from contentSections.tsx) + +See [structured-data.md](./structured-data.md) for templates. + +## File Paths + +| File | What to Check | +|------|---------------| +| the wasp config file (`main.wasp` or `main.wasp.ts`) | `app.head` meta tags | +| `app/public/` | robots.txt, favicon.ico | +| `src/landing-page/contentSections.tsx` | FAQ data for schema generation | + +## Completion + +After all fixes applied, summarize: +- What was fixed +- What user should verify manually (e.g., OG image looks correct) +- Recommend testing with [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) and [Twitter Card Validator](https://cards-dev.twitter.com/validator) + +## Documentation + +Fetch guide URLs directly: +- https://docs.opensaas.sh/llms.txt + +If you need more specific info, use mcp__wasp-docs__find_docs to search. diff --git a/template/.claude/skills/seo-optimizer/meta-tags.md b/template/.claude/skills/seo-optimizer/meta-tags.md new file mode 100644 index 000000000..8473dfa1d --- /dev/null +++ b/template/.claude/skills/seo-optimizer/meta-tags.md @@ -0,0 +1,80 @@ +# Meta Tags Reference + +## Required Meta Tags + +These must be in the wasp config file (`main.wasp` or `main.wasp.ts`) under `app.head`: + +```html + + +Your App Name - Tagline + + + + + + + + + + + + + + + + +``` + +## Open Graph Image Requirements + +- **Dimensions:** 1200 x 630 pixels (recommended) +- **Format:** PNG, JPG, or WebP +- **File size:** Under 1MB +- **Location:** `app/public/` directory +- **URL:** Must be absolute URL in production + +## Placeholder Detection + +Watch for these common placeholder values in Open SaaS: + +| Placeholder | Replace With | +|-------------|--------------| +| `"My Open SaaS App"` | Your actual app name | +| `"Open SaaS"` | Your actual app name | +| `"your-saas-app.com"` | Your production domain | +| `"Your (App) Name"` | Your name or company | +| `"me@example.com"` | Your actual email | +| `"/public-banner.webp"` | Your custom OG image path | +app MyApp { + wasp: { version: "^0.15.0" }, + title: "Acme SaaS - Project Management Made Simple", + head: [ + "", + "", + "", + "", + + "", + "", + "", + "", + "", + "", + + "", + "", + + "" + ], + // ... +} +``` + +## Testing Tools + +After making changes, verify with: +- [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) +- [Twitter Card Validator](https://cards-dev.twitter.com/validator) +- [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) +- [OpenGraph.xyz](https://www.opengraph.xyz/) diff --git a/template/.claude/skills/seo-optimizer/robots-txt.md b/template/.claude/skills/seo-optimizer/robots-txt.md new file mode 100644 index 000000000..8435029f1 --- /dev/null +++ b/template/.claude/skills/seo-optimizer/robots-txt.md @@ -0,0 +1,88 @@ +# robots.txt Template + +## Location + +Create at: `app/public/robots.txt` + +This file tells search engine crawlers which pages to index and which to ignore. + +## Standard Template for SaaS Apps + +```txt +# robots.txt for SaaS Application + +User-agent: * +Allow: / + +# Disallow authenticated app routes +Disallow: /demo-app +Disallow: /account +Disallow: /admin +Disallow: /checkout + +# Disallow API routes (if exposed) +Disallow: /api/ +Disallow: /operations/ + +# Sitemap (if you have one) +Sitemap: https://yourdomain.com/sitemap.xml +``` + +## What to Allow + +**Allow crawling:** +- `/` - Landing page +- `/pricing` - Pricing page +- `/login` - Login page (for SEO, users searching "yourapp login") +- `/signup` - Signup page +- Blog pages (if on same domain) + +## What to Disallow + +**Block crawling:** +- `/demo-app` - Authenticated app pages +- `/account` - User account pages +- `/admin` - Admin dashboard +- `/checkout` - Payment/checkout flows +- `/api/` - API endpoints +- `/operations/` - Wasp operations + +## Sitemap Reference + +If your app has a sitemap (Astro blog generates one automatically): +```txt +Sitemap: https://yourdomain.com/sitemap.xml +``` + +For the main Wasp app, you may need to generate a sitemap separately or use a service. + +## Important Notes + +1. **robots.txt is public** - Don't use it to hide sensitive URLs (they can still be discovered) +2. **Not a security measure** - It's a suggestion to crawlers, not enforcement +3. **Crawl-delay** - Avoid unless you have server capacity issues +4. **Test changes** - Use [Google's robots.txt Tester](https://support.google.com/webmasters/answer/6062598) + +## Common Mistakes + +**Too restrictive:** +```txt +# BAD - blocks everything +User-agent: * +Disallow: / +``` + +**Missing trailing slashes:** +```txt +# This blocks /admin but not /administrator +Disallow: /admin + +# Use this to block all paths starting with /admin +Disallow: /admin +``` + +## After Creating + +1. Verify file is accessible at `https://yourdomain.com/robots.txt` +2. Check in [Google Search Console](https://search.google.com/search-console) for crawl errors +3. Submit sitemap if you have one diff --git a/template/.claude/skills/seo-optimizer/structured-data.md b/template/.claude/skills/seo-optimizer/structured-data.md new file mode 100644 index 000000000..c5a7ba96c --- /dev/null +++ b/template/.claude/skills/seo-optimizer/structured-data.md @@ -0,0 +1,127 @@ +# Structured Data (JSON-LD) Templates + +Structured data helps search engines understand your content and can enable rich results in search. + +## How to Add JSON-LD to Open SaaS + +Add as a ` +``` + +**Note:** JSON must be on a single line or escaped properly in the wasp string. + +## Organization Schema + +Use this for your company/app info. Helps with knowledge panel in search results. + +```json +{ + "@context": "https://schema.org", + "@type": "Organization", + "name": "Your App Name", + "url": "https://yourdomain.com", + "logo": "https://yourdomain.com/logo.png", + "description": "Brief description of your company/app", + "sameAs": [ + "https://twitter.com/yourhandle", + "https://github.com/yourorg", + "https://linkedin.com/company/yourcompany" + ] +} +``` + +## WebSite Schema + +Enables sitelinks search box in Google results. + +```json +{ + "@context": "https://schema.org", + "@type": "WebSite", + "name": "Your App Name", + "url": "https://yourdomain.com" +} +``` + +## FAQPage Schema + +If your landing page has an FAQ section, this can show FAQ rich results in Google. + +**Template:** +```json +{ + "@context": "https://schema.org", + "@type": "FAQPage", + "mainEntity": [ + { + "@type": "Question", + "name": "What is Your App?", + "acceptedAnswer": { + "@type": "Answer", + "text": "Your App is a SaaS platform that..." + } + }, + { + "@type": "Question", + "name": "How much does it cost?", + "acceptedAnswer": { + "@type": "Answer", + "text": "We offer plans starting at..." + } + } + ] +} +``` + +**Generating from contentSections.tsx:** + +Read the FAQs array from [`../../../app/src/landing-page/contentSections.tsx`](../../../app/src/landing-page/contentSections.tsx) and generate the schema: + +```typescript +// contentSections.tsx has: +export const faqs = [ + { id: 1, question: '...', answer: '...' }, + // ... +] +``` + +Transform to JSON-LD `mainEntity` array. + +## SoftwareApplication Schema + +Good for SaaS products, can show ratings and pricing in search. + +```json +{ + "@context": "https://schema.org", + "@type": "SoftwareApplication", + "name": "Your App Name", + "applicationCategory": "BusinessApplication", + "operatingSystem": "Web", + "offers": { + "@type": "Offer", + "price": "0", + "priceCurrency": "USD" + } +} +``` + +## Testing Structured Data + +After adding, verify with: +- [Google Rich Results Test](https://search.google.com/test/rich-results) +- [Schema Markup Validator](https://validator.schema.org/) + +## Common Issues + +**JSON parsing errors:** +- Escape quotes properly: `\"` not `"` +- No trailing commas in arrays/objects +- Must be valid JSON on single line in wasp string + +**Schema not detected:** +- Ensure `type="application/ld+json"` is set +- Check JSON is valid +- Wait for Google to recrawl (can take days) diff --git a/template/.claude/skills/setup-wizard/SKILL.md b/template/.claude/skills/setup-wizard/SKILL.md new file mode 100644 index 000000000..61161d8e2 --- /dev/null +++ b/template/.claude/skills/setup-wizard/SKILL.md @@ -0,0 +1,114 @@ +--- +name: setup-wizard +description: interactive guided setup for configuring a new Open SaaS project. +--- + +# setup-wizard + +This wizard walks through essential Open SaaS configuration. Run each step in order, asking the user for decisions at each stage. + +## Before Starting + +1. verify user is in the app directory (check for wasp config file: `main.wasp` or `main.wasp.ts`) +2. ask: "Would you like me to guide you through setting up your Open SaaS project?" + +## Setup Steps + +Guide the user through these steps in order. At each step, ask for their preferences before making changes. + +### Step 1: App Branding + +Ask the user: +- What is your app name? +- What is a one-line description? + +Then update in [`../../../app/main.wasp`](../../../app/main.wasp): +- `app.name` +- `app.title` +- `app.head` meta tags + +DO NOT change URLs in the `app.head` meta tags. Leave these as they are until the user has a production domain and is ready to deploy. + +### Step 2: Authentication + +Ask the user which auth methods they want: +- Email/password (works right away with `Dummy` email sender in local development) +- Google OAuth (requires API keys) +- GitHub OAuth (requires API keys) +- Discord OAuth (requires API keys) +- Slack OAuth (requires API keys) +- Keycloak OAuth (requires API keys) + +For each OAuth provider selected: +1. uncomment or add the provider in wasp config file auth methods +2. inform user they'll need to set env vars later (Step 6) + +### Step 3: Payment Provider + +Ask the user which payment provider they'd like to use: + - Stripe (industry standard, most configurable, lower fees per transaction) + - Polar (Merchant of Record, great DX, higher fees per transaction) + - Lemon Squeezy (Merchant of Record, higher fees per transaction) + - Skip for now (no payments) + +If they choose one: +1. update `src/payment/paymentProcessor.ts` to select their provider +2. remove the unused payment processor code from the `src/payment/` directory +3. remove unused payment provider code and clients from `src/analytics/stats.ts` +4. prompt the user to remove the unused provider variables from the `.env.server` file +5. note they'll configure their provider's credentials in Step 6 + +### Step 4: Email Provider + +Ask the user which email provider they'd like to use: + - SendGrid (requires API key) + - Mailgun (requires API key) + - SMTP (requires SMTP server credentials) + - Skip for now (default provider is `Dummy`, which logs emails to console for local development) + +If they choose one: +1. update `emailSender.provider` in wasp config file +2. note they'll configure credentials in Step 6 + +### Step 5: Analytics (Optional) + +Ask the user if they want to set up analytics: + - Plausible (requires API key) + - Google Analytics (requires API key) + - Skip for now (no analytics) + +If yes, note they'll configure the analytics provider in Step 6. + +### Step 6: Completing Integrations & Environment Variables + +For each integration selected in Steps 2-5: +1. fetch the guide or skill from the list below to guide the user through the integration +2. generate checklist of required env vars +3. give user instructions for retrieving and adding env vars to `.env.server` +4. follow steps/run commands in the guide or skill to complete the integration for the user where applicable +5. prompt the user to confirm they're ready to move to the next integration +6. move to next integration + +Skills and guides to fetch for each integration: +- OAuth providers: find the correct provider guide URL at https://wasp.sh/llms.txt +- Email providers: find the sending emails guide URL at https://wasp.sh/llms.txt +- Analytics providers: find the correct guide URL at https://docs.opensaas.sh/llms.txt +- Payment providers: invoke the [configuring-payments skill](../configuring-payments/SKILL.md) + +### Step 7: Verify Setup + +1. start the wasp app processes in new terminals as background tasks in the current Claude Code session: `wasp start db` and `wasp start`. +2. after starting the development server, verify configuration compiles +3. check for any errors. + +## Step 8: Completion + +Provide a summary of the setup process and what other skills and commands are available to help with further setup. + +## Documentation + +Fetch guide URLs directly from the Wasp and Open SaaS docs: +- https://wasp.sh/llms.txt +- https://docs.opensaas.sh/llms.txt + +If you need more specific info, use mcp__wasp-docs__find_docs to search. diff --git a/template/.claude/skills/validating-pre-deployment/SKILL.md b/template/.claude/skills/validating-pre-deployment/SKILL.md new file mode 100644 index 000000000..145bb4b39 --- /dev/null +++ b/template/.claude/skills/validating-pre-deployment/SKILL.md @@ -0,0 +1,209 @@ +--- +name: validating-pre-deployment +description: validate configuration and test production build before deploying. +--- + +# validating-pre-deployment + +Pre-deployment validation checks that catch common issues before deploying with the [deploying-app skill](../deploying-app/SKILL.md). + +## Before Starting + +1. verify user is in the app directory (check for `main.wasp` or `main.wasp.ts`) +2. ask: "Would you like me to run pre-deployment checks on your Open SaaS app?" + +## Validation Steps + +Run these checks in order. Report all issues found, then ask the user if they want to proceed or fix issues first. + +### Step 1: Wasp Config Metadata + +Check the wasp config file (`main.wasp` or `main.wasp.ts`) `app` block for placeholder values: + +**App Identity:** +- `app.title` - should NOT be "My Open SaaS App" or contain "Open SaaS" +- `app.name` - should be customized (PascalCase identifier) + +**Head Meta Tags (in `app.head`):** +- ``) +- OR scripts should be removed/commented if not using Plausible + +**Email Configuration:** +- `auth.methods.email.fromField.email` - should NOT be "me@example.com" +- `emailSender.defaultFrom.email` - should NOT be "me@example.com" +- `emailSender.provider` - should NOT be "Dummy" for production + +Report format: +``` +## Configuration Issues Found + +### Critical (must fix): +- [ ] issue description + +### Warnings (recommended to fix): +- [ ] issue description + +### Passed: +- [x] check that passed +``` + +### Step 2: Environment Variables + +Based on the wasp config, generate a checklist of required env vars for the user to verify. **Do NOT read .env files** - ask the user to confirm they have the required variables set. + +Present the checklist based on what's configured: + +``` +## Environment Variables Checklist + +Please verify you have these variables ready for deployment: + +### Server Variables (.env.server) +- [ ] DATABASE_URL - auto-set by Railway/Fly, no action needed +- [ ] JWT_SECRET - auto-generated by Wasp CLI deploy + +### Based on Your Config: +[Generate this list by checking main.wasp for enabled features] +``` + +**If OAuth providers enabled:** +- Google: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` +- GitHub: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` +- Discord: `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET` +- etc. + +**If email provider is SendGrid/Mailgun/SMTP:** +- SendGrid: `SENDGRID_API_KEY` +- Mailgun: `MAILGUN_API_KEY`, `MAILGUN_DOMAIN` +- SMTP: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD` + +**If file uploads configured:** +- `AWS_S3_REGION`, `AWS_S3_FILES_BUCKET`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` + +**If OpenAI integration used:** +- `OPENAI_API_KEY` + +**If payment provider configured:** +- Remind user to switch from test/sandbox keys to **production keys** +- Stripe: `STRIPE_API_KEY` (live key), `STRIPE_WEBHOOK_SECRET` +- Lemon Squeezy: `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_WEBHOOK_SECRET` +- Polar: `POLAR_ACCESS_TOKEN`, `POLAR_WEBHOOK_SECRET`, set `POLAR_SANDBOX_MODE=false` +- See [configuring-payments skill](../configuring-payments/SKILL.md) for full setup + +**Client Variables:** +- List any `REACT_APP_*` vars needed +- Remind user: "Client env vars must be passed at deploy time, not set in hosting dashboards." + +Ask user: "Do you have all the required environment variables ready? (yes/no)" + +### Step 3: Production Configuration Reminders + +Based on enabled features, remind user of production setup tasks: + +**If OAuth providers enabled:** +- Remind user: "Add your production domain to OAuth redirect URLs in each provider's dashboard" +- Format: `https://your-domain.com/auth//callback` + +**If payment provider configured:** +- Stripe: Create production webhook endpoint at `https://your-domain.com/payments-webhook` +- Lemon Squeezy: Configure webhook with events: `order_created`, `subscription_created`, `subscription_updated`, `subscription_cancelled` +- Polar: Create webhook endpoint, disable sandbox mode + +**If AWS S3 file uploads used:** +- Remind user: "Add your production domain to S3 bucket CORS `AllowedOrigins`" + +### Step 4: Database Migrations + +Check for pending migrations: +```bash +# List migration files +ls -la migrations/ +``` + +Remind user: +- Production automatically applies pending migrations on server start +- Ensure migrations are committed to version control +- Test migrations work locally before deploying + +### Step 5: Production Build Test + +Ask user: "Would you like to test the production build locally? This catches environment-specific issues." + +If yes, guide through: + +1. Start database (if not running): + ```bash + wasp start db + ``` + +2. Run production build test: + ```bash + wasp build start --server-env DATABASE_URL= --server-env JWT_SECRET=test-secret-for-local + ``` + + Or with env file: + ```bash + wasp build start --server-env-file .env.server + ``` + +3. Verify app loads at `http://localhost:3000` + +4. Check for: + - Build compilation errors + - Runtime errors in console + - Missing env var errors + +If issues found, help debug before proceeding. + +### Step 6: Final Checklist + +Present summary: + +``` +## Pre-Deployment Summary + +### Configuration Status: +- App Name: [name] +- App Title: [title] +- Email Provider: [provider] +- Auth Methods: [list] +- Payment Provider: [if configured] + +### Issues to Resolve: +[list any issues from steps 1-4] + +### Before Deploying: +- [ ] All configuration placeholders replaced +- [ ] Production email provider configured +- [ ] Required env vars ready for deployment +- [ ] Production build tested locally (optional but recommended) +- [ ] Database migrations committed + +### Ready to Deploy? +``` + +## Completion + +If all checks pass or user chooses to proceed: +- Summarize what was validated +- Ask: "Would you like to proceed with deployment? I can guide you through deploying to Railway or Fly.io." +- If yes, transition to [deploying-app skill](../deploying-app/SKILL.md) + +## Documentation + +Fetch guide URLs directly: +- https://docs.opensaas.sh/llms.txt + +If you need more specific info, use mcp__wasp-docs__find_docs to search. diff --git a/template/.mcp.json b/template/.mcp.json new file mode 100644 index 000000000..43045c94d --- /dev/null +++ b/template/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "chrome-devtools": { + "command": "npx", + "args": ["chrome-devtools-mcp@latest"] + }, + "wasp-docs": { + "command": "wasp-mcp-docs" + } + } +} diff --git a/template/app/.cursor/commands/deslop.md b/template/app/.cursor/commands/deslop.md new file mode 100644 index 000000000..d82835663 --- /dev/null +++ b/template/app/.cursor/commands/deslop.md @@ -0,0 +1,11 @@ +# Remove AI code slop + +Check the diff against main, and remove all AI generated slop introduced in this branch. + +This includes: +- Extra comments that a human wouldn't add or is inconsistent with the rest of the file +- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) +- Casts to any to get around type issues +- Any other style that is inconsistent with the file + +Report at the end with only a 1-3 sentence summary of what you changed \ No newline at end of file diff --git a/template/app/.cursor/rules/wasp-overview.mdc b/template/app/.cursor/rules/wasp-overview.mdc index bd9ee942e..17484b0a9 100644 --- a/template/app/.cursor/rules/wasp-overview.mdc +++ b/template/app/.cursor/rules/wasp-overview.mdc @@ -9,6 +9,25 @@ This document covers the fundamental concepts of the Wasp framework and the basi ## Background Information +### What is Open SaaS + +- Open SaaS is a free, open-source SaaS starter template built on top of the Wasp framework. +- It provides a production-ready foundation for building SaaS applications with essential features already implemented: + - Full-stack authentication (email + OAuth providers: Google, GitHub, Discord) + - Stripe and Lemon Squeezy payment integration with subscription management + - Admin dashboard with user management + - Email sending (SendGrid, Mailgun, or SMTP) + - File uploads with AWS S3 + - Analytics integration (Plausible or Google Analytics) + - Landing page and documentation site (Astro Starlight) + - Example AI integration with OpenAI + - End-to-end testing with Playwright + - One-command deployment to Railway or Fly.io +- Open SaaS follows best practices and conventions specific to building SaaS applications on Wasp. +- The template is designed to be customized and extended for your specific SaaS product needs. +- Documentation: https://docs.opensaas.sh +- LLM-optimized Open SaaS docs: https://docs.opensaas.sh/llms-full.txt + ### What is Wasp - Wasp (Web Application SPecification language) is a declarative, statically typed, domain-specific language (DSL) for building modern, full-stack web applications. diff --git a/template/app/CLAUDE.md b/template/app/CLAUDE.md new file mode 100644 index 000000000..b6651771e --- /dev/null +++ b/template/app/CLAUDE.md @@ -0,0 +1,89 @@ +# CLAUDE.md + +## What is Open SaaS? + +Open SaaS is a free, open-source SaaS starter built on Wasp (React + Node.js + Prisma). It includes auth, payments, admin dashboard, email, file uploads, and analytics out of the box. Users customize this template to build their own SaaS applications. + +**Tech stack:** Wasp, React, TypeScript, Node.js, Prisma, PostgreSQL, TailwindCSS, ShadCN UI + +## Critical Files + +Read these first to understand the app: + +- [main.wasp](main.wasp) - App config: routes, pages, auth, operations, jobs + - note: users can also use `main.wasp.ts` instead of `main.wasp` +- [schema.prisma](schema.prisma) - Database models and relationships +- [src/payment/](src/payment/) - Payment processor integration & great example of Wasp's feature set in action. +- [src/auth/](src/auth/) - Authentication logic and pages + +## Commands + +```bash +# Development +wasp start # Start dev server (DB + server + client) +wasp start db # Start only the database +wasp db migrate-dev # Apply schema changes +wasp db studio # Inspect database GUI + +# Production +wasp build # Generate production build +wasp deploy # Deploy to Railway or Fly.io + +# Maintenance +wasp clean # Delete generated code (fixes most issues) +``` + +## Project Structure + +``` +src/ +├── {feature}/ # Feature modules (auth, payment, user, admin, etc.) +│ ├── operations.ts # Wasp queries and actions +│ └── *Page.tsx # Page components +├── client/components/ # Shared UI components (ShadCN) +└── shared/ # Code shared between client and server +``` + +Features are organized by domain. Use existing features as patterns when adding new ones. + +## Coding Standards + +- **Comment the "why", not the "what"** - Explain non-obvious decisions and edge cases +- Use descriptive names over comments where possible +- Follow existing patterns in the codebase + +## Import Conventions (Critical) + +**In TypeScript files:** +- ✅ `import type { User } from 'wasp/entities'` +- ✅ `import type { GetTasks } from 'wasp/server/operations'` +- ✅ `import { getTasks,useQuery } from 'wasp/client/operations'` +- ✅ `import { SubscriptionStatus } from '@prisma/client'` +- ✅ Local code: relative paths `import { X } from './X'` +- ❌ Never `@wasp/...` or `@src/...` +- ⚠️ Call actions directly using `async/await`. DO NOT use Wasp's `useAction` hook unless optimistic updates are needed. + +**In main.wasp:** +- ✅ `fn: import { getTasks } from "@src/tasks/operations.ts"` +- ❌ Never relative paths + +## Troubleshooting + +| Error | Fix | +|-------|-----| +| `Cannot find module 'wasp/...'` | Use `wasp/`, not `@wasp/` | +| `Cannot find module '@src/...'` in TS | Use relative paths; `@src/` is only for main.wasp | +| `context.entities.X undefined` | Add entity to `entities: [...]` in main.wasp | +| Types not updating | Restart Wasp server, then TS server | +| Schema changes not applied | `wasp db migrate-dev --name change-name` | +| General weirdness | `wasp clean && wasp start` | + +## Resources + +**Help:** https://discord.gg/aCamt5wCpS (#🙋questions channel) + +**MCP Tools:** Use `mcp__wasp-docs` to search Wasp/OpenSaaS docs + +**Documentation:** +- https://wasp.sh/llms.txt +- https://docs.opensaas.sh/llms.txt