From 2acd3e86ed947f0cfe5b3f7d7004769df677d07d Mon Sep 17 00:00:00 2001 From: Ahmad Alisha Date: Wed, 17 Jun 2026 12:52:23 +0530 Subject: [PATCH 1/8] feat: add RevenueCat integration templates and modular scaffolding support for native apps --- apps/cli/src/helpers/addons/mcp-setup.ts | 9 + .../cli/src/helpers/core/post-installation.ts | 26 + apps/cli/src/prompts/payments.ts | 18 + apps/cli/src/utils/compatibility-rules.ts | 9 + apps/cli/test/payments.test.ts | 48 + apps/web/src/lib/constant.ts | 2750 +++++++++-------- .../src/processors/env-vars.ts | 46 + .../src/processors/payments-deps.ts | 23 + .../src/template-handlers/payments.ts | 40 +- .../template-generator/src/utils/add-deps.ts | 4 + .../convex/backend/convex/http.ts.hbs | 12 +- .../native/bare/app/(drawer)/index.tsx.hbs | 11 +- .../unistyles/app/(drawer)/index.tsx.hbs | 9 + .../native/uniwind/app/(drawer)/index.tsx.hbs | 11 + .../backend/convex/convex.config.ts.hbs | 8 +- .../native/bare/app/(drawer)/index.tsx.hbs | 10 +- .../frontend/native/bare/app/_layout.tsx.hbs | 285 +- .../unistyles/app/(drawer)/index.tsx.hbs | 9 + .../native/unistyles/app/_layout.tsx.hbs | 10 + .../native/uniwind/app/(drawer)/index.tsx.hbs | 31 +- .../native/uniwind/app/_layout.tsx.hbs | 10 + .../convex/backend/convex/revenuecat.ts.hbs | 9 + .../convex/no-better-auth/convex/http.ts.hbs | 9 + .../bare/components/paywall-example.tsx.hbs | 129 + .../subscription-status-card.tsx.hbs | 46 + .../base/contexts/revenuecat-context.tsx.hbs | 286 ++ .../native/base/lib/revenuecat.ts.hbs | 197 ++ .../components/paywall-example.tsx.hbs | 128 + .../subscription-status-card.tsx.hbs | 46 + .../components/paywall-example.tsx.hbs | 91 + .../subscription-status-card.tsx.hbs | 25 + packages/types/src/compatibility.ts | 9 + packages/types/src/schemas.ts | 2 +- 33 files changed, 2829 insertions(+), 1527 deletions(-) create mode 100644 packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/bare/components/subscription-status-card.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/base/contexts/revenuecat-context.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/base/lib/revenuecat.ts.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/unistyles/components/paywall-example.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/unistyles/components/subscription-status-card.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/uniwind/components/paywall-example.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs diff --git a/apps/cli/src/helpers/addons/mcp-setup.ts b/apps/cli/src/helpers/addons/mcp-setup.ts index f3140134e..727d712f9 100644 --- a/apps/cli/src/helpers/addons/mcp-setup.ts +++ b/apps/cli/src/helpers/addons/mcp-setup.ts @@ -214,6 +214,15 @@ function getRecommendedMcpServers(config: ProjectConfig): McpServerDef[] { }); } + if (config.payments === "revenuecat") { + servers.push({ + key: "revenuecat", + label: "RevenueCat", + name: "revenuecat", + target: "https://mcp.revenuecat.ai/mcp", + }); + } + return servers; } diff --git a/apps/cli/src/helpers/core/post-installation.ts b/apps/cli/src/helpers/core/post-installation.ts index 04c1242ae..a30fd41f6 100644 --- a/apps/cli/src/helpers/core/post-installation.ts +++ b/apps/cli/src/helpers/core/post-installation.ts @@ -138,6 +138,8 @@ export async function displayPostInstallInstructions( ); const polarInstructions = config.payments === "polar" ? getPolarInstructions(backend, packageManager) : ""; + const revenueCatInstructions = + config.payments === "revenuecat" ? getRevenueCatInstructions(backend, packageManager) : ""; const paymentSetupInstructions = getPaymentSetupInstructions(config.payments, backend); const alchemyDeployInstructions = getAlchemyDeployInstructions( runCmd, @@ -283,6 +285,7 @@ export async function displayPostInstallInstructions( if (authSetupInstructions) output += `\n${authSetupInstructions.trim()}\n`; if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`; if (polarInstructions) output += `\n${polarInstructions.trim()}\n`; + if (revenueCatInstructions) output += `\n${revenueCatInstructions.trim()}\n`; if (paymentSetupInstructions) output += `\n${paymentSetupInstructions.trim()}\n`; if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`; @@ -689,6 +692,29 @@ function getPolarInstructions(backend: Backend, packageManager: string) { return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`; } +function getRevenueCatInstructions(backend: Backend, packageManager: string) { + const base = + `${pc.bold("RevenueCat Payments Setup:")}\n` + + `${pc.cyan("•")} Create a project, entitlement, and offering in ${pc.underline("https://app.revenuecat.com/")}\n` + + `${pc.cyan("•")} Set the public SDK keys in ${pc.white("apps/native/.env")}:\n` + + `${pc.white(" EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_your_ios_key")}\n` + + `${pc.white(" EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=goog_your_android_key")}\n` + + `${pc.white(" EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID=pro")}`; + + if (backend === "convex") { + const cmd = packageManager === "npm" ? "npx" : packageManager; + return ( + `${base}\n` + + `${pc.cyan("•")} Set the webhook secret (min 32 chars) from ${pc.white("packages/backend")}:\n` + + `${pc.white(" cd packages/backend")}\n` + + `${pc.white(` ${cmd} convex env set REVENUECAT_WEBHOOK_AUTH your_webhook_secret`)}\n` + + `${pc.cyan("•")} Configure a RevenueCat webhook to ${pc.white("https:///webhooks/revenuecat")} using the same value as the Authorization header` + ); + } + + return base; +} + function getAlchemyDeployInstructions( runCmd: string, webDeploy: WebDeploy, diff --git a/apps/cli/src/prompts/payments.ts b/apps/cli/src/prompts/payments.ts index 10ecaf60a..cfe3a852b 100644 --- a/apps/cli/src/prompts/payments.ts +++ b/apps/cli/src/prompts/payments.ts @@ -38,6 +38,15 @@ export function resolvePaymentsPrompt( (context.auth === "better-auth" || context.auth === "better-auth-organizations") && (context.frontends?.length === 0 || splitFrontends(context.frontends).web.length > 0); + const hasNativeFrontend = (context.frontends ?? []).some( + (frontend) => + frontend === "native-bare" || + frontend === "native-uniwind" || + frontend === "native-unistyles", + ); + + const isRevenueCatCompatible = hasNativeFrontend; + const options: Array<{ value: Payments; label: string; hint: string }> = []; if (isPolarCompatible) { @@ -76,6 +85,15 @@ export function resolvePaymentsPrompt( }, ); + if (isRevenueCatCompatible) { + options.push({ + value: "revenuecat" as Payments, + label: "RevenueCat", + hint: "In-app subscriptions and cross-platform monetization for mobile.", + }); + } + + return { shouldPrompt: true, mode: "single", diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index de3035fc3..bcb302731 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -618,6 +618,15 @@ export function validatePaymentsCompatibility( ); } } + + if (payments === "revenuecat") { + const { native } = splitFrontends(frontends); + if (native.length === 0) { + exitWithError( + "RevenueCat payments requires a native frontend. Please select a native frontend or choose a different payments provider.", + ); + } + } } export function validateExamplesCompatibility( diff --git a/apps/cli/test/payments.test.ts b/apps/cli/test/payments.test.ts index ab993ed0c..2342a1f47 100644 --- a/apps/cli/test/payments.test.ts +++ b/apps/cli/test/payments.test.ts @@ -686,6 +686,54 @@ describe("Payments Options", () => { }); }); + describe("RevenueCat payments", () => { + test("revenuecat with native-bare frontend", async () => { + const result = await runTRPCTest( + createCustomConfig({ + projectName: "revenuecat-native-bare", + frontend: ["native-bare"], + backend: "hono", + api: "none", + payments: "revenuecat", + }), + ); + expectSuccess(result); + }); + + test("revenuecat with native-unistyles frontend", async () => { + const result = await runTRPCTest( + createCustomConfig({ + projectName: "revenuecat-native-unistyles", + frontend: ["native-unistyles"], + backend: "convex", + runtime: "none", + database: "none", + orm: "none", + api: "none", + payments: "revenuecat", + }), + ); + expectSuccess(result); + }); + + test("revenuecat with native-uniwind and better-auth (convex combined webhook)", async () => { + const result = await runTRPCTest( + createCustomConfig({ + projectName: "revenuecat-uniwind-better-auth", + frontend: ["native-uniwind"], + backend: "convex", + runtime: "none", + database: "none", + orm: "none", + api: "none", + auth: "better-auth", + payments: "revenuecat", + }), + ); + expectSuccess(result); + }); + }); + describe("No payments option", () => { test("none payments option", async () => { const result = await runTRPCTest( diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts index da1844d91..c5e06c424 100644 --- a/apps/web/src/lib/constant.ts +++ b/apps/web/src/lib/constant.ts @@ -973,6 +973,14 @@ export const TECH_OPTIONS: Record< color: "from-indigo-500 to-blue-600", default: false, }, + { + id: "revenuecat", + name: "RevenueCat", + description: "In-app subscriptions and cross-platform monetization for mobile.", + icon: "https://cdn.simpleicons.org/revenuecat/F2545B", + color: "from-red-400 to-red-600", + default: false, + }, { id: "none", name: "No Payments", @@ -5246,63 +5254,63 @@ export const ECOSYSTEMS: { icon: string; color: string; }[] = [ - { - id: "typescript", - name: "TypeScript", - description: "Full-stack TypeScript web ecosystem", - icon: "https://cdn.simpleicons.org/typescript/3178C6", - color: "from-blue-500 to-blue-700", - }, - { - id: "react-native", - name: "React Native", - description: "Expo and React Native mobile ecosystem", - icon: "https://cdn.simpleicons.org/react/61DAFB", - color: "from-cyan-500 to-blue-700", - }, - { - id: "rust", - name: "Rust", - description: "High-performance Rust ecosystem", - icon: "", - color: "from-orange-500 to-orange-700", - }, - { - id: "python", - name: "Python", - description: "Python full-stack ecosystem", - icon: "https://cdn.simpleicons.org/python/3776AB", - color: "from-blue-400 to-yellow-500", - }, - { - id: "go", - name: "Go", - description: "High-performance Go ecosystem", - icon: "https://cdn.simpleicons.org/go/00ADD8", - color: "from-cyan-500 to-blue-600", - }, - { - id: "java", - name: "Java", - description: "Modern Java ecosystem", - icon: "/icon/java.svg", - color: "from-red-500 to-orange-600", - }, - { - id: "elixir", - name: "Elixir", - description: "Phoenix full-stack ecosystem", - icon: "https://cdn.simpleicons.org/elixir/4B275F", - color: "from-purple-500 to-fuchsia-600", - }, - { - id: "dotnet", - name: ".NET", - description: "ASP.NET Core ecosystem", - icon: "https://cdn.simpleicons.org/dotnet/512BD4", - color: "from-violet-500 to-blue-700", - }, -]; + { + id: "typescript", + name: "TypeScript", + description: "Full-stack TypeScript web ecosystem", + icon: "https://cdn.simpleicons.org/typescript/3178C6", + color: "from-blue-500 to-blue-700", + }, + { + id: "react-native", + name: "React Native", + description: "Expo and React Native mobile ecosystem", + icon: "https://cdn.simpleicons.org/react/61DAFB", + color: "from-cyan-500 to-blue-700", + }, + { + id: "rust", + name: "Rust", + description: "High-performance Rust ecosystem", + icon: "", + color: "from-orange-500 to-orange-700", + }, + { + id: "python", + name: "Python", + description: "Python full-stack ecosystem", + icon: "https://cdn.simpleicons.org/python/3776AB", + color: "from-blue-400 to-yellow-500", + }, + { + id: "go", + name: "Go", + description: "High-performance Go ecosystem", + icon: "https://cdn.simpleicons.org/go/00ADD8", + color: "from-cyan-500 to-blue-600", + }, + { + id: "java", + name: "Java", + description: "Modern Java ecosystem", + icon: "/icon/java.svg", + color: "from-red-500 to-orange-600", + }, + { + id: "elixir", + name: "Elixir", + description: "Phoenix full-stack ecosystem", + icon: "https://cdn.simpleicons.org/elixir/4B275F", + color: "from-purple-500 to-fuchsia-600", + }, + { + id: "dotnet", + name: ".NET", + description: "ASP.NET Core ecosystem", + icon: "https://cdn.simpleicons.org/dotnet/512BD4", + color: "from-violet-500 to-blue-700", + }, + ]; // Categories available for each ecosystem export const ECOSYSTEM_CATEGORIES = Object.fromEntries( @@ -5341,1320 +5349,1320 @@ export const PRESET_TEMPLATES: { category: PresetCategory; stack: Partial; }[] = [ - { - id: "t3", - name: "T3 Stack", - description: "Next.js + tRPC + Prisma + PostgreSQL + Better Auth", - category: "next", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "postgres", - orm: "prisma", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome"], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nextjs-saas", - name: "Next.js SaaS", - description: "Next.js + Stripe + Drizzle + PostgreSQL + Auth + React Email", - category: "next", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "stripe", - email: "react-email", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome"], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nextjs-minimal", - name: "Next.js Minimal", - description: "Next.js + Tailwind + shadcn/ui — no database or backend", - category: "next", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "mern", - name: "MERN Stack", - description: "MongoDB + Express + React Router + Node.js", - category: "react", - stack: { - projectName: "my-app", - webFrontend: ["react-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "node", - backend: "express", - database: "mongodb", - orm: "mongoose", - dbSetup: "mongodb-atlas", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "pern", - name: "PERN Stack", - description: "PostgreSQL + Express + TanStack Router + Node.js", - category: "react", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "node", - backend: "express", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "react-hono", - name: "React + Hono", - description: "React Router + Hono + Drizzle + SQLite + oRPC", - category: "react", - stack: { - projectName: "my-app", - webFrontend: ["react-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "tanstack-start", - name: "TanStack Start", - description: "TanStack Start fullstack + tRPC + Drizzle + SQLite", - category: "tanstack", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-start"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-tanstack-start", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "tanstack-hono", - name: "TanStack + Hono", - description: "TanStack Router + Hono + tRPC + Drizzle + PostgreSQL", - category: "tanstack", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "bun", - backend: "hono", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "sveltekit-fullstack", - name: "SvelteKit Fullstack", - description: "SvelteKit + oRPC + Drizzle + SQLite + Better Auth", - category: "svelte", - stack: { - projectName: "my-app", - webFrontend: ["svelte"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "self-svelte", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "sveltekit-hono", - name: "SvelteKit + Hono", - description: "SvelteKit + Hono + oRPC + Drizzle + PostgreSQL", - category: "svelte", - stack: { - projectName: "my-app", - webFrontend: ["svelte"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "bun", - backend: "hono", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nuxt-fullstack", - name: "Nuxt Fullstack", - description: "Nuxt + oRPC + Prisma + PostgreSQL + Better Auth", - category: "nuxt", - stack: { - projectName: "my-app", - webFrontend: ["nuxt"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "self-nuxt", - database: "postgres", - orm: "prisma", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nuxt-hono", - name: "Nuxt + Hono", - description: "Nuxt + Hono + oRPC + Drizzle + SQLite", - category: "nuxt", - stack: { - projectName: "my-app", - webFrontend: ["nuxt"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "astro-react", - name: "Astro + React", - description: "Astro with React integration + Hono + tRPC + Drizzle", - category: "astro", - stack: { - projectName: "my-app", - webFrontend: ["astro"], - nativeFrontend: ["none"], - astroIntegration: "react", - cssFramework: "tailwind", - uiLibrary: "daisyui", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "astro-fullstack", - name: "Fullstack Astro", - description: "Astro with React using built-in API routes (fullstack mode)", - category: "astro", - stack: { - projectName: "my-app", - webFrontend: ["astro"], - nativeFrontend: ["none"], - astroIntegration: "react", - cssFramework: "tailwind", - uiLibrary: "daisyui", - runtime: "none", - backend: "self-astro", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "tanstack-fullstack", - name: "TanStack Fullstack", - description: - "Full TanStack ecosystem: Start + oRPC + Query + Store + Form + Table + Virtual + Pacer + AI (with Showcase)", - category: "tanstack", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-start"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-tanstack-start", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "tanstack-store", - forms: "tanstack-form", - codeQuality: ["biome"], - documentation: [], - appPlatforms: [ - "turborepo", - "tanstack-table", - "tanstack-virtual", - "tanstack-query", - "tanstack-pacer", - ], - packageManager: "bun", - examples: ["tanstack-showcase"], - git: "true", - install: "true", - api: "orpc", - aiSdk: "tanstack-ai", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "solidstart-fullstack", - name: "SolidStart Fullstack", - description: "SolidStart + oRPC + Drizzle + SQLite + Better Auth", - category: "solid", - stack: { - projectName: "my-app", - webFrontend: ["solid-start"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "self-solid-start", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "solid-hono", - name: "Solid + Hono", - description: "SolidJS SPA + Hono + oRPC + Drizzle + PostgreSQL", - category: "solid", - stack: { - projectName: "my-app", - webFrontend: ["solid"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "bun", - backend: "hono", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "angular-express", - name: "Angular + Express", - description: "Angular + Express + Prisma + PostgreSQL", - category: "angular", - stack: { - projectName: "my-app", - webFrontend: ["angular"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "node", - backend: "express", - database: "postgres", - orm: "prisma", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "qwik-city", - name: "Qwik City", - description: "Qwik with built-in server + Tailwind", - category: "qwik", - stack: { - projectName: "my-app", - webFrontend: ["qwik"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "uniwind", - name: "Uniwind Native", - description: "Expo + Uniwind native app with no backend", - category: "mobile", - stack: { - ecosystem: "react-native", - projectName: "my-app", - webFrontend: ["none"], - nativeFrontend: ["native-uniwind"], - astroIntegration: "none", - cssFramework: "none", - uiLibrary: "none", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: [], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "expo-bare", - name: "Expo Bare", - description: "Expo with bare workflow — no backend", - category: "mobile", - stack: { - ecosystem: "react-native", - projectName: "my-app", - webFrontend: ["none"], - nativeFrontend: ["native-bare"], - astroIntegration: "none", - cssFramework: "none", - uiLibrary: "none", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: [], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "ai-cli-agent-workbench", - name: "AI CLI Agent Workbench", - description: "Next.js + AI CLI + MCP + Skills + Ruler + complete AI docs", - category: "ai-tools", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome", "ruler"], - documentation: [], - appPlatforms: ["turborepo", "mcp", "skills"], - packageManager: "bun", - examples: [], - aiSdk: "ai-cli", - aiDocs: ["claude-md", "agents-md", "cursorrules"], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "ai-cli-react-hono", - name: "AI CLI React API", - description: "React Router + Hono + AI CLI command presets + agent tooling", - category: "ai-tools", - stack: { - projectName: "my-app", - webFrontend: ["react-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome", "ruler"], - documentation: [], - appPlatforms: ["turborepo", "mcp", "skills"], - packageManager: "bun", - examples: [], - aiSdk: "ai-cli", - aiDocs: ["claude-md", "agents-md", "cursorrules"], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "ai-cli-frontend-lab", - name: "AI CLI Frontend Lab", - description: "React + Vite frontend-only stack with AI CLI asset generation", - category: "ai-tools", - stack: { - projectName: "my-app", - webFrontend: ["react-vite"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome", "ruler"], - documentation: [], - appPlatforms: ["turborepo", "mcp", "skills"], - packageManager: "bun", - examples: [], - aiSdk: "ai-cli", - aiDocs: ["claude-md", "agents-md", "cursorrules"], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - // ── Rust ────────────────────────────────────────────── - { - id: "rust-fullstack", - name: "Rust Fullstack", - description: "Axum + Leptos + SeaORM — full-stack Rust with SSR", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "axum", - rustFrontend: "leptos", - rustOrm: "sea-orm", - rustApi: "none", - rustCli: "none", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "rust-api", - name: "Rust API", - description: "Axum + SeaORM — backend REST API, no frontend", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "axum", - rustFrontend: "none", - rustOrm: "sea-orm", - rustApi: "none", - rustCli: "none", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "rust-grpc", - name: "Rust gRPC", - description: "Axum + Tonic + SQLx — gRPC microservice", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "axum", - rustFrontend: "none", - rustOrm: "sqlx", - rustApi: "tonic", - rustCli: "none", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "rust-cli", - name: "Rust CLI", - description: "Clap + Ratatui — terminal UI application", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "none", - rustFrontend: "none", - rustOrm: "none", - rustApi: "none", - rustCli: "ratatui", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Python ─────────────────────────────────────────── - { - id: "python-fastapi", - name: "FastAPI", - description: "FastAPI + SQLAlchemy + Pydantic + Ruff", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "fastapi", - pythonOrm: "sqlalchemy", - pythonValidation: "pydantic", - pythonAi: [], - pythonAuth: "none", - pythonApi: "none", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "python-django", - name: "Django", - description: "Django + SQLAlchemy + Pydantic + Ruff", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "django", - pythonOrm: "sqlalchemy", - pythonValidation: "pydantic", - pythonAi: [], - pythonAuth: "none", - pythonApi: "django-rest-framework", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "python-ai-agent", - name: "AI Agent", - description: "FastAPI + LangChain + Pydantic — AI-powered API", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "fastapi", - pythonOrm: "sqlmodel", - pythonValidation: "pydantic", - pythonAi: ["langchain"], - pythonAuth: "none", - pythonApi: "none", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "python-ai-anthropic", - name: "AI Agent (Anthropic)", - description: "FastAPI + Anthropic SDK + Pydantic — Claude-powered API", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "fastapi", - pythonOrm: "sqlmodel", - pythonValidation: "pydantic", - pythonAi: ["anthropic-sdk"], - pythonAuth: "none", - pythonApi: "none", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Go ─────────────────────────────────────────────── - { - id: "go-gin", - name: "Go API (Gin)", - description: "Gin + GORM + Zap — REST API with structured logging", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "gin", - goOrm: "gorm", - goApi: "none", - goCli: "none", - goLogging: "zap", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "go-echo", - name: "Go API (Echo)", - description: "Echo + SQLC + Zap — type-safe SQL with Echo", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "echo", - goOrm: "sqlc", - goApi: "none", - goCli: "none", - goLogging: "zap", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "go-grpc", - name: "Go gRPC", - description: "Gin + gRPC-Go + GORM + Zap — gRPC microservice", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "gin", - goOrm: "gorm", - goApi: "grpc-go", - goCli: "none", - goLogging: "zap", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "go-cli", - name: "Go CLI", - description: "Cobra + Bubbletea — interactive terminal application", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "none", - goOrm: "none", - goApi: "none", - goCli: "bubbletea", - goLogging: "none", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Java ───────────────────────────────────────────── - { - id: "java-spring", - name: "Spring Boot API", - description: "Spring Boot + Maven + JUnit 5", - category: "java", - stack: { - ecosystem: "java", - projectName: "my-app", - javaWebFramework: "spring-boot", - javaBuildTool: "maven", - javaOrm: "none", - javaAuth: "none", - javaLibraries: [], - javaTestingLibraries: ["junit5"], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "java-jpa", - name: "Spring Data JPA", - description: "Spring Boot + Spring Data JPA + JUnit 5", - category: "java", - stack: { - ecosystem: "java", - projectName: "my-app", - javaWebFramework: "spring-boot", - javaBuildTool: "maven", - javaOrm: "spring-data-jpa", - javaAuth: "none", - javaLibraries: ["flyway", "spring-validation"], - javaTestingLibraries: ["junit5", "mockito"], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "java-secure", - name: "Secure Spring Boot", - description: "Spring Boot + Spring Security + JPA + Testcontainers", - category: "java", - stack: { - ecosystem: "java", - projectName: "my-app", - javaWebFramework: "spring-boot", - javaBuildTool: "gradle", - javaOrm: "spring-data-jpa", - javaAuth: "spring-security", - javaLibraries: ["spring-actuator", "flyway"], - javaTestingLibraries: ["junit5", "testcontainers"], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── .NET ───────────────────────────────────────────── - { - id: "dotnet-minimal-api", - name: ".NET Minimal API", - description: "ASP.NET Core Minimal APIs + EF Core + SignalR", - category: "dotnet", - stack: { - ecosystem: "dotnet", - projectName: "my-app", - dotnetWebFramework: "aspnet-minimal", - dotnetOrm: "ef-core", - dotnetAuth: "aspnet-identity", - dotnetApi: "minimal-api", - dotnetTesting: ["xunit"], - dotnetJobQueue: "none", - dotnetRealtime: "signalr", - dotnetObservability: ["serilog"], - dotnetCaching: "none", - dotnetDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "dotnet-graphql", - name: ".NET GraphQL", - description: "ASP.NET Core + Hot Chocolate + EF Core", - category: "dotnet", - stack: { - ecosystem: "dotnet", - projectName: "my-app", - dotnetWebFramework: "aspnet-minimal", - dotnetOrm: "ef-core", - dotnetAuth: "none", - dotnetApi: "graphql-hotchocolate", - dotnetTesting: ["xunit", "testcontainers-dotnet"], - dotnetJobQueue: "none", - dotnetRealtime: "none", - dotnetObservability: ["serilog", "health-checks"], - dotnetCaching: "none", - dotnetDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "dotnet-worker", - name: ".NET Worker", - description: "ASP.NET Core hosted services + Quartz.NET", - category: "dotnet", - stack: { - ecosystem: "dotnet", - projectName: "my-app", - dotnetWebFramework: "aspnet-minimal", - dotnetOrm: "none", - dotnetAuth: "none", - dotnetApi: "none", - dotnetTesting: ["xunit"], - dotnetJobQueue: "quartz-net", - dotnetRealtime: "none", - dotnetObservability: ["serilog", "opentelemetry-dotnet"], - dotnetCaching: "memory-cache", - dotnetDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Elixir ────────────────────────────────────────── - { - id: "elixir-phoenix-api", - name: "Phoenix API", - description: "Phoenix + Ecto SQL + REST + Channels", - category: "elixir", - stack: { - ecosystem: "elixir", - projectName: "my-app", - elixirWebFramework: "phoenix", - elixirOrm: "ecto-sql", - elixirAuth: "none", - elixirApi: "rest", - elixirRealtime: "channels", - elixirJobs: "none", - elixirValidation: "ecto-changesets", - elixirHttp: "req", - elixirJson: "jason", - elixirEmail: "none", - elixirCaching: "none", - elixirObservability: "telemetry", - elixirTesting: "ex_unit", - elixirQuality: "credo", - elixirDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "elixir-liveview-full", - name: "LiveView Fullstack", - description: "Phoenix LiveView + Ecto + Auth + Oban", - category: "elixir", - stack: { - ecosystem: "elixir", - projectName: "my-app", - elixirWebFramework: "phoenix-live-view", - elixirOrm: "ecto-sql", - elixirAuth: "phx-gen-auth", - elixirApi: "absinthe", - elixirRealtime: "presence", - elixirJobs: "oban", - elixirValidation: "ecto-changesets", - elixirHttp: "req", - elixirJson: "jason", - elixirEmail: "swoosh", - elixirCaching: "cachex", - elixirObservability: "telemetry", - elixirTesting: "ex_unit", - elixirQuality: "sobelow", - elixirDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "elixir-plain-worker", - name: "Plain Elixir Worker", - description: "Mix app + Quantum + Req — no Phoenix", - category: "elixir", - stack: { - ecosystem: "elixir", - projectName: "my-app", - elixirWebFramework: "none", - elixirOrm: "none", - elixirAuth: "none", - elixirApi: "none", - elixirRealtime: "none", - elixirJobs: "quantum", - elixirValidation: "none", - elixirHttp: "req", - elixirJson: "jason", - elixirEmail: "none", - elixirCaching: "cachex", - elixirObservability: "none", - elixirTesting: "ex_unit", - elixirQuality: "credo", - elixirDeploy: "mix-release", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, -]; + { + id: "t3", + name: "T3 Stack", + description: "Next.js + tRPC + Prisma + PostgreSQL + Better Auth", + category: "next", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "postgres", + orm: "prisma", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome"], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nextjs-saas", + name: "Next.js SaaS", + description: "Next.js + Stripe + Drizzle + PostgreSQL + Auth + React Email", + category: "next", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "stripe", + email: "react-email", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome"], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nextjs-minimal", + name: "Next.js Minimal", + description: "Next.js + Tailwind + shadcn/ui — no database or backend", + category: "next", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "mern", + name: "MERN Stack", + description: "MongoDB + Express + React Router + Node.js", + category: "react", + stack: { + projectName: "my-app", + webFrontend: ["react-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "node", + backend: "express", + database: "mongodb", + orm: "mongoose", + dbSetup: "mongodb-atlas", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "pern", + name: "PERN Stack", + description: "PostgreSQL + Express + TanStack Router + Node.js", + category: "react", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "node", + backend: "express", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "react-hono", + name: "React + Hono", + description: "React Router + Hono + Drizzle + SQLite + oRPC", + category: "react", + stack: { + projectName: "my-app", + webFrontend: ["react-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "tanstack-start", + name: "TanStack Start", + description: "TanStack Start fullstack + tRPC + Drizzle + SQLite", + category: "tanstack", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-start"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-tanstack-start", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "tanstack-hono", + name: "TanStack + Hono", + description: "TanStack Router + Hono + tRPC + Drizzle + PostgreSQL", + category: "tanstack", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "bun", + backend: "hono", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "sveltekit-fullstack", + name: "SvelteKit Fullstack", + description: "SvelteKit + oRPC + Drizzle + SQLite + Better Auth", + category: "svelte", + stack: { + projectName: "my-app", + webFrontend: ["svelte"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "self-svelte", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "sveltekit-hono", + name: "SvelteKit + Hono", + description: "SvelteKit + Hono + oRPC + Drizzle + PostgreSQL", + category: "svelte", + stack: { + projectName: "my-app", + webFrontend: ["svelte"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "bun", + backend: "hono", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nuxt-fullstack", + name: "Nuxt Fullstack", + description: "Nuxt + oRPC + Prisma + PostgreSQL + Better Auth", + category: "nuxt", + stack: { + projectName: "my-app", + webFrontend: ["nuxt"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "self-nuxt", + database: "postgres", + orm: "prisma", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nuxt-hono", + name: "Nuxt + Hono", + description: "Nuxt + Hono + oRPC + Drizzle + SQLite", + category: "nuxt", + stack: { + projectName: "my-app", + webFrontend: ["nuxt"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "astro-react", + name: "Astro + React", + description: "Astro with React integration + Hono + tRPC + Drizzle", + category: "astro", + stack: { + projectName: "my-app", + webFrontend: ["astro"], + nativeFrontend: ["none"], + astroIntegration: "react", + cssFramework: "tailwind", + uiLibrary: "daisyui", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "astro-fullstack", + name: "Fullstack Astro", + description: "Astro with React using built-in API routes (fullstack mode)", + category: "astro", + stack: { + projectName: "my-app", + webFrontend: ["astro"], + nativeFrontend: ["none"], + astroIntegration: "react", + cssFramework: "tailwind", + uiLibrary: "daisyui", + runtime: "none", + backend: "self-astro", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "tanstack-fullstack", + name: "TanStack Fullstack", + description: + "Full TanStack ecosystem: Start + oRPC + Query + Store + Form + Table + Virtual + Pacer + AI (with Showcase)", + category: "tanstack", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-start"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-tanstack-start", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "tanstack-store", + forms: "tanstack-form", + codeQuality: ["biome"], + documentation: [], + appPlatforms: [ + "turborepo", + "tanstack-table", + "tanstack-virtual", + "tanstack-query", + "tanstack-pacer", + ], + packageManager: "bun", + examples: ["tanstack-showcase"], + git: "true", + install: "true", + api: "orpc", + aiSdk: "tanstack-ai", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "solidstart-fullstack", + name: "SolidStart Fullstack", + description: "SolidStart + oRPC + Drizzle + SQLite + Better Auth", + category: "solid", + stack: { + projectName: "my-app", + webFrontend: ["solid-start"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "self-solid-start", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "solid-hono", + name: "Solid + Hono", + description: "SolidJS SPA + Hono + oRPC + Drizzle + PostgreSQL", + category: "solid", + stack: { + projectName: "my-app", + webFrontend: ["solid"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "bun", + backend: "hono", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "angular-express", + name: "Angular + Express", + description: "Angular + Express + Prisma + PostgreSQL", + category: "angular", + stack: { + projectName: "my-app", + webFrontend: ["angular"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "node", + backend: "express", + database: "postgres", + orm: "prisma", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "qwik-city", + name: "Qwik City", + description: "Qwik with built-in server + Tailwind", + category: "qwik", + stack: { + projectName: "my-app", + webFrontend: ["qwik"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "uniwind", + name: "Uniwind Native", + description: "Expo + Uniwind native app with no backend", + category: "mobile", + stack: { + ecosystem: "react-native", + projectName: "my-app", + webFrontend: ["none"], + nativeFrontend: ["native-uniwind"], + astroIntegration: "none", + cssFramework: "none", + uiLibrary: "none", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: [], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "expo-bare", + name: "Expo Bare", + description: "Expo with bare workflow — no backend", + category: "mobile", + stack: { + ecosystem: "react-native", + projectName: "my-app", + webFrontend: ["none"], + nativeFrontend: ["native-bare"], + astroIntegration: "none", + cssFramework: "none", + uiLibrary: "none", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: [], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "ai-cli-agent-workbench", + name: "AI CLI Agent Workbench", + description: "Next.js + AI CLI + MCP + Skills + Ruler + complete AI docs", + category: "ai-tools", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome", "ruler"], + documentation: [], + appPlatforms: ["turborepo", "mcp", "skills"], + packageManager: "bun", + examples: [], + aiSdk: "ai-cli", + aiDocs: ["claude-md", "agents-md", "cursorrules"], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "ai-cli-react-hono", + name: "AI CLI React API", + description: "React Router + Hono + AI CLI command presets + agent tooling", + category: "ai-tools", + stack: { + projectName: "my-app", + webFrontend: ["react-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome", "ruler"], + documentation: [], + appPlatforms: ["turborepo", "mcp", "skills"], + packageManager: "bun", + examples: [], + aiSdk: "ai-cli", + aiDocs: ["claude-md", "agents-md", "cursorrules"], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "ai-cli-frontend-lab", + name: "AI CLI Frontend Lab", + description: "React + Vite frontend-only stack with AI CLI asset generation", + category: "ai-tools", + stack: { + projectName: "my-app", + webFrontend: ["react-vite"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome", "ruler"], + documentation: [], + appPlatforms: ["turborepo", "mcp", "skills"], + packageManager: "bun", + examples: [], + aiSdk: "ai-cli", + aiDocs: ["claude-md", "agents-md", "cursorrules"], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + // ── Rust ────────────────────────────────────────────── + { + id: "rust-fullstack", + name: "Rust Fullstack", + description: "Axum + Leptos + SeaORM — full-stack Rust with SSR", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "axum", + rustFrontend: "leptos", + rustOrm: "sea-orm", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "rust-api", + name: "Rust API", + description: "Axum + SeaORM — backend REST API, no frontend", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "axum", + rustFrontend: "none", + rustOrm: "sea-orm", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "rust-grpc", + name: "Rust gRPC", + description: "Axum + Tonic + SQLx — gRPC microservice", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "axum", + rustFrontend: "none", + rustOrm: "sqlx", + rustApi: "tonic", + rustCli: "none", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "rust-cli", + name: "Rust CLI", + description: "Clap + Ratatui — terminal UI application", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "none", + rustFrontend: "none", + rustOrm: "none", + rustApi: "none", + rustCli: "ratatui", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Python ─────────────────────────────────────────── + { + id: "python-fastapi", + name: "FastAPI", + description: "FastAPI + SQLAlchemy + Pydantic + Ruff", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "fastapi", + pythonOrm: "sqlalchemy", + pythonValidation: "pydantic", + pythonAi: [], + pythonAuth: "none", + pythonApi: "none", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "python-django", + name: "Django", + description: "Django + SQLAlchemy + Pydantic + Ruff", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "django", + pythonOrm: "sqlalchemy", + pythonValidation: "pydantic", + pythonAi: [], + pythonAuth: "none", + pythonApi: "django-rest-framework", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "python-ai-agent", + name: "AI Agent", + description: "FastAPI + LangChain + Pydantic — AI-powered API", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "fastapi", + pythonOrm: "sqlmodel", + pythonValidation: "pydantic", + pythonAi: ["langchain"], + pythonAuth: "none", + pythonApi: "none", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "python-ai-anthropic", + name: "AI Agent (Anthropic)", + description: "FastAPI + Anthropic SDK + Pydantic — Claude-powered API", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "fastapi", + pythonOrm: "sqlmodel", + pythonValidation: "pydantic", + pythonAi: ["anthropic-sdk"], + pythonAuth: "none", + pythonApi: "none", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Go ─────────────────────────────────────────────── + { + id: "go-gin", + name: "Go API (Gin)", + description: "Gin + GORM + Zap — REST API with structured logging", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "gin", + goOrm: "gorm", + goApi: "none", + goCli: "none", + goLogging: "zap", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "go-echo", + name: "Go API (Echo)", + description: "Echo + SQLC + Zap — type-safe SQL with Echo", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "echo", + goOrm: "sqlc", + goApi: "none", + goCli: "none", + goLogging: "zap", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "go-grpc", + name: "Go gRPC", + description: "Gin + gRPC-Go + GORM + Zap — gRPC microservice", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "gin", + goOrm: "gorm", + goApi: "grpc-go", + goCli: "none", + goLogging: "zap", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "go-cli", + name: "Go CLI", + description: "Cobra + Bubbletea — interactive terminal application", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "none", + goOrm: "none", + goApi: "none", + goCli: "bubbletea", + goLogging: "none", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Java ───────────────────────────────────────────── + { + id: "java-spring", + name: "Spring Boot API", + description: "Spring Boot + Maven + JUnit 5", + category: "java", + stack: { + ecosystem: "java", + projectName: "my-app", + javaWebFramework: "spring-boot", + javaBuildTool: "maven", + javaOrm: "none", + javaAuth: "none", + javaLibraries: [], + javaTestingLibraries: ["junit5"], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "java-jpa", + name: "Spring Data JPA", + description: "Spring Boot + Spring Data JPA + JUnit 5", + category: "java", + stack: { + ecosystem: "java", + projectName: "my-app", + javaWebFramework: "spring-boot", + javaBuildTool: "maven", + javaOrm: "spring-data-jpa", + javaAuth: "none", + javaLibraries: ["flyway", "spring-validation"], + javaTestingLibraries: ["junit5", "mockito"], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "java-secure", + name: "Secure Spring Boot", + description: "Spring Boot + Spring Security + JPA + Testcontainers", + category: "java", + stack: { + ecosystem: "java", + projectName: "my-app", + javaWebFramework: "spring-boot", + javaBuildTool: "gradle", + javaOrm: "spring-data-jpa", + javaAuth: "spring-security", + javaLibraries: ["spring-actuator", "flyway"], + javaTestingLibraries: ["junit5", "testcontainers"], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── .NET ───────────────────────────────────────────── + { + id: "dotnet-minimal-api", + name: ".NET Minimal API", + description: "ASP.NET Core Minimal APIs + EF Core + SignalR", + category: "dotnet", + stack: { + ecosystem: "dotnet", + projectName: "my-app", + dotnetWebFramework: "aspnet-minimal", + dotnetOrm: "ef-core", + dotnetAuth: "aspnet-identity", + dotnetApi: "minimal-api", + dotnetTesting: ["xunit"], + dotnetJobQueue: "none", + dotnetRealtime: "signalr", + dotnetObservability: ["serilog"], + dotnetCaching: "none", + dotnetDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "dotnet-graphql", + name: ".NET GraphQL", + description: "ASP.NET Core + Hot Chocolate + EF Core", + category: "dotnet", + stack: { + ecosystem: "dotnet", + projectName: "my-app", + dotnetWebFramework: "aspnet-minimal", + dotnetOrm: "ef-core", + dotnetAuth: "none", + dotnetApi: "graphql-hotchocolate", + dotnetTesting: ["xunit", "testcontainers-dotnet"], + dotnetJobQueue: "none", + dotnetRealtime: "none", + dotnetObservability: ["serilog", "health-checks"], + dotnetCaching: "none", + dotnetDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "dotnet-worker", + name: ".NET Worker", + description: "ASP.NET Core hosted services + Quartz.NET", + category: "dotnet", + stack: { + ecosystem: "dotnet", + projectName: "my-app", + dotnetWebFramework: "aspnet-minimal", + dotnetOrm: "none", + dotnetAuth: "none", + dotnetApi: "none", + dotnetTesting: ["xunit"], + dotnetJobQueue: "quartz-net", + dotnetRealtime: "none", + dotnetObservability: ["serilog", "opentelemetry-dotnet"], + dotnetCaching: "memory-cache", + dotnetDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Elixir ────────────────────────────────────────── + { + id: "elixir-phoenix-api", + name: "Phoenix API", + description: "Phoenix + Ecto SQL + REST + Channels", + category: "elixir", + stack: { + ecosystem: "elixir", + projectName: "my-app", + elixirWebFramework: "phoenix", + elixirOrm: "ecto-sql", + elixirAuth: "none", + elixirApi: "rest", + elixirRealtime: "channels", + elixirJobs: "none", + elixirValidation: "ecto-changesets", + elixirHttp: "req", + elixirJson: "jason", + elixirEmail: "none", + elixirCaching: "none", + elixirObservability: "telemetry", + elixirTesting: "ex_unit", + elixirQuality: "credo", + elixirDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "elixir-liveview-full", + name: "LiveView Fullstack", + description: "Phoenix LiveView + Ecto + Auth + Oban", + category: "elixir", + stack: { + ecosystem: "elixir", + projectName: "my-app", + elixirWebFramework: "phoenix-live-view", + elixirOrm: "ecto-sql", + elixirAuth: "phx-gen-auth", + elixirApi: "absinthe", + elixirRealtime: "presence", + elixirJobs: "oban", + elixirValidation: "ecto-changesets", + elixirHttp: "req", + elixirJson: "jason", + elixirEmail: "swoosh", + elixirCaching: "cachex", + elixirObservability: "telemetry", + elixirTesting: "ex_unit", + elixirQuality: "sobelow", + elixirDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "elixir-plain-worker", + name: "Plain Elixir Worker", + description: "Mix app + Quantum + Req — no Phoenix", + category: "elixir", + stack: { + ecosystem: "elixir", + projectName: "my-app", + elixirWebFramework: "none", + elixirOrm: "none", + elixirAuth: "none", + elixirApi: "none", + elixirRealtime: "none", + elixirJobs: "quantum", + elixirValidation: "none", + elixirHttp: "req", + elixirJson: "jason", + elixirEmail: "none", + elixirCaching: "cachex", + elixirObservability: "none", + elixirTesting: "ex_unit", + elixirQuality: "credo", + elixirDeploy: "mix-release", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + ]; export { DEFAULT_STACK, isStackDefault }; export type { StackState }; diff --git a/packages/template-generator/src/processors/env-vars.ts b/packages/template-generator/src/processors/env-vars.ts index 35d44735f..63ae0e1bf 100644 --- a/packages/template-generator/src/processors/env-vars.ts +++ b/packages/template-generator/src/processors/env-vars.ts @@ -525,6 +525,7 @@ function buildNativeVars( frontend: string[], backend: ProjectConfig["backend"], auth: ProjectConfig["auth"], + payments: ProjectConfig["payments"], mobilePush: ProjectConfig["mobilePush"], mobileDeepLinking: ProjectConfig["mobileDeepLinking"], ): EnvVariable[] { @@ -574,6 +575,26 @@ function buildNativeVars( }); } + if (payments === "revenuecat") { + vars.push( + { + key: "EXPO_PUBLIC_REVENUECAT_IOS_KEY", + value: "", + condition: true, + }, + { + key: "EXPO_PUBLIC_REVENUECAT_ANDROID_KEY", + value: "", + condition: true, + }, + { + key: "EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID", + value: "pro", + condition: true, + }, + ); + } + if (mobilePush === "expo-notifications") { vars.push({ key: "EXPO_PUBLIC_EAS_PROJECT_ID", @@ -677,6 +698,15 @@ function buildConvexBackendVars( ); } + if (payments === "revenuecat") { + vars.push({ + key: "REVENUECAT_WEBHOOK_AUTH", + value: "", + condition: true, + comment: "Shared secret for RevenueCat webhook authentication (min 32 characters)", + }); + } + return vars; } @@ -720,6 +750,15 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`; # Create a Polar webhook at https:///polar/events # Enable: product.created, product.updated, subscription.created, subscription.updated +`; + } + + if (payments === "revenuecat") { + commentBlocks += `# Set RevenueCat environment variables +# npx convex env set REVENUECAT_WEBHOOK_AUTH your_webhook_secret_min_32_chars +# Create a RevenueCat webhook at https:///webhooks/revenuecat +# Set the webhook Authorization header to the same REVENUECAT_WEBHOOK_AUTH value + `; } @@ -1047,6 +1086,12 @@ function buildServerVars( comment: "Dodo Payments environment - use 'test_mode' for testing, 'live_mode' for production", }, + { + key: "REVENUECAT_WEBHOOK_AUTH", + value: "", + condition: payments === "revenuecat", + comment: "Shared secret for RevenueCat webhook authentication (min 32 characters)", + }, { key: "RESEND_API_KEY", value: "", @@ -1894,6 +1939,7 @@ export function processEnvVariables(vfs: VirtualFileSystem, config: ProjectConfi frontend, backend, auth, + payments, config.mobilePush, config.mobileDeepLinking, ); diff --git a/packages/template-generator/src/processors/payments-deps.ts b/packages/template-generator/src/processors/payments-deps.ts index d90b4a15b..3cbbdec23 100644 --- a/packages/template-generator/src/processors/payments-deps.ts +++ b/packages/template-generator/src/processors/payments-deps.ts @@ -204,4 +204,27 @@ export function processPaymentsDeps(vfs: VirtualFileSystem, config: ProjectConfi } } } + + if (payments === "revenuecat") { + const nativePath = "apps/native/package.json"; + const hasNativeFrontend = frontend.some((f) => + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), + ); + + if (hasNativeFrontend && vfs.exists(nativePath)) { + addPackageDependency({ + vfs, + packagePath: nativePath, + dependencies: ["react-native-purchases"], + }); + } + + if (backend === "convex" && vfs.exists(backendPath)) { + addPackageDependency({ + vfs, + packagePath: backendPath, + dependencies: ["convex-revenuecat"], + }); + } + } } diff --git a/packages/template-generator/src/template-handlers/payments.ts b/packages/template-generator/src/template-handlers/payments.ts index 22d742c7d..cdccafe2d 100644 --- a/packages/template-generator/src/template-handlers/payments.ts +++ b/packages/template-generator/src/template-handlers/payments.ts @@ -18,6 +18,14 @@ export async function processPaymentsTemplates( const hasSvelteWeb = config.frontend.includes("svelte"); const hasSolidWeb = config.frontend.includes("solid"); + const nativeVariant = config.frontend.includes("native-bare") + ? "bare" + : config.frontend.includes("native-uniwind") + ? "uniwind" + : config.frontend.includes("native-unistyles") + ? "unistyles" + : null; + if (config.backend === "convex") { processTemplatesFromPrefix( vfs, @@ -26,6 +34,16 @@ export async function processPaymentsTemplates( "packages/backend", config, ); + + if (config.payments === "revenuecat" && config.auth !== "better-auth") { + processTemplatesFromPrefix( + vfs, + templates, + "payments/revenuecat/convex/no-better-auth", + "packages/backend", + config, + ); + } } else if (config.backend !== "none") { processTemplatesFromPrefix( vfs, @@ -36,12 +54,30 @@ export async function processPaymentsTemplates( ); } + if (nativeVariant) { + processTemplatesFromPrefix( + vfs, + templates, + `payments/${config.payments}/native/base`, + "apps/native", + config, + ); + processTemplatesFromPrefix( + vfs, + templates, + `payments/${config.payments}/native/${nativeVariant}`, + "apps/native", + config, + ); + } + + if (hasReactWeb) { const reactFramework = config.frontend.includes("react-vite") ? "react-router" : config.frontend.find((f) => - ["tanstack-router", "react-router", "tanstack-start", "next", "vinext"].includes(f), - ); + ["tanstack-router", "react-router", "tanstack-start", "next", "vinext"].includes(f), + ); if (reactFramework) { processTemplatesFromPrefix( vfs, diff --git a/packages/template-generator/src/utils/add-deps.ts b/packages/template-generator/src/utils/add-deps.ts index 26c6f46dd..5619353ed 100644 --- a/packages/template-generator/src/utils/add-deps.ts +++ b/packages/template-generator/src/utils/add-deps.ts @@ -605,6 +605,10 @@ export const dependencyVersionMap = { dodopayments: "^2.38.1", "dodopayments-checkout": "^1.9.4", + // Payments - RevenueCat + "react-native-purchases": "^9.10.5", + "convex-revenuecat": "^0.3.2", + // File Upload - UploadThing uploadthing: "^7.7.4", "@uploadthing/react": "^7.3.3", diff --git a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs index 5633f449a..f83322092 100644 --- a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs @@ -4,10 +4,14 @@ import { authComponent, createAuth } from "./auth"; {{#if (eq payments "polar")}} import { polar } from "./polar"; {{/if}} +{{#if (eq payments "revenuecat")}} +import { revenuecat } from "./revenuecat"; +{{/if}} const http = httpRouter(); -{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} +{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes +frontend "svelte") (includes frontend "solid"))}} authComponent.registerRoutes(http, createAuth, { cors: true }); {{else}} authComponent.registerRoutes(http, createAuth); @@ -16,5 +20,9 @@ authComponent.registerRoutes(http, createAuth); polar.registerRoutes(http as any); {{/if}} +{{#if (eq payments "revenuecat")}} + +revenuecat.registerRoutes(http); +{{/if}} -export default http; +export default http; \ No newline at end of file diff --git a/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs index 2aaffa17d..de84f8d12 100644 --- a/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs @@ -6,6 +6,10 @@ import { NAV_THEME } from "@/lib/constants"; import { authClient } from "@/lib/auth-client"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; @@ -105,6 +109,11 @@ return ( )} + + {{#if (eq payments "revenuecat")}} + + + {{/if}} @@ -185,4 +194,4 @@ privateDataText: { fontSize: 14, }, }); -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs index 5e324d965..7d2ed4e00 100644 --- a/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs @@ -6,6 +6,10 @@ import { StyleSheet } from "react-native-unistyles"; import { Container } from "@/components/container"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; @@ -95,6 +99,11 @@ export default function Home() { )} + + {{#if (eq payments "revenuecat")}} + + + {{/if}} diff --git a/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs index 45bfdc5f8..18760bd1b 100644 --- a/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs @@ -6,6 +6,10 @@ import { Ionicons } from "@expo/vector-icons"; import { Card, Chip, useThemeColor } from "heroui-native"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; @@ -119,6 +123,13 @@ return ( )} + + {{#if (eq payments "revenuecat")}} + + + + + {{/if}} ); } diff --git a/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs b/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs index a9e92b567..880144655 100644 --- a/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +++ b/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs @@ -5,6 +5,9 @@ import betterAuth from "@convex-dev/better-auth/convex.config"; {{#if (eq payments "polar")}} import polar from "@convex-dev/polar/convex.config"; {{/if}} +{{#if (eq payments "revenuecat")}} +import revenuecat from "convex-revenuecat/convex.config"; +{{/if}} {{#if (includes examples "ai")}} import agent from "@convex-dev/agent/convex.config"; {{/if}} @@ -16,8 +19,11 @@ app.use(betterAuth); {{#if (eq payments "polar")}} app.use(polar); {{/if}} +{{#if (eq payments "revenuecat")}} +app.use(revenuecat); +{{/if}} {{#if (includes examples "ai")}} app.use(agent); {{/if}} -export default app; +export default app; \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs index aa2c51456..c8a2258cb 100644 --- a/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs @@ -3,6 +3,10 @@ import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from "react-nati import { Container } from "@/components/container"; import { useColorScheme } from "@/lib/use-color-scheme"; import { NAV_THEME } from "@/lib/constants"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { orpc } from "@/utils/orpc"; @@ -157,6 +161,10 @@ return ( )} {{/if}} + {{#if (eq payments "revenuecat")}} + + + {{/if}} @@ -233,4 +241,4 @@ marginBottom: 8, fontWeight: "bold", }, }); -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs b/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs index 976fbf688..82ba82d70 100644 --- a/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs @@ -4,32 +4,32 @@ import "@/polyfills"; {{/if}} {{#if (eq backend "convex")}} - {{#if (isBetterAuth auth)}} - import { ConvexReactClient } from "convex/react"; - import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; - import { authClient } from "@/lib/auth-client"; - import { env } from "@{{projectName}}/env/native"; - {{else}} - import { ConvexProvider, ConvexReactClient } from "convex/react"; - import { env } from "@{{projectName}}/env/native"; - {{/if}} - {{#if (eq auth "clerk")}} - import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; - import { ConvexProviderWithClerk } from "convex/react-clerk"; - import { tokenCache } from "@clerk/clerk-expo/token-cache"; - {{/if}} +{{#if (isBetterAuth auth)}} +import { ConvexReactClient } from "convex/react"; +import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; +import { authClient } from "@/lib/auth-client"; +import { env } from "@{{projectName}}/env/native"; +{{else}} +import { ConvexProvider, ConvexReactClient } from "convex/react"; +import { env } from "@{{projectName}}/env/native"; +{{/if}} +{{#if (eq auth "clerk")}} +import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; +import { ConvexProviderWithClerk } from "convex/react-clerk"; +import { tokenCache } from "@clerk/clerk-expo/token-cache"; +{{/if}} {{else}} - {{#unless (eq api "none")}} - import { QueryClientProvider } from "@tanstack/react-query"; - {{/unless}} +{{#unless (eq api "none")}} +import { QueryClientProvider } from "@tanstack/react-query"; +{{/unless}} {{/if}} import { Stack } from "expo-router"; import { - DarkTheme, - DefaultTheme, - type Theme, - ThemeProvider, +DarkTheme, +DefaultTheme, +type Theme, +ThemeProvider, } from "@react-navigation/native"; import { StatusBar } from "expo-status-bar"; import { GestureHandlerRootView } from "react-native-gesture-handler"; @@ -50,146 +50,155 @@ import { MobileUIProvider } from "@/components/mobile-ui-provider"; {{#if (eq mobileOTA "expo-updates")}} import { useUpdateCheck } from "@/lib/updates"; {{/if}} +{{#if (eq payments "revenuecat")}} +import { RevenueCatProvider } from "@/contexts/revenuecat-context"; +{{/if}} const LIGHT_THEME: Theme = { - ...DefaultTheme, - colors: NAV_THEME.light, +...DefaultTheme, +colors: NAV_THEME.light, }; const DARK_THEME: Theme = { - ...DarkTheme, - colors: NAV_THEME.dark, +...DarkTheme, +colors: NAV_THEME.dark, }; export const unstable_settings = { - initialRouteName: "(drawer)", +initialRouteName: "(drawer)", }; {{#if (eq backend "convex")}} const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, { - unsavedChangesWarning: false, +unsavedChangesWarning: false, }); {{/if}} const useIsomorphicLayoutEffect = - Platform.OS === "web" && typeof window === "undefined" - ? React.useEffect - : React.useLayoutEffect; +Platform.OS === "web" && typeof window === "undefined" +? React.useEffect +: React.useLayoutEffect; const styles = StyleSheet.create({ - container: { - flex: 1, - }, +container: { +flex: 1, +}, }); export default function RootLayout() { - {{#if (eq mobileOTA "expo-updates")}} - useUpdateCheck(); - {{/if}} - React.useEffect(() => { - {{#if (eq mobilePush "expo-notifications")}} - if (Platform.OS !== "web") { - import("@/lib/notifications") - .then(({ registerForPushNotificationsAsync }) => registerForPushNotificationsAsync()) - .catch(console.warn); - } - {{/if}} - }, []); - const hasMounted = useRef(false); - const { colorScheme, isDarkColorScheme } = useColorScheme(); - const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false); +{{#if (eq mobileOTA "expo-updates")}} +useUpdateCheck(); +{{/if}} +React.useEffect(() => { +{{#if (eq mobilePush "expo-notifications")}} +if (Platform.OS !== "web") { +import("@/lib/notifications") +.then(({ registerForPushNotificationsAsync }) => registerForPushNotificationsAsync()) +.catch(console.warn); +} +{{/if}} +}, []); +const hasMounted = useRef(false); +const { colorScheme, isDarkColorScheme } = useColorScheme(); +const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false); - useIsomorphicLayoutEffect(() => { - if (hasMounted.current) { - return; - } - setAndroidNavigationBar(colorScheme); - setIsColorSchemeLoaded(true); - hasMounted.current = true; - }, []); +useIsomorphicLayoutEffect(() => { +if (hasMounted.current) { +return; +} +setAndroidNavigationBar(colorScheme); +setIsColorSchemeLoaded(true); +hasMounted.current = true; +}, []); - if (!isColorSchemeLoaded) { - return null; - } +if (!isColorSchemeLoaded) { +return null; +} - return ( - <> - {{#if (eq backend "convex")}} - {{#if (eq auth "clerk")}} - - - - - - - - - - - - - - - - - {{else if (isBetterAuth auth)}} - - - - - - - - - - - - - - {{else}} - - - - - - - - - - - - - - {{/if}} - {{else}} - {{#unless (eq api "none")}} - - - - - - - - - - - - - - {{else}} - - - - - +return ( +<> + {{#if (eq payments "revenuecat")}} + + {{/if}} + {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + + + + + + + - + + - - - - {{/unless}} - {{/if}} - - ); + + + + + + {{else if (isBetterAuth auth)}} + + + + + + + + + + + + + + {{else}} + + + + + + + + + + + + + + {{/if}} + {{else}} + {{#unless (eq api "none")}} + + + + + + + + + + + + + + {{else}} + + + + + + + + + + + + {{/unless}} + {{/if}} + {{#if (eq payments "revenuecat")}} + + {{/if}} + +); } -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs index 4975059e5..1c508f109 100644 --- a/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs @@ -23,6 +23,10 @@ import { api } from "@{{ projectName }}/backend/convex/_generated/api"; import { authClient } from "@/lib/auth-client"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{else if (eq backend "convex")}} import { useQuery } from "convex/react"; import { api } from "@{{ projectName }}/backend/convex/_generated/api"; @@ -187,6 +191,11 @@ export default function Home() { )} + + {{#if (eq payments "revenuecat")}} + + + {{/if}} {{/if}} diff --git a/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs b/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs index 985826316..92fed0335 100644 --- a/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs @@ -39,6 +39,10 @@ import { StatusBar } from "expo-status-bar"; {{#if (eq mobileOTA "expo-updates")}} import { useUpdateCheck } from "@/lib/updates"; {{/if}} +{{#if (eq payments "revenuecat")}} +import { RevenueCatProvider } from "@/contexts/revenuecat-context"; +{{/if}} + export const unstable_settings = { initialRouteName: "(drawer)", @@ -66,6 +70,9 @@ export default function RootLayout() { const { theme } = useUnistyles(); return ( + {{#if (eq payments "revenuecat")}} + + {{/if}} {{#if (eq backend "convex")}} {{#if (eq auth "clerk")}} {{/unless}} {{/if}} + {{#if (eq payments "revenuecat")}} + + {{/if}} ); } {{/if}} diff --git a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs index d00aa8f6d..b0716aafa 100644 --- a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs @@ -21,6 +21,10 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api"; import { authClient } from "@/lib/auth-client"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{else if (eq backend "convex")}} import { useQuery } from "convex/react"; import { api } from "@{{projectName}}/backend/convex/_generated/api"; @@ -106,7 +110,8 @@ return ( : "API Disconnected"} - {isLoading && } + {isLoading && + } {!isLoading && isConnected && ( )} @@ -133,10 +138,14 @@ return ( - + - + @@ -155,13 +164,10 @@ return ( {user.name} {user.email} - @@ -186,8 +192,13 @@ return ( )} + {{#if (eq payments "revenuecat")}} + + + + {{/if}} ); } -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs b/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs index fcf26198a..f09263cd6 100644 --- a/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs @@ -40,6 +40,10 @@ import { useUpdateCheck } from "@/lib/updates"; {{/if}} import { AppThemeProvider } from "@/contexts/app-theme-context"; +{{#if (eq payments "revenuecat")}} +import { RevenueCatProvider } from "@/contexts/revenuecat-context"; +{{/if}} + {{#if (eq api "trpc")}} import { queryClient } from "@/utils/trpc"; {{/if}} @@ -83,6 +87,9 @@ export default function Layout() { {{/if}} }, []); return ( + {{#if (eq payments "revenuecat")}} + + {{/if}} {{#if (eq backend "convex")}} {{#if (eq auth "clerk")}} @@ -148,6 +155,9 @@ export default function Layout() { {{/unless}} {{/if}} + {{#if (eq payments "revenuecat")}} + + {{/if}} ); } {{/if}} diff --git a/packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs b/packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs new file mode 100644 index 000000000..c564a03cd --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs @@ -0,0 +1,9 @@ +import { RevenueCat } from "convex-revenuecat"; + +import { components } from "./_generated/api"; + +export const revenuecat = new RevenueCat(components.revenuecat, { + REVENUECAT_WEBHOOK_AUTH: process.env.REVENUECAT_WEBHOOK_AUTH, +}); + +export const { hasEntitlement, isSubscriber, getActiveSubscriptions } = revenuecat.api() \ No newline at end of file diff --git a/packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs b/packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs new file mode 100644 index 000000000..00d748acc --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs @@ -0,0 +1,9 @@ +import { httpRouter } from "convex/server"; + +import { revenuecat } from "./revenuecat"; + +const http = httpRouter(); + +revenuecat.registerRoutes(http); + +export default http; \ No newline at end of file diff --git a/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs b/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs new file mode 100644 index 000000000..7f76d5f54 --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs @@ -0,0 +1,129 @@ +import { useState } from "react"; +import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui"; +import { ActivityIndicator, Alert, StyleSheet, View } from "react-native"; +import type { PurchasesPackage } from "react-native-purchases"; + +import { NAV_THEME } from "@/lib/constants"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { useRevenueCat } from "@/contexts/revenuecat-context"; + +export function PaywallExample() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const { isPro, getPackages, purchasePackage, restorePurchases } = useRevenueCat(); + const [packages, setPackages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isPurchasing, setIsPurchasing] = useState(false); + const [isRestoring, setIsRestoring] = useState(false); + const isBusy = isPurchasing || isRestoring; + + const loadPackages = async () => { + setIsLoading(true); + try { + setPackages(await getPackages()); + } finally { + setIsLoading(false); + } + }; + + const handlePurchase = async (pack: PurchasesPackage) => { + if (isBusy) return; + setIsPurchasing(true); + try { + const success = await purchasePackage(pack); + Alert.alert(success ? "You're now Premium!" : "Purchase not completed"); + } finally { + setIsPurchasing(false); + } + }; + + const handleRestore = async () => { + if (isBusy) return; + setIsRestoring(true); + try { + const success = await restorePurchases(); + Alert.alert(success ? "Purchases restored" : "No purchases to restore"); + } finally { + setIsRestoring(false); + } + }; + + if (isPro) { + return ( + + + + + You're Premium + + + Thanks for your support — enjoy all features. + + + + + ); + } + + return ( + + + + + Upgrade to Premium + + + Unlock all features. + + + + + {isLoading ? ( + + + + ) : ( + + + {packages.length === 0 ? ( + + ) : ( + packages.map((pack) => ( + + )) + )} + + + + + ); +} \ No newline at end of file diff --git a/packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs b/packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs new file mode 100644 index 000000000..624c73683 --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs @@ -0,0 +1,25 @@ +import { Text } from "react-native"; +import { Card } from "heroui-native"; + +import { useRevenueCat } from "@/contexts/revenuecat-context"; + +export function SubscriptionStatusCard() { + const { isPro, isConfigured } = useRevenueCat(); + + return ( + + Subscription + {!isConfigured ? ( + + RevenueCat is not configured yet. Add your API keys in apps/native/.env. + + ) : isPro ? ( + + You're a Premium member 🎉 + + ) : ( + You're on the free plan. + )} + + ); +} \ No newline at end of file diff --git a/packages/types/src/compatibility.ts b/packages/types/src/compatibility.ts index ae2fbafe3..2bd9ef803 100644 --- a/packages/types/src/compatibility.ts +++ b/packages/types/src/compatibility.ts @@ -1953,6 +1953,15 @@ export const getDisabledReason = ( return "Dodo Payments are not yet supported for React + Vite projects"; } + if (category === "payments" && optionId === "revenuecat") { + const hasNativeFrontend = currentStack.nativeFrontend.some((f) => + ["native-bare", "native-uniwind", "native-unistyles"].includes(f) + ); + if (!hasNativeFrontend) { + return "RevenueCat payments requires a native frontend (native-bare, native-uniwind, or native-unistyles)"; + } + } + // ============================================ // CMS CONSTRAINTS // ============================================ diff --git a/packages/types/src/schemas.ts b/packages/types/src/schemas.ts index 1345e9263..2008c4436 100644 --- a/packages/types/src/schemas.ts +++ b/packages/types/src/schemas.ts @@ -216,7 +216,7 @@ export const AuthSchema = z .describe("Authentication provider"); export const PaymentsSchema = z - .enum(["polar", "stripe", "lemon-squeezy", "paddle", "dodo", "none"]) + .enum(["revenuecat","polar", "stripe", "lemon-squeezy", "paddle", "dodo", "none"]) .describe("Payments provider"); export const WebDeploySchema = z From 592463367b3bac4723021af76fc82a22ed5f2a2a Mon Sep 17 00:00:00 2001 From: Ahmad Alisha Date: Wed, 17 Jun 2026 15:25:26 +0530 Subject: [PATCH 2/8] chore:fix prompt resolver registry and tech resource link configuration --- apps/cli/src/prompts/prompt-resolver-registry.ts | 5 ++++- apps/web/src/lib/tech-resource-links.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/prompts/prompt-resolver-registry.ts b/apps/cli/src/prompts/prompt-resolver-registry.ts index 26e15ad33..1c3829126 100644 --- a/apps/cli/src/prompts/prompt-resolver-registry.ts +++ b/apps/cli/src/prompts/prompt-resolver-registry.ts @@ -321,7 +321,10 @@ export const PROMPT_RESOLVER_REGISTRY: ResolverRegistry = { payments: { schemaValues: PAYMENTS_VALUES, resolve: resolvePaymentsPrompt, - coverageContexts: [{ auth: "better-auth", backend: "hono", frontends: ["next"] }], + coverageContexts: [ + { auth: "better-auth", backend: "hono", frontends: ["next"] }, + { auth: "better-auth", backend: "hono", frontends: ["native-bare"] }, + ], }, realtime: { schemaValues: REALTIME_VALUES, diff --git a/apps/web/src/lib/tech-resource-links.ts b/apps/web/src/lib/tech-resource-links.ts index 2a7b6c7ad..2f70da244 100644 --- a/apps/web/src/lib/tech-resource-links.ts +++ b/apps/web/src/lib/tech-resource-links.ts @@ -404,6 +404,7 @@ const BASE_LINKS: LinkMap = { githubUrl: "https://github.com/PaddleHQ/paddle-node-sdk", }, dodo: { docsUrl: "https://docs.dodopayments.com/" }, + revenuecat: { docsUrl: "https://www.revenuecat.com/docs" }, resend: { docsUrl: "https://resend.com/docs", githubUrl: "https://github.com/resend/resend-node", From 7c057112a223bd2406cb6b5f8fa78cc9b961607f Mon Sep 17 00:00:00 2001 From: Ahmad Alisha Date: Thu, 18 Jun 2026 09:57:57 +0530 Subject: [PATCH 3/8] chore : fixed syntax error --- .../templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs index b0716aafa..59da3388a 100644 --- a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs @@ -198,6 +198,7 @@ return ( {{/if}} + {{/if}} ); } From 79cd94a810000419a1a963f6f31249f0fa003675 Mon Sep 17 00:00:00 2001 From: Ahmad Alisha Date: Wed, 17 Jun 2026 12:52:23 +0530 Subject: [PATCH 4/8] feat: add RevenueCat integration templates and modular scaffolding support for native apps --- apps/cli/src/helpers/addons/mcp-setup.ts | 9 + .../cli/src/helpers/core/post-installation.ts | 26 + apps/cli/src/prompts/payments.ts | 18 + apps/cli/src/utils/compatibility-rules.ts | 9 + apps/cli/test/payments.test.ts | 48 + apps/web/src/lib/constant.ts | 2750 +++++++++-------- .../src/processors/env-vars.ts | 46 + .../src/processors/payments-deps.ts | 23 + .../src/template-handlers/payments.ts | 40 +- .../template-generator/src/utils/add-deps.ts | 4 + .../convex/backend/convex/http.ts.hbs | 12 +- .../native/bare/app/(drawer)/index.tsx.hbs | 11 +- .../unistyles/app/(drawer)/index.tsx.hbs | 9 + .../native/uniwind/app/(drawer)/index.tsx.hbs | 11 + .../backend/convex/convex.config.ts.hbs | 8 +- .../native/bare/app/(drawer)/index.tsx.hbs | 10 +- .../frontend/native/bare/app/_layout.tsx.hbs | 285 +- .../unistyles/app/(drawer)/index.tsx.hbs | 9 + .../native/unistyles/app/_layout.tsx.hbs | 10 + .../native/uniwind/app/(drawer)/index.tsx.hbs | 31 +- .../native/uniwind/app/_layout.tsx.hbs | 10 + .../convex/backend/convex/revenuecat.ts.hbs | 9 + .../convex/no-better-auth/convex/http.ts.hbs | 9 + .../bare/components/paywall-example.tsx.hbs | 129 + .../subscription-status-card.tsx.hbs | 46 + .../base/contexts/revenuecat-context.tsx.hbs | 286 ++ .../native/base/lib/revenuecat.ts.hbs | 197 ++ .../components/paywall-example.tsx.hbs | 128 + .../subscription-status-card.tsx.hbs | 46 + .../components/paywall-example.tsx.hbs | 91 + .../subscription-status-card.tsx.hbs | 25 + packages/types/src/compatibility.ts | 9 + packages/types/src/schemas.ts | 2 +- 33 files changed, 2829 insertions(+), 1527 deletions(-) create mode 100644 packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/bare/components/subscription-status-card.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/base/contexts/revenuecat-context.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/base/lib/revenuecat.ts.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/unistyles/components/paywall-example.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/unistyles/components/subscription-status-card.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/uniwind/components/paywall-example.tsx.hbs create mode 100644 packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs diff --git a/apps/cli/src/helpers/addons/mcp-setup.ts b/apps/cli/src/helpers/addons/mcp-setup.ts index f3140134e..727d712f9 100644 --- a/apps/cli/src/helpers/addons/mcp-setup.ts +++ b/apps/cli/src/helpers/addons/mcp-setup.ts @@ -214,6 +214,15 @@ function getRecommendedMcpServers(config: ProjectConfig): McpServerDef[] { }); } + if (config.payments === "revenuecat") { + servers.push({ + key: "revenuecat", + label: "RevenueCat", + name: "revenuecat", + target: "https://mcp.revenuecat.ai/mcp", + }); + } + return servers; } diff --git a/apps/cli/src/helpers/core/post-installation.ts b/apps/cli/src/helpers/core/post-installation.ts index 04c1242ae..a30fd41f6 100644 --- a/apps/cli/src/helpers/core/post-installation.ts +++ b/apps/cli/src/helpers/core/post-installation.ts @@ -138,6 +138,8 @@ export async function displayPostInstallInstructions( ); const polarInstructions = config.payments === "polar" ? getPolarInstructions(backend, packageManager) : ""; + const revenueCatInstructions = + config.payments === "revenuecat" ? getRevenueCatInstructions(backend, packageManager) : ""; const paymentSetupInstructions = getPaymentSetupInstructions(config.payments, backend); const alchemyDeployInstructions = getAlchemyDeployInstructions( runCmd, @@ -283,6 +285,7 @@ export async function displayPostInstallInstructions( if (authSetupInstructions) output += `\n${authSetupInstructions.trim()}\n`; if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`; if (polarInstructions) output += `\n${polarInstructions.trim()}\n`; + if (revenueCatInstructions) output += `\n${revenueCatInstructions.trim()}\n`; if (paymentSetupInstructions) output += `\n${paymentSetupInstructions.trim()}\n`; if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`; @@ -689,6 +692,29 @@ function getPolarInstructions(backend: Backend, packageManager: string) { return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`; } +function getRevenueCatInstructions(backend: Backend, packageManager: string) { + const base = + `${pc.bold("RevenueCat Payments Setup:")}\n` + + `${pc.cyan("•")} Create a project, entitlement, and offering in ${pc.underline("https://app.revenuecat.com/")}\n` + + `${pc.cyan("•")} Set the public SDK keys in ${pc.white("apps/native/.env")}:\n` + + `${pc.white(" EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_your_ios_key")}\n` + + `${pc.white(" EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=goog_your_android_key")}\n` + + `${pc.white(" EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID=pro")}`; + + if (backend === "convex") { + const cmd = packageManager === "npm" ? "npx" : packageManager; + return ( + `${base}\n` + + `${pc.cyan("•")} Set the webhook secret (min 32 chars) from ${pc.white("packages/backend")}:\n` + + `${pc.white(" cd packages/backend")}\n` + + `${pc.white(` ${cmd} convex env set REVENUECAT_WEBHOOK_AUTH your_webhook_secret`)}\n` + + `${pc.cyan("•")} Configure a RevenueCat webhook to ${pc.white("https:///webhooks/revenuecat")} using the same value as the Authorization header` + ); + } + + return base; +} + function getAlchemyDeployInstructions( runCmd: string, webDeploy: WebDeploy, diff --git a/apps/cli/src/prompts/payments.ts b/apps/cli/src/prompts/payments.ts index 10ecaf60a..cfe3a852b 100644 --- a/apps/cli/src/prompts/payments.ts +++ b/apps/cli/src/prompts/payments.ts @@ -38,6 +38,15 @@ export function resolvePaymentsPrompt( (context.auth === "better-auth" || context.auth === "better-auth-organizations") && (context.frontends?.length === 0 || splitFrontends(context.frontends).web.length > 0); + const hasNativeFrontend = (context.frontends ?? []).some( + (frontend) => + frontend === "native-bare" || + frontend === "native-uniwind" || + frontend === "native-unistyles", + ); + + const isRevenueCatCompatible = hasNativeFrontend; + const options: Array<{ value: Payments; label: string; hint: string }> = []; if (isPolarCompatible) { @@ -76,6 +85,15 @@ export function resolvePaymentsPrompt( }, ); + if (isRevenueCatCompatible) { + options.push({ + value: "revenuecat" as Payments, + label: "RevenueCat", + hint: "In-app subscriptions and cross-platform monetization for mobile.", + }); + } + + return { shouldPrompt: true, mode: "single", diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index b4394ed39..6edb85bf4 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -634,6 +634,15 @@ export function validatePaymentsCompatibility( ); } } + + if (payments === "revenuecat") { + const { native } = splitFrontends(frontends); + if (native.length === 0) { + exitWithError( + "RevenueCat payments requires a native frontend. Please select a native frontend or choose a different payments provider.", + ); + } + } } export function validateExamplesCompatibility( diff --git a/apps/cli/test/payments.test.ts b/apps/cli/test/payments.test.ts index ab993ed0c..2342a1f47 100644 --- a/apps/cli/test/payments.test.ts +++ b/apps/cli/test/payments.test.ts @@ -686,6 +686,54 @@ describe("Payments Options", () => { }); }); + describe("RevenueCat payments", () => { + test("revenuecat with native-bare frontend", async () => { + const result = await runTRPCTest( + createCustomConfig({ + projectName: "revenuecat-native-bare", + frontend: ["native-bare"], + backend: "hono", + api: "none", + payments: "revenuecat", + }), + ); + expectSuccess(result); + }); + + test("revenuecat with native-unistyles frontend", async () => { + const result = await runTRPCTest( + createCustomConfig({ + projectName: "revenuecat-native-unistyles", + frontend: ["native-unistyles"], + backend: "convex", + runtime: "none", + database: "none", + orm: "none", + api: "none", + payments: "revenuecat", + }), + ); + expectSuccess(result); + }); + + test("revenuecat with native-uniwind and better-auth (convex combined webhook)", async () => { + const result = await runTRPCTest( + createCustomConfig({ + projectName: "revenuecat-uniwind-better-auth", + frontend: ["native-uniwind"], + backend: "convex", + runtime: "none", + database: "none", + orm: "none", + api: "none", + auth: "better-auth", + payments: "revenuecat", + }), + ); + expectSuccess(result); + }); + }); + describe("No payments option", () => { test("none payments option", async () => { const result = await runTRPCTest( diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts index 497d595b7..6b497e94f 100644 --- a/apps/web/src/lib/constant.ts +++ b/apps/web/src/lib/constant.ts @@ -987,6 +987,14 @@ export const TECH_OPTIONS: Record< color: "from-indigo-500 to-blue-600", default: false, }, + { + id: "revenuecat", + name: "RevenueCat", + description: "In-app subscriptions and cross-platform monetization for mobile.", + icon: "https://cdn.simpleicons.org/revenuecat/F2545B", + color: "from-red-400 to-red-600", + default: false, + }, { id: "none", name: "No Payments", @@ -5350,63 +5358,63 @@ export const ECOSYSTEMS: { icon: string; color: string; }[] = [ - { - id: "typescript", - name: "TypeScript", - description: "Full-stack TypeScript web ecosystem", - icon: "https://cdn.simpleicons.org/typescript/3178C6", - color: "from-blue-500 to-blue-700", - }, - { - id: "react-native", - name: "React Native", - description: "Expo and React Native mobile ecosystem", - icon: "https://cdn.simpleicons.org/react/61DAFB", - color: "from-cyan-500 to-blue-700", - }, - { - id: "rust", - name: "Rust", - description: "High-performance Rust ecosystem", - icon: "", - color: "from-orange-500 to-orange-700", - }, - { - id: "python", - name: "Python", - description: "Python full-stack ecosystem", - icon: "https://cdn.simpleicons.org/python/3776AB", - color: "from-blue-400 to-yellow-500", - }, - { - id: "go", - name: "Go", - description: "High-performance Go ecosystem", - icon: "https://cdn.simpleicons.org/go/00ADD8", - color: "from-cyan-500 to-blue-600", - }, - { - id: "java", - name: "Java", - description: "Modern Java ecosystem", - icon: "/icon/java.svg", - color: "from-red-500 to-orange-600", - }, - { - id: "elixir", - name: "Elixir", - description: "Phoenix full-stack ecosystem", - icon: "https://cdn.simpleicons.org/elixir/4B275F", - color: "from-purple-500 to-fuchsia-600", - }, - { - id: "dotnet", - name: ".NET", - description: "ASP.NET Core ecosystem", - icon: "https://cdn.simpleicons.org/dotnet/512BD4", - color: "from-violet-500 to-blue-700", - }, -]; + { + id: "typescript", + name: "TypeScript", + description: "Full-stack TypeScript web ecosystem", + icon: "https://cdn.simpleicons.org/typescript/3178C6", + color: "from-blue-500 to-blue-700", + }, + { + id: "react-native", + name: "React Native", + description: "Expo and React Native mobile ecosystem", + icon: "https://cdn.simpleicons.org/react/61DAFB", + color: "from-cyan-500 to-blue-700", + }, + { + id: "rust", + name: "Rust", + description: "High-performance Rust ecosystem", + icon: "", + color: "from-orange-500 to-orange-700", + }, + { + id: "python", + name: "Python", + description: "Python full-stack ecosystem", + icon: "https://cdn.simpleicons.org/python/3776AB", + color: "from-blue-400 to-yellow-500", + }, + { + id: "go", + name: "Go", + description: "High-performance Go ecosystem", + icon: "https://cdn.simpleicons.org/go/00ADD8", + color: "from-cyan-500 to-blue-600", + }, + { + id: "java", + name: "Java", + description: "Modern Java ecosystem", + icon: "/icon/java.svg", + color: "from-red-500 to-orange-600", + }, + { + id: "elixir", + name: "Elixir", + description: "Phoenix full-stack ecosystem", + icon: "https://cdn.simpleicons.org/elixir/4B275F", + color: "from-purple-500 to-fuchsia-600", + }, + { + id: "dotnet", + name: ".NET", + description: "ASP.NET Core ecosystem", + icon: "https://cdn.simpleicons.org/dotnet/512BD4", + color: "from-violet-500 to-blue-700", + }, + ]; // Categories available for each ecosystem export const ECOSYSTEM_CATEGORIES = Object.fromEntries( @@ -5445,1320 +5453,1320 @@ export const PRESET_TEMPLATES: { category: PresetCategory; stack: Partial; }[] = [ - { - id: "t3", - name: "T3 Stack", - description: "Next.js + tRPC + Prisma + PostgreSQL + Better Auth", - category: "next", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "postgres", - orm: "prisma", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome"], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nextjs-saas", - name: "Next.js SaaS", - description: "Next.js + Stripe + Drizzle + PostgreSQL + Auth + React Email", - category: "next", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "stripe", - email: "react-email", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome"], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nextjs-minimal", - name: "Next.js Minimal", - description: "Next.js + Tailwind + shadcn/ui — no database or backend", - category: "next", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "mern", - name: "MERN Stack", - description: "MongoDB + Express + React Router + Node.js", - category: "react", - stack: { - projectName: "my-app", - webFrontend: ["react-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "node", - backend: "express", - database: "mongodb", - orm: "mongoose", - dbSetup: "mongodb-atlas", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "pern", - name: "PERN Stack", - description: "PostgreSQL + Express + TanStack Router + Node.js", - category: "react", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "node", - backend: "express", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "react-hono", - name: "React + Hono", - description: "React Router + Hono + Drizzle + SQLite + oRPC", - category: "react", - stack: { - projectName: "my-app", - webFrontend: ["react-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "tanstack-start", - name: "TanStack Start", - description: "TanStack Start fullstack + tRPC + Drizzle + SQLite", - category: "tanstack", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-start"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-tanstack-start", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "tanstack-hono", - name: "TanStack + Hono", - description: "TanStack Router + Hono + tRPC + Drizzle + PostgreSQL", - category: "tanstack", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "bun", - backend: "hono", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "sveltekit-fullstack", - name: "SvelteKit Fullstack", - description: "SvelteKit + oRPC + Drizzle + SQLite + Better Auth", - category: "svelte", - stack: { - projectName: "my-app", - webFrontend: ["svelte"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "self-svelte", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "sveltekit-hono", - name: "SvelteKit + Hono", - description: "SvelteKit + Hono + oRPC + Drizzle + PostgreSQL", - category: "svelte", - stack: { - projectName: "my-app", - webFrontend: ["svelte"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "bun", - backend: "hono", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nuxt-fullstack", - name: "Nuxt Fullstack", - description: "Nuxt + oRPC + Prisma + PostgreSQL + Better Auth", - category: "nuxt", - stack: { - projectName: "my-app", - webFrontend: ["nuxt"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "self-nuxt", - database: "postgres", - orm: "prisma", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "nuxt-hono", - name: "Nuxt + Hono", - description: "Nuxt + Hono + oRPC + Drizzle + SQLite", - category: "nuxt", - stack: { - projectName: "my-app", - webFrontend: ["nuxt"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "astro-react", - name: "Astro + React", - description: "Astro with React integration + Hono + tRPC + Drizzle", - category: "astro", - stack: { - projectName: "my-app", - webFrontend: ["astro"], - nativeFrontend: ["none"], - astroIntegration: "react", - cssFramework: "tailwind", - uiLibrary: "daisyui", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "astro-fullstack", - name: "Fullstack Astro", - description: "Astro with React using built-in API routes (fullstack mode)", - category: "astro", - stack: { - projectName: "my-app", - webFrontend: ["astro"], - nativeFrontend: ["none"], - astroIntegration: "react", - cssFramework: "tailwind", - uiLibrary: "daisyui", - runtime: "none", - backend: "self-astro", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "tanstack-fullstack", - name: "TanStack Fullstack", - description: - "Full TanStack ecosystem: Start + oRPC + Query + Store + Form + Table + Virtual + Pacer + AI (with Showcase)", - category: "tanstack", - stack: { - projectName: "my-app", - webFrontend: ["tanstack-start"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-tanstack-start", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "tanstack-store", - forms: "tanstack-form", - codeQuality: ["biome"], - documentation: [], - appPlatforms: [ - "turborepo", - "tanstack-table", - "tanstack-virtual", - "tanstack-query", - "tanstack-pacer", - ], - packageManager: "bun", - examples: ["tanstack-showcase"], - git: "true", - install: "true", - api: "orpc", - aiSdk: "tanstack-ai", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "solidstart-fullstack", - name: "SolidStart Fullstack", - description: "SolidStart + oRPC + Drizzle + SQLite + Better Auth", - category: "solid", - stack: { - projectName: "my-app", - webFrontend: ["solid-start"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "self-solid-start", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "solid-hono", - name: "Solid + Hono", - description: "SolidJS SPA + Hono + oRPC + Drizzle + PostgreSQL", - category: "solid", - stack: { - projectName: "my-app", - webFrontend: ["solid"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "bun", - backend: "hono", - database: "postgres", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "angular-express", - name: "Angular + Express", - description: "Angular + Express + Prisma + PostgreSQL", - category: "angular", - stack: { - projectName: "my-app", - webFrontend: ["angular"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "node", - backend: "express", - database: "postgres", - orm: "prisma", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "qwik-city", - name: "Qwik City", - description: "Qwik with built-in server + Tailwind", - category: "qwik", - stack: { - projectName: "my-app", - webFrontend: ["qwik"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "none", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: ["turborepo"], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "uniwind", - name: "Uniwind Native", - description: "Expo + Uniwind native app with no backend", - category: "mobile", - stack: { - ecosystem: "react-native", - projectName: "my-app", - webFrontend: ["none"], - nativeFrontend: ["native-uniwind"], - astroIntegration: "none", - cssFramework: "none", - uiLibrary: "none", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: [], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "expo-bare", - name: "Expo Bare", - description: "Expo with bare workflow — no backend", - category: "mobile", - stack: { - ecosystem: "react-native", - projectName: "my-app", - webFrontend: ["none"], - nativeFrontend: ["native-bare"], - astroIntegration: "none", - cssFramework: "none", - uiLibrary: "none", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: [], - documentation: [], - appPlatforms: [], - packageManager: "bun", - examples: [], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "ai-cli-agent-workbench", - name: "AI CLI Agent Workbench", - description: "Next.js + AI CLI + MCP + Skills + Ruler + complete AI docs", - category: "ai-tools", - stack: { - projectName: "my-app", - webFrontend: ["next"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "self-next", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome", "ruler"], - documentation: [], - appPlatforms: ["turborepo", "mcp", "skills"], - packageManager: "bun", - examples: [], - aiSdk: "ai-cli", - aiDocs: ["claude-md", "agents-md", "cursorrules"], - git: "true", - install: "true", - api: "trpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "ai-cli-react-hono", - name: "AI CLI React API", - description: "React Router + Hono + AI CLI command presets + agent tooling", - category: "ai-tools", - stack: { - projectName: "my-app", - webFrontend: ["react-router"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "bun", - backend: "hono", - database: "sqlite", - orm: "drizzle", - dbSetup: "none", - auth: "better-auth", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome", "ruler"], - documentation: [], - appPlatforms: ["turborepo", "mcp", "skills"], - packageManager: "bun", - examples: [], - aiSdk: "ai-cli", - aiDocs: ["claude-md", "agents-md", "cursorrules"], - git: "true", - install: "true", - api: "orpc", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - { - id: "ai-cli-frontend-lab", - name: "AI CLI Frontend Lab", - description: "React + Vite frontend-only stack with AI CLI asset generation", - category: "ai-tools", - stack: { - projectName: "my-app", - webFrontend: ["react-vite"], - nativeFrontend: ["none"], - astroIntegration: "none", - cssFramework: "tailwind", - uiLibrary: "shadcn-ui", - runtime: "none", - backend: "none", - database: "none", - orm: "none", - dbSetup: "none", - auth: "none", - payments: "none", - email: "none", - backendLibraries: "none", - stateManagement: "none", - codeQuality: ["biome", "ruler"], - documentation: [], - appPlatforms: ["turborepo", "mcp", "skills"], - packageManager: "bun", - examples: [], - aiSdk: "ai-cli", - aiDocs: ["claude-md", "agents-md", "cursorrules"], - git: "true", - install: "true", - api: "none", - webDeploy: "none", - serverDeploy: "none", - yolo: "false", - }, - }, - // ── Rust ────────────────────────────────────────────── - { - id: "rust-fullstack", - name: "Rust Fullstack", - description: "Axum + Leptos + SeaORM — full-stack Rust with SSR", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "axum", - rustFrontend: "leptos", - rustOrm: "sea-orm", - rustApi: "none", - rustCli: "none", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "rust-api", - name: "Rust API", - description: "Axum + SeaORM — backend REST API, no frontend", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "axum", - rustFrontend: "none", - rustOrm: "sea-orm", - rustApi: "none", - rustCli: "none", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "rust-grpc", - name: "Rust gRPC", - description: "Axum + Tonic + SQLx — gRPC microservice", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "axum", - rustFrontend: "none", - rustOrm: "sqlx", - rustApi: "tonic", - rustCli: "none", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "rust-cli", - name: "Rust CLI", - description: "Clap + Ratatui — terminal UI application", - category: "rust", - stack: { - ecosystem: "rust", - projectName: "my-app", - rustWebFramework: "none", - rustFrontend: "none", - rustOrm: "none", - rustApi: "none", - rustCli: "ratatui", - rustLibraries: [], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Python ─────────────────────────────────────────── - { - id: "python-fastapi", - name: "FastAPI", - description: "FastAPI + SQLAlchemy + Pydantic + Ruff", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "fastapi", - pythonOrm: "sqlalchemy", - pythonValidation: "pydantic", - pythonAi: [], - pythonAuth: "none", - pythonApi: "none", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "python-django", - name: "Django", - description: "Django + SQLAlchemy + Pydantic + Ruff", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "django", - pythonOrm: "sqlalchemy", - pythonValidation: "pydantic", - pythonAi: [], - pythonAuth: "none", - pythonApi: "django-rest-framework", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "python-ai-agent", - name: "AI Agent", - description: "FastAPI + LangChain + Pydantic — AI-powered API", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "fastapi", - pythonOrm: "sqlmodel", - pythonValidation: "pydantic", - pythonAi: ["langchain"], - pythonAuth: "none", - pythonApi: "none", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "python-ai-anthropic", - name: "AI Agent (Anthropic)", - description: "FastAPI + Anthropic SDK + Pydantic — Claude-powered API", - category: "python", - stack: { - ecosystem: "python", - projectName: "my-app", - pythonWebFramework: "fastapi", - pythonOrm: "sqlmodel", - pythonValidation: "pydantic", - pythonAi: ["anthropic-sdk"], - pythonAuth: "none", - pythonApi: "none", - pythonTaskQueue: "none", - pythonGraphql: "none", - pythonQuality: "ruff", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Go ─────────────────────────────────────────────── - { - id: "go-gin", - name: "Go API (Gin)", - description: "Gin + GORM + Zap — REST API with structured logging", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "gin", - goOrm: "gorm", - goApi: "none", - goCli: "none", - goLogging: "zap", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "go-echo", - name: "Go API (Echo)", - description: "Echo + SQLC + Zap — type-safe SQL with Echo", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "echo", - goOrm: "sqlc", - goApi: "none", - goCli: "none", - goLogging: "zap", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "go-grpc", - name: "Go gRPC", - description: "Gin + gRPC-Go + GORM + Zap — gRPC microservice", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "gin", - goOrm: "gorm", - goApi: "grpc-go", - goCli: "none", - goLogging: "zap", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "go-cli", - name: "Go CLI", - description: "Cobra + Bubbletea — interactive terminal application", - category: "go", - stack: { - ecosystem: "go", - projectName: "my-app", - goWebFramework: "none", - goOrm: "none", - goApi: "none", - goCli: "bubbletea", - goLogging: "none", - goAuth: "none", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Java ───────────────────────────────────────────── - { - id: "java-spring", - name: "Spring Boot API", - description: "Spring Boot + Maven + JUnit 5", - category: "java", - stack: { - ecosystem: "java", - projectName: "my-app", - javaWebFramework: "spring-boot", - javaBuildTool: "maven", - javaOrm: "none", - javaAuth: "none", - javaLibraries: [], - javaTestingLibraries: ["junit5"], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "java-jpa", - name: "Spring Data JPA", - description: "Spring Boot + Spring Data JPA + JUnit 5", - category: "java", - stack: { - ecosystem: "java", - projectName: "my-app", - javaWebFramework: "spring-boot", - javaBuildTool: "maven", - javaOrm: "spring-data-jpa", - javaAuth: "none", - javaLibraries: ["flyway", "spring-validation"], - javaTestingLibraries: ["junit5", "mockito"], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "java-secure", - name: "Secure Spring Boot", - description: "Spring Boot + Spring Security + JPA + Testcontainers", - category: "java", - stack: { - ecosystem: "java", - projectName: "my-app", - javaWebFramework: "spring-boot", - javaBuildTool: "gradle", - javaOrm: "spring-data-jpa", - javaAuth: "spring-security", - javaLibraries: ["spring-actuator", "flyway"], - javaTestingLibraries: ["junit5", "testcontainers"], - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── .NET ───────────────────────────────────────────── - { - id: "dotnet-minimal-api", - name: ".NET Minimal API", - description: "ASP.NET Core Minimal APIs + EF Core + SignalR", - category: "dotnet", - stack: { - ecosystem: "dotnet", - projectName: "my-app", - dotnetWebFramework: "aspnet-minimal", - dotnetOrm: "ef-core", - dotnetAuth: "aspnet-identity", - dotnetApi: "minimal-api", - dotnetTesting: ["xunit"], - dotnetJobQueue: "none", - dotnetRealtime: "signalr", - dotnetObservability: ["serilog"], - dotnetCaching: "none", - dotnetDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "dotnet-graphql", - name: ".NET GraphQL", - description: "ASP.NET Core + Hot Chocolate + EF Core", - category: "dotnet", - stack: { - ecosystem: "dotnet", - projectName: "my-app", - dotnetWebFramework: "aspnet-minimal", - dotnetOrm: "ef-core", - dotnetAuth: "none", - dotnetApi: "graphql-hotchocolate", - dotnetTesting: ["xunit", "testcontainers-dotnet"], - dotnetJobQueue: "none", - dotnetRealtime: "none", - dotnetObservability: ["serilog", "health-checks"], - dotnetCaching: "none", - dotnetDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "dotnet-worker", - name: ".NET Worker", - description: "ASP.NET Core hosted services + Quartz.NET", - category: "dotnet", - stack: { - ecosystem: "dotnet", - projectName: "my-app", - dotnetWebFramework: "aspnet-minimal", - dotnetOrm: "none", - dotnetAuth: "none", - dotnetApi: "none", - dotnetTesting: ["xunit"], - dotnetJobQueue: "quartz-net", - dotnetRealtime: "none", - dotnetObservability: ["serilog", "opentelemetry-dotnet"], - dotnetCaching: "memory-cache", - dotnetDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - // ── Elixir ────────────────────────────────────────── - { - id: "elixir-phoenix-api", - name: "Phoenix API", - description: "Phoenix + Ecto SQL + REST + Channels", - category: "elixir", - stack: { - ecosystem: "elixir", - projectName: "my-app", - elixirWebFramework: "phoenix", - elixirOrm: "ecto-sql", - elixirAuth: "none", - elixirApi: "rest", - elixirRealtime: "channels", - elixirJobs: "none", - elixirValidation: "ecto-changesets", - elixirHttp: "req", - elixirJson: "jason", - elixirEmail: "none", - elixirCaching: "none", - elixirObservability: "telemetry", - elixirTesting: "ex_unit", - elixirQuality: "credo", - elixirDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "elixir-liveview-full", - name: "LiveView Fullstack", - description: "Phoenix LiveView + Ecto + Auth + Oban", - category: "elixir", - stack: { - ecosystem: "elixir", - projectName: "my-app", - elixirWebFramework: "phoenix-live-view", - elixirOrm: "ecto-sql", - elixirAuth: "phx-gen-auth", - elixirApi: "absinthe", - elixirRealtime: "presence", - elixirJobs: "oban", - elixirValidation: "ecto-changesets", - elixirHttp: "req", - elixirJson: "jason", - elixirEmail: "swoosh", - elixirCaching: "cachex", - elixirObservability: "telemetry", - elixirTesting: "ex_unit", - elixirQuality: "sobelow", - elixirDeploy: "docker", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, - { - id: "elixir-plain-worker", - name: "Plain Elixir Worker", - description: "Mix app + Quantum + Req — no Phoenix", - category: "elixir", - stack: { - ecosystem: "elixir", - projectName: "my-app", - elixirWebFramework: "none", - elixirOrm: "none", - elixirAuth: "none", - elixirApi: "none", - elixirRealtime: "none", - elixirJobs: "quantum", - elixirValidation: "none", - elixirHttp: "req", - elixirJson: "jason", - elixirEmail: "none", - elixirCaching: "cachex", - elixirObservability: "none", - elixirTesting: "ex_unit", - elixirQuality: "credo", - elixirDeploy: "mix-release", - aiDocs: ["claude-md"], - git: "true", - install: "true", - }, - }, -]; + { + id: "t3", + name: "T3 Stack", + description: "Next.js + tRPC + Prisma + PostgreSQL + Better Auth", + category: "next", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "postgres", + orm: "prisma", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome"], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nextjs-saas", + name: "Next.js SaaS", + description: "Next.js + Stripe + Drizzle + PostgreSQL + Auth + React Email", + category: "next", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "stripe", + email: "react-email", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome"], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nextjs-minimal", + name: "Next.js Minimal", + description: "Next.js + Tailwind + shadcn/ui — no database or backend", + category: "next", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "mern", + name: "MERN Stack", + description: "MongoDB + Express + React Router + Node.js", + category: "react", + stack: { + projectName: "my-app", + webFrontend: ["react-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "node", + backend: "express", + database: "mongodb", + orm: "mongoose", + dbSetup: "mongodb-atlas", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "pern", + name: "PERN Stack", + description: "PostgreSQL + Express + TanStack Router + Node.js", + category: "react", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "node", + backend: "express", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "react-hono", + name: "React + Hono", + description: "React Router + Hono + Drizzle + SQLite + oRPC", + category: "react", + stack: { + projectName: "my-app", + webFrontend: ["react-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "tanstack-start", + name: "TanStack Start", + description: "TanStack Start fullstack + tRPC + Drizzle + SQLite", + category: "tanstack", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-start"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-tanstack-start", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "tanstack-hono", + name: "TanStack + Hono", + description: "TanStack Router + Hono + tRPC + Drizzle + PostgreSQL", + category: "tanstack", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "bun", + backend: "hono", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "sveltekit-fullstack", + name: "SvelteKit Fullstack", + description: "SvelteKit + oRPC + Drizzle + SQLite + Better Auth", + category: "svelte", + stack: { + projectName: "my-app", + webFrontend: ["svelte"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "self-svelte", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "sveltekit-hono", + name: "SvelteKit + Hono", + description: "SvelteKit + Hono + oRPC + Drizzle + PostgreSQL", + category: "svelte", + stack: { + projectName: "my-app", + webFrontend: ["svelte"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "bun", + backend: "hono", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nuxt-fullstack", + name: "Nuxt Fullstack", + description: "Nuxt + oRPC + Prisma + PostgreSQL + Better Auth", + category: "nuxt", + stack: { + projectName: "my-app", + webFrontend: ["nuxt"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "self-nuxt", + database: "postgres", + orm: "prisma", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "nuxt-hono", + name: "Nuxt + Hono", + description: "Nuxt + Hono + oRPC + Drizzle + SQLite", + category: "nuxt", + stack: { + projectName: "my-app", + webFrontend: ["nuxt"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "astro-react", + name: "Astro + React", + description: "Astro with React integration + Hono + tRPC + Drizzle", + category: "astro", + stack: { + projectName: "my-app", + webFrontend: ["astro"], + nativeFrontend: ["none"], + astroIntegration: "react", + cssFramework: "tailwind", + uiLibrary: "daisyui", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "astro-fullstack", + name: "Fullstack Astro", + description: "Astro with React using built-in API routes (fullstack mode)", + category: "astro", + stack: { + projectName: "my-app", + webFrontend: ["astro"], + nativeFrontend: ["none"], + astroIntegration: "react", + cssFramework: "tailwind", + uiLibrary: "daisyui", + runtime: "none", + backend: "self-astro", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "tanstack-fullstack", + name: "TanStack Fullstack", + description: + "Full TanStack ecosystem: Start + oRPC + Query + Store + Form + Table + Virtual + Pacer + AI (with Showcase)", + category: "tanstack", + stack: { + projectName: "my-app", + webFrontend: ["tanstack-start"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-tanstack-start", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "tanstack-store", + forms: "tanstack-form", + codeQuality: ["biome"], + documentation: [], + appPlatforms: [ + "turborepo", + "tanstack-table", + "tanstack-virtual", + "tanstack-query", + "tanstack-pacer", + ], + packageManager: "bun", + examples: ["tanstack-showcase"], + git: "true", + install: "true", + api: "orpc", + aiSdk: "tanstack-ai", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "solidstart-fullstack", + name: "SolidStart Fullstack", + description: "SolidStart + oRPC + Drizzle + SQLite + Better Auth", + category: "solid", + stack: { + projectName: "my-app", + webFrontend: ["solid-start"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "self-solid-start", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "solid-hono", + name: "Solid + Hono", + description: "SolidJS SPA + Hono + oRPC + Drizzle + PostgreSQL", + category: "solid", + stack: { + projectName: "my-app", + webFrontend: ["solid"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "bun", + backend: "hono", + database: "postgres", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "angular-express", + name: "Angular + Express", + description: "Angular + Express + Prisma + PostgreSQL", + category: "angular", + stack: { + projectName: "my-app", + webFrontend: ["angular"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "node", + backend: "express", + database: "postgres", + orm: "prisma", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "qwik-city", + name: "Qwik City", + description: "Qwik with built-in server + Tailwind", + category: "qwik", + stack: { + projectName: "my-app", + webFrontend: ["qwik"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "none", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: ["turborepo"], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "uniwind", + name: "Uniwind Native", + description: "Expo + Uniwind native app with no backend", + category: "mobile", + stack: { + ecosystem: "react-native", + projectName: "my-app", + webFrontend: ["none"], + nativeFrontend: ["native-uniwind"], + astroIntegration: "none", + cssFramework: "none", + uiLibrary: "none", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: [], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "expo-bare", + name: "Expo Bare", + description: "Expo with bare workflow — no backend", + category: "mobile", + stack: { + ecosystem: "react-native", + projectName: "my-app", + webFrontend: ["none"], + nativeFrontend: ["native-bare"], + astroIntegration: "none", + cssFramework: "none", + uiLibrary: "none", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: [], + documentation: [], + appPlatforms: [], + packageManager: "bun", + examples: [], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "ai-cli-agent-workbench", + name: "AI CLI Agent Workbench", + description: "Next.js + AI CLI + MCP + Skills + Ruler + complete AI docs", + category: "ai-tools", + stack: { + projectName: "my-app", + webFrontend: ["next"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "self-next", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome", "ruler"], + documentation: [], + appPlatforms: ["turborepo", "mcp", "skills"], + packageManager: "bun", + examples: [], + aiSdk: "ai-cli", + aiDocs: ["claude-md", "agents-md", "cursorrules"], + git: "true", + install: "true", + api: "trpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "ai-cli-react-hono", + name: "AI CLI React API", + description: "React Router + Hono + AI CLI command presets + agent tooling", + category: "ai-tools", + stack: { + projectName: "my-app", + webFrontend: ["react-router"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "bun", + backend: "hono", + database: "sqlite", + orm: "drizzle", + dbSetup: "none", + auth: "better-auth", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome", "ruler"], + documentation: [], + appPlatforms: ["turborepo", "mcp", "skills"], + packageManager: "bun", + examples: [], + aiSdk: "ai-cli", + aiDocs: ["claude-md", "agents-md", "cursorrules"], + git: "true", + install: "true", + api: "orpc", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + { + id: "ai-cli-frontend-lab", + name: "AI CLI Frontend Lab", + description: "React + Vite frontend-only stack with AI CLI asset generation", + category: "ai-tools", + stack: { + projectName: "my-app", + webFrontend: ["react-vite"], + nativeFrontend: ["none"], + astroIntegration: "none", + cssFramework: "tailwind", + uiLibrary: "shadcn-ui", + runtime: "none", + backend: "none", + database: "none", + orm: "none", + dbSetup: "none", + auth: "none", + payments: "none", + email: "none", + backendLibraries: "none", + stateManagement: "none", + codeQuality: ["biome", "ruler"], + documentation: [], + appPlatforms: ["turborepo", "mcp", "skills"], + packageManager: "bun", + examples: [], + aiSdk: "ai-cli", + aiDocs: ["claude-md", "agents-md", "cursorrules"], + git: "true", + install: "true", + api: "none", + webDeploy: "none", + serverDeploy: "none", + yolo: "false", + }, + }, + // ── Rust ────────────────────────────────────────────── + { + id: "rust-fullstack", + name: "Rust Fullstack", + description: "Axum + Leptos + SeaORM — full-stack Rust with SSR", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "axum", + rustFrontend: "leptos", + rustOrm: "sea-orm", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "rust-api", + name: "Rust API", + description: "Axum + SeaORM — backend REST API, no frontend", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "axum", + rustFrontend: "none", + rustOrm: "sea-orm", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "rust-grpc", + name: "Rust gRPC", + description: "Axum + Tonic + SQLx — gRPC microservice", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "axum", + rustFrontend: "none", + rustOrm: "sqlx", + rustApi: "tonic", + rustCli: "none", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "rust-cli", + name: "Rust CLI", + description: "Clap + Ratatui — terminal UI application", + category: "rust", + stack: { + ecosystem: "rust", + projectName: "my-app", + rustWebFramework: "none", + rustFrontend: "none", + rustOrm: "none", + rustApi: "none", + rustCli: "ratatui", + rustLibraries: [], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Python ─────────────────────────────────────────── + { + id: "python-fastapi", + name: "FastAPI", + description: "FastAPI + SQLAlchemy + Pydantic + Ruff", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "fastapi", + pythonOrm: "sqlalchemy", + pythonValidation: "pydantic", + pythonAi: [], + pythonAuth: "none", + pythonApi: "none", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "python-django", + name: "Django", + description: "Django + SQLAlchemy + Pydantic + Ruff", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "django", + pythonOrm: "sqlalchemy", + pythonValidation: "pydantic", + pythonAi: [], + pythonAuth: "none", + pythonApi: "django-rest-framework", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "python-ai-agent", + name: "AI Agent", + description: "FastAPI + LangChain + Pydantic — AI-powered API", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "fastapi", + pythonOrm: "sqlmodel", + pythonValidation: "pydantic", + pythonAi: ["langchain"], + pythonAuth: "none", + pythonApi: "none", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "python-ai-anthropic", + name: "AI Agent (Anthropic)", + description: "FastAPI + Anthropic SDK + Pydantic — Claude-powered API", + category: "python", + stack: { + ecosystem: "python", + projectName: "my-app", + pythonWebFramework: "fastapi", + pythonOrm: "sqlmodel", + pythonValidation: "pydantic", + pythonAi: ["anthropic-sdk"], + pythonAuth: "none", + pythonApi: "none", + pythonTaskQueue: "none", + pythonGraphql: "none", + pythonQuality: "ruff", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Go ─────────────────────────────────────────────── + { + id: "go-gin", + name: "Go API (Gin)", + description: "Gin + GORM + Zap — REST API with structured logging", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "gin", + goOrm: "gorm", + goApi: "none", + goCli: "none", + goLogging: "zap", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "go-echo", + name: "Go API (Echo)", + description: "Echo + SQLC + Zap — type-safe SQL with Echo", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "echo", + goOrm: "sqlc", + goApi: "none", + goCli: "none", + goLogging: "zap", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "go-grpc", + name: "Go gRPC", + description: "Gin + gRPC-Go + GORM + Zap — gRPC microservice", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "gin", + goOrm: "gorm", + goApi: "grpc-go", + goCli: "none", + goLogging: "zap", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "go-cli", + name: "Go CLI", + description: "Cobra + Bubbletea — interactive terminal application", + category: "go", + stack: { + ecosystem: "go", + projectName: "my-app", + goWebFramework: "none", + goOrm: "none", + goApi: "none", + goCli: "bubbletea", + goLogging: "none", + goAuth: "none", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Java ───────────────────────────────────────────── + { + id: "java-spring", + name: "Spring Boot API", + description: "Spring Boot + Maven + JUnit 5", + category: "java", + stack: { + ecosystem: "java", + projectName: "my-app", + javaWebFramework: "spring-boot", + javaBuildTool: "maven", + javaOrm: "none", + javaAuth: "none", + javaLibraries: [], + javaTestingLibraries: ["junit5"], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "java-jpa", + name: "Spring Data JPA", + description: "Spring Boot + Spring Data JPA + JUnit 5", + category: "java", + stack: { + ecosystem: "java", + projectName: "my-app", + javaWebFramework: "spring-boot", + javaBuildTool: "maven", + javaOrm: "spring-data-jpa", + javaAuth: "none", + javaLibraries: ["flyway", "spring-validation"], + javaTestingLibraries: ["junit5", "mockito"], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "java-secure", + name: "Secure Spring Boot", + description: "Spring Boot + Spring Security + JPA + Testcontainers", + category: "java", + stack: { + ecosystem: "java", + projectName: "my-app", + javaWebFramework: "spring-boot", + javaBuildTool: "gradle", + javaOrm: "spring-data-jpa", + javaAuth: "spring-security", + javaLibraries: ["spring-actuator", "flyway"], + javaTestingLibraries: ["junit5", "testcontainers"], + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── .NET ───────────────────────────────────────────── + { + id: "dotnet-minimal-api", + name: ".NET Minimal API", + description: "ASP.NET Core Minimal APIs + EF Core + SignalR", + category: "dotnet", + stack: { + ecosystem: "dotnet", + projectName: "my-app", + dotnetWebFramework: "aspnet-minimal", + dotnetOrm: "ef-core", + dotnetAuth: "aspnet-identity", + dotnetApi: "minimal-api", + dotnetTesting: ["xunit"], + dotnetJobQueue: "none", + dotnetRealtime: "signalr", + dotnetObservability: ["serilog"], + dotnetCaching: "none", + dotnetDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "dotnet-graphql", + name: ".NET GraphQL", + description: "ASP.NET Core + Hot Chocolate + EF Core", + category: "dotnet", + stack: { + ecosystem: "dotnet", + projectName: "my-app", + dotnetWebFramework: "aspnet-minimal", + dotnetOrm: "ef-core", + dotnetAuth: "none", + dotnetApi: "graphql-hotchocolate", + dotnetTesting: ["xunit", "testcontainers-dotnet"], + dotnetJobQueue: "none", + dotnetRealtime: "none", + dotnetObservability: ["serilog", "health-checks"], + dotnetCaching: "none", + dotnetDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "dotnet-worker", + name: ".NET Worker", + description: "ASP.NET Core hosted services + Quartz.NET", + category: "dotnet", + stack: { + ecosystem: "dotnet", + projectName: "my-app", + dotnetWebFramework: "aspnet-minimal", + dotnetOrm: "none", + dotnetAuth: "none", + dotnetApi: "none", + dotnetTesting: ["xunit"], + dotnetJobQueue: "quartz-net", + dotnetRealtime: "none", + dotnetObservability: ["serilog", "opentelemetry-dotnet"], + dotnetCaching: "memory-cache", + dotnetDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + // ── Elixir ────────────────────────────────────────── + { + id: "elixir-phoenix-api", + name: "Phoenix API", + description: "Phoenix + Ecto SQL + REST + Channels", + category: "elixir", + stack: { + ecosystem: "elixir", + projectName: "my-app", + elixirWebFramework: "phoenix", + elixirOrm: "ecto-sql", + elixirAuth: "none", + elixirApi: "rest", + elixirRealtime: "channels", + elixirJobs: "none", + elixirValidation: "ecto-changesets", + elixirHttp: "req", + elixirJson: "jason", + elixirEmail: "none", + elixirCaching: "none", + elixirObservability: "telemetry", + elixirTesting: "ex_unit", + elixirQuality: "credo", + elixirDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "elixir-liveview-full", + name: "LiveView Fullstack", + description: "Phoenix LiveView + Ecto + Auth + Oban", + category: "elixir", + stack: { + ecosystem: "elixir", + projectName: "my-app", + elixirWebFramework: "phoenix-live-view", + elixirOrm: "ecto-sql", + elixirAuth: "phx-gen-auth", + elixirApi: "absinthe", + elixirRealtime: "presence", + elixirJobs: "oban", + elixirValidation: "ecto-changesets", + elixirHttp: "req", + elixirJson: "jason", + elixirEmail: "swoosh", + elixirCaching: "cachex", + elixirObservability: "telemetry", + elixirTesting: "ex_unit", + elixirQuality: "sobelow", + elixirDeploy: "docker", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + { + id: "elixir-plain-worker", + name: "Plain Elixir Worker", + description: "Mix app + Quantum + Req — no Phoenix", + category: "elixir", + stack: { + ecosystem: "elixir", + projectName: "my-app", + elixirWebFramework: "none", + elixirOrm: "none", + elixirAuth: "none", + elixirApi: "none", + elixirRealtime: "none", + elixirJobs: "quantum", + elixirValidation: "none", + elixirHttp: "req", + elixirJson: "jason", + elixirEmail: "none", + elixirCaching: "cachex", + elixirObservability: "none", + elixirTesting: "ex_unit", + elixirQuality: "credo", + elixirDeploy: "mix-release", + aiDocs: ["claude-md"], + git: "true", + install: "true", + }, + }, + ]; export { DEFAULT_STACK, isStackDefault }; export type { StackState }; diff --git a/packages/template-generator/src/processors/env-vars.ts b/packages/template-generator/src/processors/env-vars.ts index 94319d333..32e8d0320 100644 --- a/packages/template-generator/src/processors/env-vars.ts +++ b/packages/template-generator/src/processors/env-vars.ts @@ -525,6 +525,7 @@ function buildNativeVars( frontend: string[], backend: ProjectConfig["backend"], auth: ProjectConfig["auth"], + payments: ProjectConfig["payments"], mobilePush: ProjectConfig["mobilePush"], mobileDeepLinking: ProjectConfig["mobileDeepLinking"], ): EnvVariable[] { @@ -574,6 +575,26 @@ function buildNativeVars( }); } + if (payments === "revenuecat") { + vars.push( + { + key: "EXPO_PUBLIC_REVENUECAT_IOS_KEY", + value: "", + condition: true, + }, + { + key: "EXPO_PUBLIC_REVENUECAT_ANDROID_KEY", + value: "", + condition: true, + }, + { + key: "EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID", + value: "pro", + condition: true, + }, + ); + } + if (mobilePush === "expo-notifications") { vars.push({ key: "EXPO_PUBLIC_EAS_PROJECT_ID", @@ -677,6 +698,15 @@ function buildConvexBackendVars( ); } + if (payments === "revenuecat") { + vars.push({ + key: "REVENUECAT_WEBHOOK_AUTH", + value: "", + condition: true, + comment: "Shared secret for RevenueCat webhook authentication (min 32 characters)", + }); + } + return vars; } @@ -720,6 +750,15 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`; # Create a Polar webhook at https:///polar/events # Enable: product.created, product.updated, subscription.created, subscription.updated +`; + } + + if (payments === "revenuecat") { + commentBlocks += `# Set RevenueCat environment variables +# npx convex env set REVENUECAT_WEBHOOK_AUTH your_webhook_secret_min_32_chars +# Create a RevenueCat webhook at https:///webhooks/revenuecat +# Set the webhook Authorization header to the same REVENUECAT_WEBHOOK_AUTH value + `; } @@ -1047,6 +1086,12 @@ function buildServerVars( comment: "Dodo Payments environment - use 'test_mode' for testing, 'live_mode' for production", }, + { + key: "REVENUECAT_WEBHOOK_AUTH", + value: "", + condition: payments === "revenuecat", + comment: "Shared secret for RevenueCat webhook authentication (min 32 characters)", + }, { key: "RESEND_API_KEY", value: "", @@ -1912,6 +1957,7 @@ export function processEnvVariables(vfs: VirtualFileSystem, config: ProjectConfi frontend, backend, auth, + payments, config.mobilePush, config.mobileDeepLinking, ); diff --git a/packages/template-generator/src/processors/payments-deps.ts b/packages/template-generator/src/processors/payments-deps.ts index d90b4a15b..3cbbdec23 100644 --- a/packages/template-generator/src/processors/payments-deps.ts +++ b/packages/template-generator/src/processors/payments-deps.ts @@ -204,4 +204,27 @@ export function processPaymentsDeps(vfs: VirtualFileSystem, config: ProjectConfi } } } + + if (payments === "revenuecat") { + const nativePath = "apps/native/package.json"; + const hasNativeFrontend = frontend.some((f) => + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), + ); + + if (hasNativeFrontend && vfs.exists(nativePath)) { + addPackageDependency({ + vfs, + packagePath: nativePath, + dependencies: ["react-native-purchases"], + }); + } + + if (backend === "convex" && vfs.exists(backendPath)) { + addPackageDependency({ + vfs, + packagePath: backendPath, + dependencies: ["convex-revenuecat"], + }); + } + } } diff --git a/packages/template-generator/src/template-handlers/payments.ts b/packages/template-generator/src/template-handlers/payments.ts index 22d742c7d..cdccafe2d 100644 --- a/packages/template-generator/src/template-handlers/payments.ts +++ b/packages/template-generator/src/template-handlers/payments.ts @@ -18,6 +18,14 @@ export async function processPaymentsTemplates( const hasSvelteWeb = config.frontend.includes("svelte"); const hasSolidWeb = config.frontend.includes("solid"); + const nativeVariant = config.frontend.includes("native-bare") + ? "bare" + : config.frontend.includes("native-uniwind") + ? "uniwind" + : config.frontend.includes("native-unistyles") + ? "unistyles" + : null; + if (config.backend === "convex") { processTemplatesFromPrefix( vfs, @@ -26,6 +34,16 @@ export async function processPaymentsTemplates( "packages/backend", config, ); + + if (config.payments === "revenuecat" && config.auth !== "better-auth") { + processTemplatesFromPrefix( + vfs, + templates, + "payments/revenuecat/convex/no-better-auth", + "packages/backend", + config, + ); + } } else if (config.backend !== "none") { processTemplatesFromPrefix( vfs, @@ -36,12 +54,30 @@ export async function processPaymentsTemplates( ); } + if (nativeVariant) { + processTemplatesFromPrefix( + vfs, + templates, + `payments/${config.payments}/native/base`, + "apps/native", + config, + ); + processTemplatesFromPrefix( + vfs, + templates, + `payments/${config.payments}/native/${nativeVariant}`, + "apps/native", + config, + ); + } + + if (hasReactWeb) { const reactFramework = config.frontend.includes("react-vite") ? "react-router" : config.frontend.find((f) => - ["tanstack-router", "react-router", "tanstack-start", "next", "vinext"].includes(f), - ); + ["tanstack-router", "react-router", "tanstack-start", "next", "vinext"].includes(f), + ); if (reactFramework) { processTemplatesFromPrefix( vfs, diff --git a/packages/template-generator/src/utils/add-deps.ts b/packages/template-generator/src/utils/add-deps.ts index 45d525bd7..c90889471 100644 --- a/packages/template-generator/src/utils/add-deps.ts +++ b/packages/template-generator/src/utils/add-deps.ts @@ -630,6 +630,10 @@ export const dependencyVersionMap = { dodopayments: "^2.40.1", "dodopayments-checkout": "^1.9.4", + // Payments - RevenueCat + "react-native-purchases": "^9.10.5", + "convex-revenuecat": "^0.3.2", + // File Upload - UploadThing uploadthing: "^7.7.4", "@uploadthing/react": "^7.3.3", diff --git a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs index 5633f449a..f83322092 100644 --- a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs @@ -4,10 +4,14 @@ import { authComponent, createAuth } from "./auth"; {{#if (eq payments "polar")}} import { polar } from "./polar"; {{/if}} +{{#if (eq payments "revenuecat")}} +import { revenuecat } from "./revenuecat"; +{{/if}} const http = httpRouter(); -{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} +{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes +frontend "svelte") (includes frontend "solid"))}} authComponent.registerRoutes(http, createAuth, { cors: true }); {{else}} authComponent.registerRoutes(http, createAuth); @@ -16,5 +20,9 @@ authComponent.registerRoutes(http, createAuth); polar.registerRoutes(http as any); {{/if}} +{{#if (eq payments "revenuecat")}} + +revenuecat.registerRoutes(http); +{{/if}} -export default http; +export default http; \ No newline at end of file diff --git a/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs index 2aaffa17d..de84f8d12 100644 --- a/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs @@ -6,6 +6,10 @@ import { NAV_THEME } from "@/lib/constants"; import { authClient } from "@/lib/auth-client"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; @@ -105,6 +109,11 @@ return ( )} + + {{#if (eq payments "revenuecat")}} + + + {{/if}} @@ -185,4 +194,4 @@ privateDataText: { fontSize: 14, }, }); -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs index 5e324d965..7d2ed4e00 100644 --- a/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs @@ -6,6 +6,10 @@ import { StyleSheet } from "react-native-unistyles"; import { Container } from "@/components/container"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; @@ -95,6 +99,11 @@ export default function Home() { )} + + {{#if (eq payments "revenuecat")}} + + + {{/if}} diff --git a/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs index 45bfdc5f8..18760bd1b 100644 --- a/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs @@ -6,6 +6,10 @@ import { Ionicons } from "@expo/vector-icons"; import { Card, Chip, useThemeColor } from "heroui-native"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; @@ -119,6 +123,13 @@ return ( )} + + {{#if (eq payments "revenuecat")}} + + + + + {{/if}} ); } diff --git a/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs b/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs index a9e92b567..880144655 100644 --- a/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +++ b/packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs @@ -5,6 +5,9 @@ import betterAuth from "@convex-dev/better-auth/convex.config"; {{#if (eq payments "polar")}} import polar from "@convex-dev/polar/convex.config"; {{/if}} +{{#if (eq payments "revenuecat")}} +import revenuecat from "convex-revenuecat/convex.config"; +{{/if}} {{#if (includes examples "ai")}} import agent from "@convex-dev/agent/convex.config"; {{/if}} @@ -16,8 +19,11 @@ app.use(betterAuth); {{#if (eq payments "polar")}} app.use(polar); {{/if}} +{{#if (eq payments "revenuecat")}} +app.use(revenuecat); +{{/if}} {{#if (includes examples "ai")}} app.use(agent); {{/if}} -export default app; +export default app; \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs index aa2c51456..c8a2258cb 100644 --- a/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs @@ -3,6 +3,10 @@ import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from "react-nati import { Container } from "@/components/container"; import { useColorScheme } from "@/lib/use-color-scheme"; import { NAV_THEME } from "@/lib/constants"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{#if (eq api "orpc")}} import { useQuery } from "@tanstack/react-query"; import { orpc } from "@/utils/orpc"; @@ -157,6 +161,10 @@ return ( )} {{/if}} + {{#if (eq payments "revenuecat")}} + + + {{/if}} @@ -233,4 +241,4 @@ marginBottom: 8, fontWeight: "bold", }, }); -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs b/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs index 976fbf688..82ba82d70 100644 --- a/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs @@ -4,32 +4,32 @@ import "@/polyfills"; {{/if}} {{#if (eq backend "convex")}} - {{#if (isBetterAuth auth)}} - import { ConvexReactClient } from "convex/react"; - import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; - import { authClient } from "@/lib/auth-client"; - import { env } from "@{{projectName}}/env/native"; - {{else}} - import { ConvexProvider, ConvexReactClient } from "convex/react"; - import { env } from "@{{projectName}}/env/native"; - {{/if}} - {{#if (eq auth "clerk")}} - import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; - import { ConvexProviderWithClerk } from "convex/react-clerk"; - import { tokenCache } from "@clerk/clerk-expo/token-cache"; - {{/if}} +{{#if (isBetterAuth auth)}} +import { ConvexReactClient } from "convex/react"; +import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; +import { authClient } from "@/lib/auth-client"; +import { env } from "@{{projectName}}/env/native"; +{{else}} +import { ConvexProvider, ConvexReactClient } from "convex/react"; +import { env } from "@{{projectName}}/env/native"; +{{/if}} +{{#if (eq auth "clerk")}} +import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; +import { ConvexProviderWithClerk } from "convex/react-clerk"; +import { tokenCache } from "@clerk/clerk-expo/token-cache"; +{{/if}} {{else}} - {{#unless (eq api "none")}} - import { QueryClientProvider } from "@tanstack/react-query"; - {{/unless}} +{{#unless (eq api "none")}} +import { QueryClientProvider } from "@tanstack/react-query"; +{{/unless}} {{/if}} import { Stack } from "expo-router"; import { - DarkTheme, - DefaultTheme, - type Theme, - ThemeProvider, +DarkTheme, +DefaultTheme, +type Theme, +ThemeProvider, } from "@react-navigation/native"; import { StatusBar } from "expo-status-bar"; import { GestureHandlerRootView } from "react-native-gesture-handler"; @@ -50,146 +50,155 @@ import { MobileUIProvider } from "@/components/mobile-ui-provider"; {{#if (eq mobileOTA "expo-updates")}} import { useUpdateCheck } from "@/lib/updates"; {{/if}} +{{#if (eq payments "revenuecat")}} +import { RevenueCatProvider } from "@/contexts/revenuecat-context"; +{{/if}} const LIGHT_THEME: Theme = { - ...DefaultTheme, - colors: NAV_THEME.light, +...DefaultTheme, +colors: NAV_THEME.light, }; const DARK_THEME: Theme = { - ...DarkTheme, - colors: NAV_THEME.dark, +...DarkTheme, +colors: NAV_THEME.dark, }; export const unstable_settings = { - initialRouteName: "(drawer)", +initialRouteName: "(drawer)", }; {{#if (eq backend "convex")}} const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, { - unsavedChangesWarning: false, +unsavedChangesWarning: false, }); {{/if}} const useIsomorphicLayoutEffect = - Platform.OS === "web" && typeof window === "undefined" - ? React.useEffect - : React.useLayoutEffect; +Platform.OS === "web" && typeof window === "undefined" +? React.useEffect +: React.useLayoutEffect; const styles = StyleSheet.create({ - container: { - flex: 1, - }, +container: { +flex: 1, +}, }); export default function RootLayout() { - {{#if (eq mobileOTA "expo-updates")}} - useUpdateCheck(); - {{/if}} - React.useEffect(() => { - {{#if (eq mobilePush "expo-notifications")}} - if (Platform.OS !== "web") { - import("@/lib/notifications") - .then(({ registerForPushNotificationsAsync }) => registerForPushNotificationsAsync()) - .catch(console.warn); - } - {{/if}} - }, []); - const hasMounted = useRef(false); - const { colorScheme, isDarkColorScheme } = useColorScheme(); - const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false); +{{#if (eq mobileOTA "expo-updates")}} +useUpdateCheck(); +{{/if}} +React.useEffect(() => { +{{#if (eq mobilePush "expo-notifications")}} +if (Platform.OS !== "web") { +import("@/lib/notifications") +.then(({ registerForPushNotificationsAsync }) => registerForPushNotificationsAsync()) +.catch(console.warn); +} +{{/if}} +}, []); +const hasMounted = useRef(false); +const { colorScheme, isDarkColorScheme } = useColorScheme(); +const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false); - useIsomorphicLayoutEffect(() => { - if (hasMounted.current) { - return; - } - setAndroidNavigationBar(colorScheme); - setIsColorSchemeLoaded(true); - hasMounted.current = true; - }, []); +useIsomorphicLayoutEffect(() => { +if (hasMounted.current) { +return; +} +setAndroidNavigationBar(colorScheme); +setIsColorSchemeLoaded(true); +hasMounted.current = true; +}, []); - if (!isColorSchemeLoaded) { - return null; - } +if (!isColorSchemeLoaded) { +return null; +} - return ( - <> - {{#if (eq backend "convex")}} - {{#if (eq auth "clerk")}} - - - - - - - - - - - - - - - - - {{else if (isBetterAuth auth)}} - - - - - - - - - - - - - - {{else}} - - - - - - - - - - - - - - {{/if}} - {{else}} - {{#unless (eq api "none")}} - - - - - - - - - - - - - - {{else}} - - - - - +return ( +<> + {{#if (eq payments "revenuecat")}} + + {{/if}} + {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + + + + + + + - + + - - - - {{/unless}} - {{/if}} - - ); + + + + + + {{else if (isBetterAuth auth)}} + + + + + + + + + + + + + + {{else}} + + + + + + + + + + + + + + {{/if}} + {{else}} + {{#unless (eq api "none")}} + + + + + + + + + + + + + + {{else}} + + + + + + + + + + + + {{/unless}} + {{/if}} + {{#if (eq payments "revenuecat")}} + + {{/if}} + +); } -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs index 4975059e5..1c508f109 100644 --- a/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs @@ -23,6 +23,10 @@ import { api } from "@{{ projectName }}/backend/convex/_generated/api"; import { authClient } from "@/lib/auth-client"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{else if (eq backend "convex")}} import { useQuery } from "convex/react"; import { api } from "@{{ projectName }}/backend/convex/_generated/api"; @@ -187,6 +191,11 @@ export default function Home() { )} + + {{#if (eq payments "revenuecat")}} + + + {{/if}} {{/if}} diff --git a/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs b/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs index 985826316..92fed0335 100644 --- a/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs @@ -39,6 +39,10 @@ import { StatusBar } from "expo-status-bar"; {{#if (eq mobileOTA "expo-updates")}} import { useUpdateCheck } from "@/lib/updates"; {{/if}} +{{#if (eq payments "revenuecat")}} +import { RevenueCatProvider } from "@/contexts/revenuecat-context"; +{{/if}} + export const unstable_settings = { initialRouteName: "(drawer)", @@ -66,6 +70,9 @@ export default function RootLayout() { const { theme } = useUnistyles(); return ( + {{#if (eq payments "revenuecat")}} + + {{/if}} {{#if (eq backend "convex")}} {{#if (eq auth "clerk")}} {{/unless}} {{/if}} + {{#if (eq payments "revenuecat")}} + + {{/if}} ); } {{/if}} diff --git a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs index d00aa8f6d..b0716aafa 100644 --- a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs @@ -21,6 +21,10 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api"; import { authClient } from "@/lib/auth-client"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; +{{#if (eq payments "revenuecat")}} +import { SubscriptionStatusCard } from "@/components/subscription-status-card"; +import { PaywallExample } from "@/components/paywall-example"; +{{/if}} {{else if (eq backend "convex")}} import { useQuery } from "convex/react"; import { api } from "@{{projectName}}/backend/convex/_generated/api"; @@ -106,7 +110,8 @@ return ( : "API Disconnected"} - {isLoading && } + {isLoading && + } {!isLoading && isConnected && ( )} @@ -133,10 +138,14 @@ return ( - + - + @@ -155,13 +164,10 @@ return ( {user.name} {user.email} - @@ -186,8 +192,13 @@ return ( )} + {{#if (eq payments "revenuecat")}} + + + + {{/if}} ); } -{{/if}} +{{/if}} \ No newline at end of file diff --git a/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs b/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs index fcf26198a..f09263cd6 100644 --- a/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs @@ -40,6 +40,10 @@ import { useUpdateCheck } from "@/lib/updates"; {{/if}} import { AppThemeProvider } from "@/contexts/app-theme-context"; +{{#if (eq payments "revenuecat")}} +import { RevenueCatProvider } from "@/contexts/revenuecat-context"; +{{/if}} + {{#if (eq api "trpc")}} import { queryClient } from "@/utils/trpc"; {{/if}} @@ -83,6 +87,9 @@ export default function Layout() { {{/if}} }, []); return ( + {{#if (eq payments "revenuecat")}} + + {{/if}} {{#if (eq backend "convex")}} {{#if (eq auth "clerk")}} @@ -148,6 +155,9 @@ export default function Layout() { {{/unless}} {{/if}} + {{#if (eq payments "revenuecat")}} + + {{/if}} ); } {{/if}} diff --git a/packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs b/packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs new file mode 100644 index 000000000..c564a03cd --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/convex/backend/convex/revenuecat.ts.hbs @@ -0,0 +1,9 @@ +import { RevenueCat } from "convex-revenuecat"; + +import { components } from "./_generated/api"; + +export const revenuecat = new RevenueCat(components.revenuecat, { + REVENUECAT_WEBHOOK_AUTH: process.env.REVENUECAT_WEBHOOK_AUTH, +}); + +export const { hasEntitlement, isSubscriber, getActiveSubscriptions } = revenuecat.api() \ No newline at end of file diff --git a/packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs b/packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs new file mode 100644 index 000000000..00d748acc --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/convex/no-better-auth/convex/http.ts.hbs @@ -0,0 +1,9 @@ +import { httpRouter } from "convex/server"; + +import { revenuecat } from "./revenuecat"; + +const http = httpRouter(); + +revenuecat.registerRoutes(http); + +export default http; \ No newline at end of file diff --git a/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs b/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs new file mode 100644 index 000000000..7f76d5f54 --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs @@ -0,0 +1,129 @@ +import { useState } from "react"; +import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui"; +import { ActivityIndicator, Alert, StyleSheet, View } from "react-native"; +import type { PurchasesPackage } from "react-native-purchases"; + +import { NAV_THEME } from "@/lib/constants"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { useRevenueCat } from "@/contexts/revenuecat-context"; + +export function PaywallExample() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const { isPro, getPackages, purchasePackage, restorePurchases } = useRevenueCat(); + const [packages, setPackages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isPurchasing, setIsPurchasing] = useState(false); + const [isRestoring, setIsRestoring] = useState(false); + const isBusy = isPurchasing || isRestoring; + + const loadPackages = async () => { + setIsLoading(true); + try { + setPackages(await getPackages()); + } finally { + setIsLoading(false); + } + }; + + const handlePurchase = async (pack: PurchasesPackage) => { + if (isBusy) return; + setIsPurchasing(true); + try { + const success = await purchasePackage(pack); + Alert.alert(success ? "You're now Premium!" : "Purchase not completed"); + } finally { + setIsPurchasing(false); + } + }; + + const handleRestore = async () => { + if (isBusy) return; + setIsRestoring(true); + try { + const success = await restorePurchases(); + Alert.alert(success ? "Purchases restored" : "No purchases to restore"); + } finally { + setIsRestoring(false); + } + }; + + if (isPro) { + return ( + + + + + You're Premium + + + Thanks for your support — enjoy all features. + + + + + ); + } + + return ( + + + + + Upgrade to Premium + + + Unlock all features. + + + + + {isLoading ? ( + + + + ) : ( + + + {packages.length === 0 ? ( + + ) : ( + packages.map((pack) => ( + + )) + )} + + + + + ); +} \ No newline at end of file diff --git a/packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs b/packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs new file mode 100644 index 000000000..624c73683 --- /dev/null +++ b/packages/template-generator/templates/payments/revenuecat/native/uniwind/components/subscription-status-card.tsx.hbs @@ -0,0 +1,25 @@ +import { Text } from "react-native"; +import { Card } from "heroui-native"; + +import { useRevenueCat } from "@/contexts/revenuecat-context"; + +export function SubscriptionStatusCard() { + const { isPro, isConfigured } = useRevenueCat(); + + return ( + + Subscription + {!isConfigured ? ( + + RevenueCat is not configured yet. Add your API keys in apps/native/.env. + + ) : isPro ? ( + + You're a Premium member 🎉 + + ) : ( + You're on the free plan. + )} + + ); +} \ No newline at end of file diff --git a/packages/types/src/compatibility.ts b/packages/types/src/compatibility.ts index bb9bde9b0..58f1542d6 100644 --- a/packages/types/src/compatibility.ts +++ b/packages/types/src/compatibility.ts @@ -2056,6 +2056,15 @@ export const getDisabledReason = ( return "Dodo Payments are not yet supported for React + Vite projects"; } + if (category === "payments" && optionId === "revenuecat") { + const hasNativeFrontend = currentStack.nativeFrontend.some((f) => + ["native-bare", "native-uniwind", "native-unistyles"].includes(f) + ); + if (!hasNativeFrontend) { + return "RevenueCat payments requires a native frontend (native-bare, native-uniwind, or native-unistyles)"; + } + } + // ============================================ // CMS CONSTRAINTS // ============================================ diff --git a/packages/types/src/schemas.ts b/packages/types/src/schemas.ts index 5d6ccf68f..f29326174 100644 --- a/packages/types/src/schemas.ts +++ b/packages/types/src/schemas.ts @@ -220,7 +220,7 @@ export const AuthSchema = z .describe("Authentication provider"); export const PaymentsSchema = z - .enum(["polar", "stripe", "lemon-squeezy", "paddle", "dodo", "none"]) + .enum(["revenuecat","polar", "stripe", "lemon-squeezy", "paddle", "dodo", "none"]) .describe("Payments provider"); export const WebDeploySchema = z From a3dc495a3c25cabc1f427f92bd2745a7ae6e0a50 Mon Sep 17 00:00:00 2001 From: Ahmad Alisha Date: Wed, 17 Jun 2026 15:25:26 +0530 Subject: [PATCH 5/8] chore:fix prompt resolver registry and tech resource link configuration --- apps/cli/src/prompts/prompt-resolver-registry.ts | 5 ++++- apps/web/src/lib/tech-resource-links.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/prompts/prompt-resolver-registry.ts b/apps/cli/src/prompts/prompt-resolver-registry.ts index 26e15ad33..1c3829126 100644 --- a/apps/cli/src/prompts/prompt-resolver-registry.ts +++ b/apps/cli/src/prompts/prompt-resolver-registry.ts @@ -321,7 +321,10 @@ export const PROMPT_RESOLVER_REGISTRY: ResolverRegistry = { payments: { schemaValues: PAYMENTS_VALUES, resolve: resolvePaymentsPrompt, - coverageContexts: [{ auth: "better-auth", backend: "hono", frontends: ["next"] }], + coverageContexts: [ + { auth: "better-auth", backend: "hono", frontends: ["next"] }, + { auth: "better-auth", backend: "hono", frontends: ["native-bare"] }, + ], }, realtime: { schemaValues: REALTIME_VALUES, diff --git a/apps/web/src/lib/tech-resource-links.ts b/apps/web/src/lib/tech-resource-links.ts index 1113f9df7..e0e306412 100644 --- a/apps/web/src/lib/tech-resource-links.ts +++ b/apps/web/src/lib/tech-resource-links.ts @@ -420,6 +420,7 @@ const BASE_LINKS: LinkMap = { githubUrl: "https://github.com/PaddleHQ/paddle-node-sdk", }, dodo: { docsUrl: "https://docs.dodopayments.com/" }, + revenuecat: { docsUrl: "https://www.revenuecat.com/docs" }, resend: { docsUrl: "https://resend.com/docs", githubUrl: "https://github.com/resend/resend-node", From e87287cc811b8b00221b9457cdbd41f4da8a21d0 Mon Sep 17 00:00:00 2001 From: Ahmad Alisha Date: Thu, 18 Jun 2026 09:57:57 +0530 Subject: [PATCH 6/8] chore : fixed syntax error --- .../templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs index b0716aafa..59da3388a 100644 --- a/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs @@ -198,6 +198,7 @@ return ( {{/if}} + {{/if}} ); } From 00a242fc854641ca9308e42dbeeb75ee0dc6f626 Mon Sep 17 00:00:00 2001 From: Marve10s Date: Sat, 4 Jul 2026 15:38:09 +0300 Subject: [PATCH 7/8] fix(revenuecat): make native-bare components type-check and tighten gating - Rewrite native-bare paywall/subscription components to plain react-native primitives (View/Text/Pressable/StyleSheet). They previously imported from `@expo/ui`, which is never added as a dependency, so `native-bare + revenuecat` failed `tsc --noEmit` (TS2307). Matches the PR's stated 'standard StyleSheet' intent for native-bare and the unistyles variant's structure. - Add a revenuecat branch to analyzeStackCompatibility so a web-only stack with payments=revenuecat is auto-normalized to none (mirrors polar), keeping the auto-adjust engine consistent with the prompt/getDisabledReason/CLI gates. - Stop writing REVENUECAT_WEBHOOK_AUTH to apps/server/.env for non-convex backends (frontend-only path has no server webhook route; convex still gets it via packages/backend/.env.local). --- .../src/processors/env-vars.ts | 6 - .../bare/components/paywall-example.tsx.hbs | 106 ++++++++++-------- .../subscription-status-card.tsx.hbs | 49 ++++---- packages/types/src/compatibility.ts | 16 ++- 4 files changed, 99 insertions(+), 78 deletions(-) diff --git a/packages/template-generator/src/processors/env-vars.ts b/packages/template-generator/src/processors/env-vars.ts index 1f10fdae2..480fe621f 100644 --- a/packages/template-generator/src/processors/env-vars.ts +++ b/packages/template-generator/src/processors/env-vars.ts @@ -1090,12 +1090,6 @@ function buildServerVars( comment: "Dodo Payments environment - use 'test_mode' for testing, 'live_mode' for production", }, - { - key: "REVENUECAT_WEBHOOK_AUTH", - value: "", - condition: payments === "revenuecat", - comment: "Shared secret for RevenueCat webhook authentication (min 32 characters)", - }, { key: "RESEND_API_KEY", value: "", diff --git a/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs b/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs index 7f76d5f54..a11324cbb 100644 --- a/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs +++ b/packages/template-generator/templates/payments/revenuecat/native/bare/components/paywall-example.tsx.hbs @@ -1,6 +1,5 @@ import { useState } from "react"; -import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui"; -import { ActivityIndicator, Alert, StyleSheet, View } from "react-native"; +import { ActivityIndicator, Alert, Pressable, StyleSheet, Text, View } from "react-native"; import type { PurchasesPackage } from "react-native-purchases"; import { NAV_THEME } from "@/lib/constants"; @@ -51,79 +50,90 @@ export function PaywallExample() { if (isPro) { return ( - - - - You're Premium - - - Thanks for your support — enjoy all features. - - - + You're Premium + + Thanks for your support — enjoy all features. + ); } return ( - - - - Upgrade to Premium - - - Unlock all features. - - - + Upgrade to Premium + Unlock all features. {isLoading ? ( + ) : packages.length === 0 ? ( + + View plans + ) : ( - - - {packages.length === 0 ? ( -